diff --git a/packages/kbot/docs/images-tauri-5.md b/packages/kbot/docs/images-tauri-5.md new file mode 100644 index 00000000..bf5710cc --- /dev/null +++ b/packages/kbot/docs/images-tauri-5.md @@ -0,0 +1,135 @@ +# Image Generation Architecture — Platform v5 + +This document captures the shape of the refreshed multiplatform image generation plan that we will break down into actionable tasks next. It keeps the current CLI + desktop flow, layers in mobile (Android/iOS) expectations, and sketches a browser/web-app path with configurable endpoints. + +## 1. CLI Desktop (Current Flow) + +- **Ownership**: `src/commands/images.ts` remains the orchestration point; it spawns the packaged Tauri desktop binary and handles filesystem writes. +- **IPC Contract**: JSON payloads over `stdin`/`stdout` between the CLI and Tauri. The CLI continues to push resolved prompts, destination paths, API key, and included files. +- **Image Ops**: Google Generative AI integration stays in Node-land (`createImage`, `editImage`) with `@polymech/fs` helpers for persistence. + +```ts +// CLI-side launch (simplified excerpt) +const tauriProcess = spawn(getGuiAppPath(), args, { stdio: ['pipe', 'pipe', 'pipe'] }); +tauriProcess.stdin?.write(JSON.stringify({ + cmd: 'forward_config_to_frontend', + prompt: argv.prompt, + dst: argv.dst, + apiKey: apiKey, + files: absoluteIncludes, +}) + '\n'); +``` + +**Libraries**: existing stack (`@polymech` packages, `tslog`, Node core modules). No new work required beyond polish/bugfix. + +## 2. Android / iOS — Standalone Tauri + +Desktop spawning is not available on mobile; the GUI ships as the full application. We lean on the TypeScript layer plus Tauri’s HTTP plugin to hit Google’s endpoints without wiring Rust-side HTTP clients. + +### Requirements +- Bundle `@tauri-apps/plugin-http`, `@tauri-apps/plugin-os`, `@tauri-apps/plugin-fs`. +- Rely on the existing `tauriApi.fetch` abstraction so we do not unwrap the plugin everywhere. +- Persist lightweight state (prompt history, cached API key) in app data dir just like desktop. + +### Example TypeScript Mobile Client + +```ts +// gui/tauri-app/src/lib/mobileClient.ts +import { tauriApi } from './tauriApi'; + +const GOOGLE_BASE = 'https://generativelanguage.googleapis.com/v1beta'; + +export async function mobileCreateImage(prompt: string, apiKey: string, model = 'gemini-2.5-flash-image-preview') { + const response = await tauriApi.fetch(`${GOOGLE_BASE}/models/${model}:generateContent`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }), + }); + + const data = await response.json(); + const inline = data.candidates?.[0]?.content?.parts?.find((part: any) => part.inlineData)?.inlineData; + if (!inline?.data) throw new Error('No image data in Gemini response'); + return Buffer.from(inline.data, 'base64'); +} +``` + +### Configuration Notes +- `tauri.conf.json` must whitelist `https://generativelanguage.googleapis.com/**` inside the HTTP plugin scope and CSP `connect-src`. +- Add platform detection inside the React/Svelte front-end to toggle mobile-first UX and storage paths. + +**Libraries**: `@tauri-apps/plugin-http`, `@tauri-apps/api`, `@google/generative-ai` (optional; the REST fetch example above avoids it if desired), existing UI stack. + +## 3. Web App — Browser, Configurable Endpoints + +Constraints (CORS, secret handling) require a server-side companion and a client that can be pointed at custom endpoints per user/tenant. The browser front-end holds no secrets; all API keys live server-side. + +### Backend Sketch (Hono) + +```ts +// web/api/imageServer.ts +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { GoogleGenerativeAI } from '@google/generative-ai'; + +const app = new Hono(); + +app.use('/*', cors({ + origin: ['http://localhost:3000', 'https://your-frontend.example'], + allowHeaders: ['Content-Type', 'Authorization'], + allowMethods: ['POST', 'OPTIONS'], +})); + +app.post('/api/images/create', async (c) => { + const { prompt, apiKey, model = 'gemini-2.5-flash-image-preview' } = await c.req.json(); + const genAI = new GoogleGenerativeAI(apiKey); + const modelClient = genAI.getGenerativeModel({ model }); + const result = await modelClient.generateContent(prompt); + const inline = result.response.candidates?.[0]?.content?.parts?.find((part) => 'inlineData' in part)?.inlineData; + if (!inline?.data) return c.json({ success: false, error: 'No image data' }, 500); + return c.json({ success: true, image: inline }); +}); + +export default app; +``` + +### Browser Client Stub + +```ts +// web/client/webImageClient.ts +export class WebImageClient { + constructor(private endpoint: string) {} + + async createImage(prompt: string, apiKeyAlias: string) { + const res = await fetch(`${this.endpoint}/api/images/create`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ prompt, apiKey: apiKeyAlias }), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = await res.json(); + if (!data.success) throw new Error(data.error || 'Unknown backend error'); + return data.image; // caller decides how to render Blob/Base64 + } +} +``` + +### Configuration Extension + +- Expand shared config schema with a `web.apiEndpoint` block and optional per-user overrides. +- Allow `cli` users to pass `--web-endpoint` for headless flows that still want the backend. +- Document environment variable support (`REACT_APP_API_ENDPOINT`, `VITE_IMAGE_API_URL`, etc.). + +**Libraries**: `hono`, `hono/cors`, `@google/generative-ai`, hosting runtime (`bun`, `node`, or serverless). Front-end remains React/Vite/SvelteKit as today. + +## Cross-Platform Checklist (Preview) + +- Align TypeScript interfaces (`UnifiedImageGenerator`) so desktop/mobile/web can plug into the same UI surface. +- Ensure persistent storage format (`.kbot-gui.json`) works across platforms—consider namespacing mobile vs desktop history entries. +- Plan rate limiting and API key management per platform (mobile secure storage, web backend vault). +- Identify testing layers (unit mocks for fetch, integration harness for Tauri mobile, e2e web flows). + +This structure will be decomposed into a detailed TODO roadmap in the following slice. + diff --git a/packages/kbot/docs/images-tauri-gem.md b/packages/kbot/docs/images-tauri-gem.md new file mode 100644 index 00000000..98f873b6 --- /dev/null +++ b/packages/kbot/docs/images-tauri-gem.md @@ -0,0 +1,603 @@ +# Image Generation Architecture — Multi-Platform Strategy + +This document outlines the architectural approach for supporting image generation across CLI (desktop), mobile (Android/iOS), and web platforms while maintaining code reuse and consistent user experience. + +## Current State Analysis + +The existing CLI flow works well for desktop scenarios: +- `src/commands/images.ts` orchestrates the process +- Spawns Tauri desktop binary via `spawn()` +- Handles image operations through Google Generative AI +- Uses filesystem operations via `@polymech/fs` +- IPC communication over stdin/stdout with JSON payloads + +## 1. CLI Desktop (Current Flow - Maintained) + +**Architecture**: CLI spawns Tauri GUI, handles all image operations in Node.js + +```ts +// src/commands/images.ts (existing pattern) +const tauriProcess = spawn(getGuiAppPath(), args, { stdio: ['pipe', 'pipe', 'pipe'] }); + +// Send config to GUI +tauriProcess.stdin?.write(JSON.stringify({ + cmd: 'forward_config_to_frontend', + prompt: argv.prompt, + dst: argv.dst, + apiKey: apiKey, + files: absoluteIncludes, +}) + '\n'); + +// Handle generation requests from GUI +if (message.type === 'generate_request') { + const imageBuffer = genFiles.length > 0 + ? await editImage(genPrompt, genFiles, parsedOptions) + : await createImage(genPrompt, parsedOptions); + + write(finalDstPath, imageBuffer); +} +``` + +**Libraries**: +- Existing stack: `@polymech/fs`, `tslog`, Node core modules +- Google Generative AI integration +- Tauri for GUI spawning + +**No changes required** - this flow remains optimal for desktop CLI usage. + +## 2. Android/iOS - Standalone Tauri with TypeScript HTTP Client + +**Architecture**: Tauri app runs standalone, TypeScript handles HTTP calls directly + +Since mobile platforms cannot spawn processes, the Tauri app becomes the primary application. We leverage Tauri's HTTP plugin to make API calls from the TypeScript frontend. + +### Configuration Updates + +```json +// gui/tauri-app/src-tauri/tauri.conf.json +{ + "plugins": { + "http": { + "scope": [ + "https://generativelanguage.googleapis.com/**" + ] + } + }, + "security": { + "csp": "connect-src 'self' https://generativelanguage.googleapis.com" + } +} +``` + +### Mobile Image Client + +```ts +// gui/tauri-app/src/lib/mobileImageClient.ts +import { tauriApi } from './tauriApi'; + +const GOOGLE_GENERATIVE_AI_BASE = 'https://generativelanguage.googleapis.com/v1beta'; + +export interface MobileImageOptions { + model?: string; + apiKey: string; +} + +export class MobileImageClient { + constructor(private options: MobileImageOptions) {} + + async createImage(prompt: string): Promise { + const { model = 'gemini-2.5-flash-image-preview', apiKey } = this.options; + + const response = await tauriApi.fetch(`${GOOGLE_GENERATIVE_AI_BASE}/models/${model}:generateContent`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + contents: [{ + parts: [{ text: prompt }] + }] + }), + }); + + if (!response.ok) { + throw new Error(`Google API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + const inline = data.candidates?.[0]?.content?.parts?.find( + (part: any) => part.inlineData + )?.inlineData; + + if (!inline?.data) { + throw new Error('No image data in Gemini response'); + } + + return Buffer.from(inline.data, 'base64'); + } + + async editImage(prompt: string, imageFiles: string[]): Promise { + const { model = 'gemini-2.5-flash-image-preview', apiKey } = this.options; + + // Read image files using Tauri FS + const imageParts = await Promise.all( + imageFiles.map(async (filePath) => { + const imageData = await tauriApi.fs.readFile(filePath); + const base64 = btoa(String.fromCharCode(...imageData)); + const mimeType = filePath.toLowerCase().endsWith('.png') ? 'image/png' : 'image/jpeg'; + + return { + inlineData: { + mimeType, + data: base64 + } + }; + }) + ); + + const response = await tauriApi.fetch(`${GOOGLE_GENERATIVE_AI_BASE}/models/${model}:generateContent`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + contents: [{ + parts: [ + { text: prompt }, + ...imageParts + ] + }] + }), + }); + + if (!response.ok) { + throw new Error(`Google API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + const inline = data.candidates?.[0]?.content?.parts?.find( + (part: any) => part.inlineData + )?.inlineData; + + if (!inline?.data) { + throw new Error('No image data in Gemini response'); + } + + return Buffer.from(inline.data, 'base64'); + } +} +``` + +### Mobile Integration + +```ts +// gui/tauri-app/src/components/MobileImageWizard.tsx +import { MobileImageClient } from '../lib/mobileImageClient'; + +export function MobileImageWizard() { + const [apiKey, setApiKey] = useState(''); + const [prompt, setPrompt] = useState(''); + + const handleGenerate = async () => { + const client = new MobileImageClient({ apiKey }); + + try { + const imageBuffer = await client.createImage(prompt); + + // Save to mobile app data directory + const appDataDir = await tauriApi.path.appDataDir(); + const imagePath = await tauriApi.path.join(appDataDir, `generated_${Date.now()}.png`); + + await tauriApi.fs.writeFile(imagePath, imageBuffer); + + // Update UI with generated image + setGeneratedImage(imagePath); + } catch (error) { + console.error('Generation failed:', error); + } + }; + + return ( +
+ {/* Mobile-optimized UI */} +
+ ); +} +``` + +**Libraries**: +- `@tauri-apps/plugin-http` - HTTP requests +- `@tauri-apps/plugin-fs` - File system operations +- `@tauri-apps/plugin-os` - Platform detection +- Existing React/TypeScript stack + +## 3. Web App - Browser with Backend API + +**Architecture**: Browser frontend + backend API server, configurable endpoints + +Web browsers have CORS restrictions and cannot store API keys securely. We need a backend service to handle API calls and a configurable frontend. + +### Backend API Server (Hono) + +```ts +// web/api/imageServer.ts +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { GoogleGenerativeAI } from '@google/generative-ai'; +import { z } from 'zod'; + +const app = new Hono(); + +// CORS configuration +app.use('/*', cors({ + origin: [ + 'http://localhost:3000', + 'http://localhost:5173', // Vite dev + process.env.FRONTEND_URL || 'https://your-app.example.com' + ], + allowHeaders: ['Content-Type', 'Authorization', 'X-API-Key'], + allowMethods: ['POST', 'GET', 'OPTIONS'], +})); + +// Request schemas +const CreateImageSchema = z.object({ + prompt: z.string().min(1), + model: z.string().default('gemini-2.5-flash-image-preview'), + userApiKey: z.string().optional(), // User-provided API key +}); + +const EditImageSchema = z.object({ + prompt: z.string().min(1), + images: z.array(z.object({ + data: z.string(), // base64 + mimeType: z.string(), + })), + model: z.string().default('gemini-2.5-flash-image-preview'), + userApiKey: z.string().optional(), +}); + +// Middleware for API key resolution +const resolveApiKey = async (c: any, userApiKey?: string) => { + // Priority: user-provided > environment > tenant-specific + return userApiKey || + process.env.GOOGLE_GENERATIVE_AI_KEY || + await getTenantApiKey(c.req.header('X-Tenant-ID')); +}; + +app.post('/api/images/create', async (c) => { + try { + const body = await c.req.json(); + const { prompt, model, userApiKey } = CreateImageSchema.parse(body); + + const apiKey = await resolveApiKey(c, userApiKey); + if (!apiKey) { + return c.json({ success: false, error: 'No API key available' }, 401); + } + + const genAI = new GoogleGenerativeAI(apiKey); + const modelClient = genAI.getGenerativeModel({ model }); + + const result = await modelClient.generateContent(prompt); + const response = await result.response; + + const inline = response.candidates?.[0]?.content?.parts?.find( + (part) => 'inlineData' in part + )?.inlineData; + + if (!inline?.data) { + return c.json({ success: false, error: 'No image data in response' }, 500); + } + + return c.json({ + success: true, + image: { + data: inline.data, + mimeType: inline.mimeType || 'image/png' + } + }); + } catch (error) { + console.error('Create image error:', error); + return c.json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }, 500); + } +}); + +app.post('/api/images/edit', async (c) => { + try { + const body = await c.req.json(); + const { prompt, images, model, userApiKey } = EditImageSchema.parse(body); + + const apiKey = await resolveApiKey(c, userApiKey); + if (!apiKey) { + return c.json({ success: false, error: 'No API key available' }, 401); + } + + const genAI = new GoogleGenerativeAI(apiKey); + const modelClient = genAI.getGenerativeModel({ model }); + + const parts = [ + { text: prompt }, + ...images.map(img => ({ + inlineData: { + mimeType: img.mimeType, + data: img.data + } + })) + ]; + + const result = await modelClient.generateContent({ contents: [{ parts }] }); + const response = await result.response; + + const inline = response.candidates?.[0]?.content?.parts?.find( + (part) => 'inlineData' in part + )?.inlineData; + + if (!inline?.data) { + return c.json({ success: false, error: 'No image data in response' }, 500); + } + + return c.json({ + success: true, + image: { + data: inline.data, + mimeType: inline.mimeType || 'image/png' + } + }); + } catch (error) { + console.error('Edit image error:', error); + return c.json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }, 500); + } +}); + +// Health check +app.get('/api/health', (c) => { + return c.json({ status: 'ok', timestamp: new Date().toISOString() }); +}); + +export default app; +``` + +### Web Client + +```ts +// web/client/webImageClient.ts +export interface WebImageClientConfig { + endpoint: string; + apiKey?: string; // Optional user API key + tenantId?: string; +} + +export interface ImageResult { + data: string; // base64 + mimeType: string; +} + +export class WebImageClient { + constructor(private config: WebImageClientConfig) {} + + async createImage(prompt: string, model?: string): Promise { + const response = await fetch(`${this.config.endpoint}/api/images/create`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(this.config.tenantId && { 'X-Tenant-ID': this.config.tenantId }), + }, + body: JSON.stringify({ + prompt, + model, + userApiKey: this.config.apiKey, + }), + }); + + if (!response.ok) { + const error = await response.json().catch(() => ({ error: 'Network error' })); + throw new Error(error.error || `HTTP ${response.status}`); + } + + const data = await response.json(); + if (!data.success) { + throw new Error(data.error || 'Unknown server error'); + } + + return data.image; + } + + async editImage(prompt: string, imageFiles: File[], model?: string): Promise { + // Convert files to base64 + const images = await Promise.all( + imageFiles.map(async (file) => ({ + data: await fileToBase64(file), + mimeType: file.type, + })) + ); + + const response = await fetch(`${this.config.endpoint}/api/images/edit`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(this.config.tenantId && { 'X-Tenant-ID': this.config.tenantId }), + }, + body: JSON.stringify({ + prompt, + images, + model, + userApiKey: this.config.apiKey, + }), + }); + + if (!response.ok) { + const error = await response.json().catch(() => ({ error: 'Network error' })); + throw new Error(error.error || `HTTP ${response.status}`); + } + + const data = await response.json(); + if (!data.success) { + throw new Error(data.error || 'Unknown server error'); + } + + return data.image; + } +} + +// Utility function +async function fileToBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result as string; + resolve(result.split(',')[1]); // Remove data:image/...;base64, prefix + }; + reader.onerror = reject; + reader.readAsDataURL(file); + }); +} +``` + +### Web Frontend Integration + +```tsx +// web/components/WebImageWizard.tsx +import { WebImageClient } from '../client/webImageClient'; + +export function WebImageWizard() { + const [client, setClient] = useState(null); + const [endpoint, setEndpoint] = useState(process.env.REACT_APP_API_ENDPOINT || ''); + const [apiKey, setApiKey] = useState(''); + + useEffect(() => { + if (endpoint) { + setClient(new WebImageClient({ endpoint, apiKey })); + } + }, [endpoint, apiKey]); + + const handleGenerate = async (prompt: string) => { + if (!client) return; + + try { + const result = await client.createImage(prompt); + + // Create blob URL for display + const blob = new Blob([ + Uint8Array.from(atob(result.data), c => c.charCodeAt(0)) + ], { type: result.mimeType }); + + const imageUrl = URL.createObjectURL(blob); + setGeneratedImage(imageUrl); + + // Optionally trigger download + const link = document.createElement('a'); + link.href = imageUrl; + link.download = `generated_${Date.now()}.png`; + link.click(); + } catch (error) { + console.error('Generation failed:', error); + } + }; + + return ( +
+
+ setEndpoint(e.target.value)} + /> + setApiKey(e.target.value)} + /> +
+ {/* Rest of UI */} +
+ ); +} +``` + +**Libraries**: +- **Backend**: `hono`, `hono/cors`, `@google/generative-ai`, `zod` +- **Frontend**: React/Vue/Svelte, standard web APIs +- **Deployment**: Bun, Node.js, or serverless (Vercel, Netlify Functions) + +## Configuration Schema Extension + +```ts +// shared/config/imageConfig.ts +export interface ImageConfig { + // Existing CLI config + cli?: { + model?: string; + logLevel?: number; + }; + + // Mobile-specific config + mobile?: { + model?: string; + cacheDir?: string; + maxImageSize?: number; + }; + + // Web-specific config + web?: { + apiEndpoint: string; + tenantId?: string; + allowUserApiKeys?: boolean; + maxFileSize?: number; + }; + + // Shared Google AI config + google?: { + key?: string; // For CLI and mobile + defaultModel?: string; + }; +} +``` + +## Platform Detection and Unified Interface + +```ts +// shared/lib/unifiedImageClient.ts +export interface UnifiedImageGenerator { + createImage(prompt: string, options?: any): Promise; + editImage(prompt: string, images: string[] | File[], options?: any): Promise; +} + +export async function createImageClient(config: ImageConfig): Promise { + // Detect platform + if (typeof window === 'undefined') { + // Node.js CLI environment + const { CLIImageClient } = await import('./cliImageClient'); + return new CLIImageClient(config.cli, config.google); + } else if ((window as any).__TAURI__) { + // Tauri mobile/desktop environment + const { MobileImageClient } = await import('./mobileImageClient'); + return new MobileImageClient({ apiKey: config.google?.key || '' }); + } else { + // Web browser environment + const { WebImageClient } = await import('./webImageClient'); + return new WebImageClient({ + endpoint: config.web?.apiEndpoint || '', + tenantId: config.web?.tenantId, + }); + } +} +``` + +## Summary + +This architecture provides: + +1. **CLI Desktop**: Maintains current efficient Node.js-based approach +2. **Mobile**: Leverages Tauri HTTP plugin for direct API calls from TypeScript +3. **Web**: Secure backend API with configurable endpoints and tenant support + +Each platform optimizes for its constraints while sharing common TypeScript interfaces and configuration schemas. The next step is to break this down into actionable implementation tasks. diff --git a/packages/kbot/docs/images-tauri.md b/packages/kbot/docs/images-tauri.md new file mode 100644 index 00000000..b55f3703 --- /dev/null +++ b/packages/kbot/docs/images-tauri.md @@ -0,0 +1,871 @@ +# Multi-Platform Image Generation Architecture + +## Overview + +This document outlines the architecture for supporting image generation across multiple platforms: +1. **CLI Desktop** (current implementation) - Node.js CLI spawning Tauri GUI +2. **Mobile** (Android/iOS) - Standalone Tauri app with HTTP API calls +3. **Web App** - Browser-based application with configurable endpoints + +## Current Architecture (CLI Desktop) + +### Flow +``` +CLI (images.ts) → Spawn Tauri Process → IPC Communication → Google AI API → Image Generation +``` + +### Key Components +- **CLI Entry**: `src/commands/images.ts` - Main command handler +- **Image Generation**: `src/lib/images-google.ts` - Google Generative AI integration +- **Tauri GUI**: `gui/tauri-app/` - Desktop GUI application +- **IPC Bridge**: Stdin/stdout communication between CLI and Tauri + +### Current Implementation Details +```typescript +// CLI spawns Tauri process +const tauriProcess = spawn(guiAppPath, args, { stdio: ['pipe', 'pipe', 'pipe'] }); + +// Communication via JSON messages +const configResponse = { + cmd: 'forward_config_to_frontend', + prompt: argv.prompt || null, + dst: argv.dst || null, + apiKey: apiKey || null, + files: absoluteIncludes +}; +``` + +## Platform-Specific Architectures + +### 1. CLI Desktop (Current - Keep As-Is) + +**Pros**: +- Direct file system access +- Native performance +- Existing implementation works well + +**Architecture**: +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ +│ CLI App │───▶│ Tauri GUI │───▶│ Google AI API │ +│ (images.ts) │ │ (Rust) │ │ (Direct) │ +└─────────────┘ └──────────────┘ └─────────────────┘ +``` + +### 2. Mobile (Android/iOS) - Standalone Tauri + +**Challenge**: No CLI spawning capability on mobile +**Solution**: Standalone Tauri app with HTTP client for API calls + +**Architecture**: +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Tauri App │───▶│ HTTP Client │───▶│ Google AI API │ +│ (Standalone) │ │ (tauri-plugin- │ │ (via HTTP) │ +│ │ │ http) │ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +**Implementation Strategy**: + +#### Option A: TypeScript Frontend HTTP (Recommended) +```typescript +// src/lib/images-mobile.ts +import { tauriApi } from '../gui/tauri-app/src/lib/tauriApi'; + +export class MobileImageGenerator { + private apiKey: string; + private baseUrl = 'https://generativelanguage.googleapis.com/v1beta'; + + constructor(apiKey: string) { + this.apiKey = apiKey; + } + + async createImage(prompt: string): Promise { + const response = await tauriApi.fetch(`${this.baseUrl}/models/gemini-2.5-flash-image-preview:generateContent`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + }, + body: JSON.stringify({ + contents: [{ + parts: [{ text: prompt }] + }] + }) + }); + + const data = await response.json(); + const imageData = data.candidates[0].content.parts[0].inlineData.data; + return Buffer.from(imageData, 'base64'); + } + + async editImage(prompt: string, imageFiles: File[]): Promise { + const parts = []; + + // Add image parts + for (const file of imageFiles) { + const arrayBuffer = await file.arrayBuffer(); + const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); + parts.push({ + inlineData: { + mimeType: file.type, + data: base64 + } + }); + } + + // Add text prompt + parts.push({ text: prompt }); + + const response = await tauriApi.fetch(`${this.baseUrl}/models/gemini-2.5-flash-image-preview:generateContent`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + }, + body: JSON.stringify({ + contents: [{ parts }] + }) + }); + + const data = await response.json(); + const imageData = data.candidates[0].content.parts[0].inlineData.data; + return Buffer.from(imageData, 'base64'); + } +} +``` + +#### Mobile-Specific Tauri Configuration +```json +// gui/tauri-app/src-tauri/tauri.conf.json (mobile additions) +{ + "plugins": { + "http": { + "all": true, + "request": true, + "scope": [ + "https://generativelanguage.googleapis.com/**" + ] + } + }, + "security": { + "csp": { + "default-src": "'self'", + "connect-src": "'self' https://generativelanguage.googleapis.com" + } + } +} +``` + +### 3. Web App - Browser-Based with Configurable Endpoints + +**Challenge**: CORS restrictions, no direct Google AI API access +**Solution**: Backend API server (Hono) + configurable endpoints + +**Architecture**: +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Web App │───▶│ Backend API │───▶│ Google AI API │ +│ (React/TS) │ │ (Hono.js) │ │ (Server) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +#### Backend API Server (Hono.js) +```typescript +// src/web/image-api-server.ts +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { GoogleGenerativeAI } from '@google/generative-ai'; + +const app = new Hono(); + +app.use('/*', cors({ + origin: ['http://localhost:3000', 'https://your-domain.com'], + allowHeaders: ['Content-Type', 'Authorization'], + allowMethods: ['POST', 'GET', 'OPTIONS'], +})); + +interface ImageRequest { + prompt: string; + images?: Array<{ + data: string; // base64 + mimeType: string; + }>; + apiKey: string; + model?: string; +} + +app.post('/api/images/create', async (c) => { + try { + const { prompt, apiKey, model = 'gemini-2.5-flash-image-preview' }: ImageRequest = await c.req.json(); + + const genAI = new GoogleGenerativeAI(apiKey); + const genModel = genAI.getGenerativeModel({ model }); + + const result = await genModel.generateContent(prompt); + const response = result.response; + + if (!response.candidates?.[0]?.content?.parts) { + throw new Error('No image generated'); + } + + const imageData = response.candidates[0].content.parts.find(part => + 'inlineData' in part + )?.inlineData; + + if (!imageData) { + throw new Error('No image data in response'); + } + + return c.json({ + success: true, + image: { + data: imageData.data, + mimeType: imageData.mimeType + } + }); + + } catch (error) { + return c.json({ + success: false, + error: error.message + }, 500); + } +}); + +app.post('/api/images/edit', async (c) => { + try { + const { prompt, images, apiKey, model = 'gemini-2.5-flash-image-preview' }: ImageRequest = await c.req.json(); + + const genAI = new GoogleGenerativeAI(apiKey); + const genModel = genAI.getGenerativeModel({ model }); + + const parts = []; + + // Add image parts + if (images) { + for (const img of images) { + parts.push({ + inlineData: { + mimeType: img.mimeType, + data: img.data + } + }); + } + } + + // Add text prompt + parts.push({ text: prompt }); + + const result = await genModel.generateContent(parts); + const response = result.response; + + if (!response.candidates?.[0]?.content?.parts) { + throw new Error('No image generated'); + } + + const imageData = response.candidates[0].content.parts.find(part => + 'inlineData' in part + )?.inlineData; + + if (!imageData) { + throw new Error('No image data in response'); + } + + return c.json({ + success: true, + image: { + data: imageData.data, + mimeType: imageData.mimeType + } + }); + + } catch (error) { + return c.json({ + success: false, + error: error.message + }, 500); + } +}); + +export default app; + +// Server startup +if (import.meta.main) { + const port = parseInt(process.env.PORT || '3001'); + console.log(`🚀 Image API server starting on port ${port}`); + + Bun.serve({ + fetch: app.fetch, + port, + }); +} +``` + +#### Web Frontend Client +```typescript +// src/web/image-client.ts +export interface WebImageConfig { + apiEndpoint: string; // e.g., 'http://localhost:3001' or 'https://api.yourservice.com' + apiKey: string; +} + +export class WebImageGenerator { + private config: WebImageConfig; + + constructor(config: WebImageConfig) { + this.config = config; + } + + async createImage(prompt: string): Promise { + const response = await fetch(`${this.config.apiEndpoint}/api/images/create`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + prompt, + apiKey: this.config.apiKey + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.error || 'Unknown error'); + } + + // Convert base64 to blob + const binaryString = atob(data.image.data); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return new Blob([bytes], { type: data.image.mimeType }); + } + + async editImage(prompt: string, imageFiles: File[]): Promise { + const images = []; + + for (const file of imageFiles) { + const arrayBuffer = await file.arrayBuffer(); + const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); + images.push({ + data: base64, + mimeType: file.type + }); + } + + const response = await fetch(`${this.config.apiEndpoint}/api/images/edit`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + prompt, + images, + apiKey: this.config.apiKey + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.error || 'Unknown error'); + } + + // Convert base64 to blob + const binaryString = atob(data.image.data); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return new Blob([bytes], { type: data.image.mimeType }); + } +} +``` + +#### Web App Configuration +```typescript +// src/web/config.ts +export interface PlatformConfig { + platform: 'cli' | 'mobile' | 'web'; + + // Web-specific config + web?: { + apiEndpoint: string; + corsEnabled: boolean; + allowedOrigins: string[]; + }; + + // Mobile-specific config + mobile?: { + directApiAccess: boolean; + cacheImages: boolean; + maxImageSize: number; + }; + + // CLI-specific config (existing) + cli?: { + guiEnabled: boolean; + tempDir: string; + }; +} + +export const getDefaultConfig = (): PlatformConfig => { + // Detect platform + const isTauri = !!(window as any).__TAURI__; + const isMobile = isTauri && /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); + const isWeb = !isTauri; + + if (isMobile) { + return { + platform: 'mobile', + mobile: { + directApiAccess: true, + cacheImages: true, + maxImageSize: 5 * 1024 * 1024 // 5MB + } + }; + } else if (isWeb) { + return { + platform: 'web', + web: { + apiEndpoint: process.env.REACT_APP_API_ENDPOINT || 'http://localhost:3001', + corsEnabled: true, + allowedOrigins: ['http://localhost:3000'] + } + }; + } else { + return { + platform: 'cli', + cli: { + guiEnabled: true, + tempDir: process.env.TEMP || '/tmp' + } + }; + } +}; +``` + +## Platform Detection & Unified Interface + +```typescript +// src/lib/image-generator-factory.ts +import { WebImageGenerator } from '../web/image-client'; +import { MobileImageGenerator } from './images-mobile'; +import { createImage, editImage } from './images-google'; // CLI version +import { getDefaultConfig, PlatformConfig } from '../web/config'; + +export interface UnifiedImageGenerator { + createImage(prompt: string): Promise; + editImage(prompt: string, images: File[] | string[]): Promise; +} + +export class ImageGeneratorFactory { + static create(config?: PlatformConfig): UnifiedImageGenerator { + const platformConfig = config || getDefaultConfig(); + + switch (platformConfig.platform) { + case 'web': + return new WebImageGeneratorAdapter( + new WebImageGenerator({ + apiEndpoint: platformConfig.web!.apiEndpoint, + apiKey: '' // Will be set later + }) + ); + + case 'mobile': + return new MobileImageGeneratorAdapter( + new MobileImageGenerator('') // API key set later + ); + + case 'cli': + default: + return new CLIImageGeneratorAdapter(); + } + } +} + +// Adapters to normalize the interface +class WebImageGeneratorAdapter implements UnifiedImageGenerator { + constructor(private generator: WebImageGenerator) {} + + async createImage(prompt: string): Promise { + return this.generator.createImage(prompt); + } + + async editImage(prompt: string, images: File[]): Promise { + return this.generator.editImage(prompt, images); + } +} + +class MobileImageGeneratorAdapter implements UnifiedImageGenerator { + constructor(private generator: MobileImageGenerator) {} + + async createImage(prompt: string): Promise { + return this.generator.createImage(prompt); + } + + async editImage(prompt: string, images: File[]): Promise { + return this.generator.editImage(prompt, images); + } +} + +class CLIImageGeneratorAdapter implements UnifiedImageGenerator { + async createImage(prompt: string): Promise { + // Use existing CLI implementation + return createImage(prompt, {} as any) as Promise; + } + + async editImage(prompt: string, images: string[]): Promise { + // Use existing CLI implementation + return editImage(prompt, images, {} as any) as Promise; + } +} +``` + +## Required Dependencies + +### CLI (Existing) +```json +{ + "dependencies": { + "@google/generative-ai": "^0.21.0", + "tauri": "^2.0.0" + } +} +``` + +### Mobile (Tauri) +```json +{ + "dependencies": { + "@tauri-apps/plugin-http": "^2.0.0", + "@tauri-apps/api": "^2.0.0" + } +} +``` + +### Web Backend (Hono) +```json +{ + "dependencies": { + "hono": "^4.0.0", + "@google/generative-ai": "^0.21.0", + "bun": "^1.0.0" + } +} +``` + +### Web Frontend +```json +{ + "dependencies": { + "react": "^18.0.0", + "@types/react": "^18.0.0" + } +} +``` + +## Deployment Strategies + +### CLI Desktop +- **Current**: Nexe bundling with Tauri executable +- **Distribution**: GitHub releases with platform-specific binaries + +### Mobile +- **Android**: APK via Tauri build system +- **iOS**: App Store via Tauri + Xcode +- **Distribution**: App stores or direct APK/IPA + +### Web App +- **Frontend**: Static hosting (Vercel, Netlify, Cloudflare Pages) +- **Backend**: + - **Option 1**: Bun/Node.js server (Railway, Render, DigitalOcean) + - **Option 2**: Serverless functions (Vercel Functions, Cloudflare Workers) + - **Option 3**: Docker containers (any cloud provider) + +## Migration Path + +### Phase 1: Maintain CLI (Current) +- Keep existing CLI implementation +- No changes to current workflow + +### Phase 2: Add Mobile Support +- Implement `MobileImageGenerator` class +- Add HTTP client configuration +- Test on Android/iOS simulators + +### Phase 3: Add Web Support +- Create Hono backend API +- Implement web frontend client +- Add configuration management + +### Phase 4: Unified Interface +- Implement factory pattern +- Add platform detection +- Create unified API surface + +## Security Considerations + +### API Key Management +- **CLI**: Local config files, environment variables +- **Mobile**: Secure storage via Tauri +- **Web**: Backend-only, never expose to frontend + +### CORS & CSP +- **Web**: Strict CORS policies, CSP headers +- **Mobile**: Tauri security policies +- **CLI**: Not applicable (local execution) + +### Rate Limiting +- **All Platforms**: Implement client-side rate limiting +- **Web**: Server-side rate limiting per IP/user + +## Testing Strategy + +### Unit Tests +```typescript +// tests/image-generator.test.ts +import { ImageGeneratorFactory } from '../src/lib/image-generator-factory'; + +describe('ImageGenerator', () => { + test('CLI platform creates correct generator', () => { + const generator = ImageGeneratorFactory.create({ platform: 'cli' }); + expect(generator).toBeInstanceOf(CLIImageGeneratorAdapter); + }); + + test('Web platform creates correct generator', () => { + const generator = ImageGeneratorFactory.create({ + platform: 'web', + web: { apiEndpoint: 'http://test.com', corsEnabled: true, allowedOrigins: [] } + }); + expect(generator).toBeInstanceOf(WebImageGeneratorAdapter); + }); +}); +``` + +### Integration Tests +- **CLI**: Test Tauri process spawning +- **Mobile**: Test HTTP API calls with mock server +- **Web**: Test full frontend-backend flow + +## Performance Considerations + +### Image Handling +- **CLI**: Direct file system access (fastest) +- **Mobile**: In-memory processing, consider caching +- **Web**: Base64 encoding overhead, consider streaming + +### Network Optimization +- **Mobile**: Implement request queuing, retry logic +- **Web**: Connection pooling, request batching + +### Memory Management +- **All Platforms**: Stream large images, avoid loading entire files into memory +- **Mobile**: Implement image compression before API calls + +--- + +## Implementation Todo List + +### Phase 1: Mobile Platform Support (Priority: High) + +#### 1.1 Mobile HTTP Client Implementation +- [ ] **Create mobile image generator class** (`src/lib/images-mobile.ts`) + - [ ] Implement `MobileImageGenerator` class with HTTP client + - [ ] Add TypeScript fetch wrapper using `tauriApi.fetch` + - [ ] Handle Google AI API authentication and requests + - [ ] Add error handling for network failures and API errors + - [ ] Implement image creation endpoint integration + - [ ] Implement image editing endpoint integration + +#### 1.2 Mobile Tauri Configuration +- [ ] **Update Tauri config for mobile HTTP access** + - [ ] Add `tauri-plugin-http` to dependencies + - [ ] Configure HTTP scope for Google AI API endpoints + - [ ] Update CSP policies for external API access + - [ ] Test HTTP plugin functionality on mobile simulators + +#### 1.3 Mobile Platform Detection +- [ ] **Add mobile platform detection logic** + - [ ] Detect Android/iOS in Tauri environment + - [ ] Create mobile-specific configuration defaults + - [ ] Add mobile UI adaptations (touch-friendly controls) + - [ ] Implement mobile-specific file handling + +### Phase 2: Web Platform Support (Priority: Medium) + +#### 2.1 Backend API Server (Hono) +- [ ] **Create Hono.js backend server** (`src/web/image-api-server.ts`) + - [ ] Set up Hono app with CORS middleware + - [ ] Implement `/api/images/create` endpoint + - [ ] Implement `/api/images/edit` endpoint + - [ ] Add request validation and error handling + - [ ] Add rate limiting middleware + - [ ] Add API key validation + - [ ] Add logging and monitoring + +#### 2.2 Web Frontend Client +- [ ] **Create web image client** (`src/web/image-client.ts`) + - [ ] Implement `WebImageGenerator` class + - [ ] Add fetch-based API communication + - [ ] Handle file uploads and base64 conversion + - [ ] Add progress tracking for large requests + - [ ] Implement retry logic for failed requests + +#### 2.3 Web Configuration Management +- [ ] **Add web-specific configuration** (`src/web/config.ts`) + - [ ] Create configurable API endpoints + - [ ] Add environment variable support + - [ ] Implement CORS configuration + - [ ] Add deployment-specific settings + +### Phase 3: Unified Interface (Priority: Medium) + +#### 3.1 Factory Pattern Implementation +- [ ] **Create image generator factory** (`src/lib/image-generator-factory.ts`) + - [ ] Implement platform detection logic + - [ ] Create unified interface for all platforms + - [ ] Add adapter classes for each platform + - [ ] Implement configuration-based generator selection + +#### 3.2 Platform Adapters +- [ ] **Create platform adapters** + - [ ] `CLIImageGeneratorAdapter` - wrap existing CLI implementation + - [ ] `MobileImageGeneratorAdapter` - wrap mobile HTTP client + - [ ] `WebImageGeneratorAdapter` - wrap web API client + - [ ] Normalize return types (Buffer vs Blob handling) + +### Phase 4: Testing & Quality Assurance (Priority: High) + +#### 4.1 Unit Tests +- [ ] **Write comprehensive unit tests** + - [ ] Test factory pattern and platform detection + - [ ] Test each adapter class individually + - [ ] Mock HTTP requests for mobile/web testing + - [ ] Test error handling scenarios + - [ ] Test configuration loading and validation + +#### 4.2 Integration Tests +- [ ] **Create integration test suite** + - [ ] Test CLI-to-Tauri communication (existing) + - [ ] Test mobile HTTP API calls with mock server + - [ ] Test web frontend-backend communication + - [ ] Test cross-platform image format compatibility + - [ ] Test API key management across platforms + +#### 4.3 Platform-Specific Testing +- [ ] **Mobile testing** + - [ ] Test on Android emulator/device + - [ ] Test on iOS simulator/device + - [ ] Test network connectivity edge cases + - [ ] Test file system permissions + - [ ] Performance testing with large images + +- [ ] **Web testing** + - [ ] Test CORS configuration + - [ ] Test different browsers (Chrome, Firefox, Safari) + - [ ] Test file upload limits + - [ ] Test API server deployment + - [ ] Load testing for concurrent requests + +### Phase 5: Deployment & Distribution (Priority: Low) + +#### 5.1 Mobile Deployment +- [ ] **Set up mobile build pipeline** + - [ ] Configure Android build (APK/AAB) + - [ ] Configure iOS build (IPA) + - [ ] Set up code signing for both platforms + - [ ] Create app store metadata and screenshots + - [ ] Test installation and updates + +#### 5.2 Web Deployment +- [ ] **Deploy web application** + - [ ] Set up frontend hosting (Vercel/Netlify) + - [ ] Deploy backend API server + - [ ] Configure domain and SSL certificates + - [ ] Set up monitoring and logging + - [ ] Configure CDN for static assets + +#### 5.3 Documentation & Guides +- [ ] **Create user documentation** + - [ ] Platform-specific installation guides + - [ ] API configuration instructions + - [ ] Troubleshooting guides + - [ ] Performance optimization tips + - [ ] Security best practices + +### Phase 6: Advanced Features (Priority: Low) + +#### 6.1 Performance Optimizations +- [ ] **Implement performance improvements** + - [ ] Image compression before API calls + - [ ] Request batching for multiple images + - [ ] Caching layer for repeated requests + - [ ] Progressive image loading + - [ ] Background processing for large operations + +#### 6.2 Enhanced Security +- [ ] **Add security enhancements** + - [ ] API key encryption at rest + - [ ] Request signing for web API + - [ ] Rate limiting per user/session + - [ ] Input sanitization and validation + - [ ] Audit logging for API calls + +#### 6.3 User Experience Improvements +- [ ] **Enhance user interface** + - [ ] Drag-and-drop file uploads + - [ ] Real-time preview of edits + - [ ] Batch processing interface + - [ ] History and favorites management + - [ ] Keyboard shortcuts and accessibility + +--- + +## Estimated Timeline + +- **Phase 1 (Mobile)**: 2-3 weeks +- **Phase 2 (Web)**: 2-3 weeks +- **Phase 3 (Unified)**: 1 week +- **Phase 4 (Testing)**: 2 weeks +- **Phase 5 (Deployment)**: 1 week +- **Phase 6 (Advanced)**: 3-4 weeks + +**Total Estimated Time**: 11-16 weeks + +## Dependencies & Prerequisites + +### Required Skills +- TypeScript/JavaScript development +- Tauri framework knowledge +- React/frontend development +- Hono.js/backend API development +- Mobile app development (Android/iOS) +- Google AI API integration + +### Required Tools +- Node.js 18+ +- Rust toolchain +- Android Studio (for Android builds) +- Xcode (for iOS builds) +- Bun runtime (for Hono server) + +### External Services +- Google AI API access and billing +- Cloud hosting for web backend +- App store developer accounts (mobile) +- Domain registration (web) diff --git a/packages/kbot/gui/tauri-app/build-android.sh b/packages/kbot/gui/tauri-app/build-android.sh index 2662d3e5..89c7b57a 100644 Binary files a/packages/kbot/gui/tauri-app/build-android.sh and b/packages/kbot/gui/tauri-app/build-android.sh differ diff --git a/packages/kbot/gui/tauri-app/isolation-dist/index.html b/packages/kbot/gui/tauri-app/isolation-dist/index.html new file mode 100644 index 00000000..bf59e8ea --- /dev/null +++ b/packages/kbot/gui/tauri-app/isolation-dist/index.html @@ -0,0 +1,10 @@ + + + + + Isolation Secure Script + + + + + diff --git a/packages/kbot/gui/tauri-app/isolation-dist/index.js b/packages/kbot/gui/tauri-app/isolation-dist/index.js new file mode 100644 index 00000000..076b3669 --- /dev/null +++ b/packages/kbot/gui/tauri-app/isolation-dist/index.js @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +window.__TAURI_ISOLATION_HOOK__ = (payload) => { + return payload +} diff --git a/packages/kbot/gui/tauri-app/src-tauri/Cargo.lock b/packages/kbot/gui/tauri-app/src-tauri/Cargo.lock index 8a78a7cc..d52705e9 100644 --- a/packages/kbot/gui/tauri-app/src-tauri/Cargo.lock +++ b/packages/kbot/gui/tauri-app/src-tauri/Cargo.lock @@ -17,6 +17,41 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.8" @@ -749,6 +784,16 @@ dependencies = [ "windows-link 0.2.0", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.48" @@ -998,6 +1043,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1038,6 +1084,15 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.21.3" @@ -1853,6 +1908,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gif" version = "0.13.3" @@ -2449,6 +2514,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "interpolate_name" version = "0.2.4" @@ -3371,6 +3445,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "open" version = "5.3.2" @@ -3773,6 +3853,18 @@ dependencies = [ "windows-sys 0.61.0", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "potential_utf" version = "0.1.3" @@ -5298,6 +5390,7 @@ dependencies = [ "tray-icon", "url", "urlpattern", + "uuid", "webkit2gtk", "webview2-com", "window-vibrancy", @@ -5355,6 +5448,7 @@ dependencies = [ "semver", "serde", "serde_json", + "tauri-codegen", "tauri-utils", "tauri-winres", "toml 0.9.7", @@ -5744,11 +5838,13 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212" dependencies = [ + "aes-gcm", "anyhow", "brotli", "cargo_metadata", "ctor", "dunce", + "getrandom 0.3.3", "glob", "html5ever", "http", @@ -5767,6 +5863,7 @@ dependencies = [ "serde-untagged", "serde_json", "serde_with", + "serialize-to-javascript", "swift-rs", "thiserror 2.0.16", "toml 0.9.7", @@ -6295,6 +6392,16 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" diff --git a/packages/kbot/gui/tauri-app/src-tauri/Cargo.toml b/packages/kbot/gui/tauri-app/src-tauri/Cargo.toml index 8c1aedde..870cb27a 100644 --- a/packages/kbot/gui/tauri-app/src-tauri/Cargo.toml +++ b/packages/kbot/gui/tauri-app/src-tauri/Cargo.toml @@ -15,10 +15,10 @@ name = "tauri_app_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2", features = [] } +tauri-build = { version = "2", features = ["isolation"] } [dependencies] -tauri = { version = "2", features = ["protocol-asset", "devtools"] } +tauri = { version = "2", features = ["isolation", "macos-private-api", "protocol-asset", "devtools"] } tauri-plugin-opener = "2.5.0" tauri-plugin-dialog = "2.4.0" tauri-plugin-fs = "2.0.0" diff --git a/packages/kbot/gui/tauri-app/src-tauri/gen/android/app/src/main/res/values/colors.xml b/packages/kbot/gui/tauri-app/src-tauri/gen/android/app/src/main/res/values/colors.xml index 823492c9..c4baf251 100644 --- a/packages/kbot/gui/tauri-app/src-tauri/gen/android/app/src/main/res/values/colors.xml +++ b/packages/kbot/gui/tauri-app/src-tauri/gen/android/app/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #FF018786 #FF000000 #FFFFFFFF + #FF2196F3 \ No newline at end of file diff --git a/packages/kbot/gui/tauri-app/src-tauri/tauri.conf.json b/packages/kbot/gui/tauri-app/src-tauri/tauri.conf.json index ec50b891..e008a869 100644 --- a/packages/kbot/gui/tauri-app/src-tauri/tauri.conf.json +++ b/packages/kbot/gui/tauri-app/src-tauri/tauri.conf.json @@ -10,6 +10,8 @@ "frontendDist": "../dist" }, "app": { + "withGlobalTauri": true, + "macOSPrivateApi": true, "windows": [ { "title": "tauri-app", @@ -18,8 +20,15 @@ } ], "security": { + "pattern": { + "use": "isolation", + "options": { + "dir": "../isolation-dist/" + } + }, "csp": { "default-src": "'self' customprotocol: asset:", + "script-src": "'self' 'unsafe-inline'", "connect-src": "ipc: http://ipc.localhost", "font-src": ["https://fonts.gstatic.com"], "img-src": "'self' asset: http://asset.localhost blob: data:", @@ -44,6 +53,9 @@ "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico" - ] + ], + "iOS": { + "minimumSystemVersion": "14.0" + } } } diff --git a/packages/kbot/gui/tauri-app/src/App.tsx b/packages/kbot/gui/tauri-app/src/App.tsx index 1b82c891..e9a400b6 100644 --- a/packages/kbot/gui/tauri-app/src/App.tsx +++ b/packages/kbot/gui/tauri-app/src/App.tsx @@ -41,32 +41,34 @@ function App() { }; return ( - - - - } - /> - - } - /> - - +
+ + + + } + /> + + } + /> + + +
); } diff --git a/packages/kbot/gui/tauri-app/src/components/Header.tsx b/packages/kbot/gui/tauri-app/src/components/Header.tsx index 74be72f1..306d1f63 100644 --- a/packages/kbot/gui/tauri-app/src/components/Header.tsx +++ b/packages/kbot/gui/tauri-app/src/components/Header.tsx @@ -1,81 +1,81 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; - -interface HeaderProps { - showDebugPanel: boolean; - setShowDebugPanel: (show: boolean) => void; - isDarkMode: boolean; - toggleTheme: () => void; -} - -const Header: React.FC = ({ - showDebugPanel, - setShowDebugPanel, - isDarkMode, - toggleTheme, -}) => { - const navigate = useNavigate(); - return ( -
- {/* Title on its own row */} -
-

Image Wizard

-
- - {/* Controls row - single row layout */} -
- {/* Button group - single row */} -
- {/* Debug Panel Toggle */} - - - {/* Theme Toggle */} - -
- - {/* Settings Button */} - -
-
- ); -}; - -export default Header; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +interface HeaderProps { + showDebugPanel: boolean; + setShowDebugPanel: (show: boolean) => void; + isDarkMode: boolean; + toggleTheme: () => void; +} + +const Header: React.FC = ({ + showDebugPanel, + setShowDebugPanel, + isDarkMode, + toggleTheme, +}) => { + const navigate = useNavigate(); + return ( +
+ {/* Title on its own row */} +
+

Image Wizard

+
+ + {/* Controls row - single row layout */} +
+ {/* Button group - single row */} +
+ {/* Debug Panel Toggle */} + + + {/* Theme Toggle */} + +
+ + {/* Settings Button */} + +
+
+ ); +}; + +export default Header; diff --git a/packages/kbot/gui/tauri-app/src/components/ImageWizard.tsx b/packages/kbot/gui/tauri-app/src/components/ImageWizard.tsx index a2e33f4c..7cd09fa6 100644 --- a/packages/kbot/gui/tauri-app/src/components/ImageWizard.tsx +++ b/packages/kbot/gui/tauri-app/src/components/ImageWizard.tsx @@ -718,14 +718,14 @@ const ImageWizard: React.FC = ({ } return ( -
+
{/* Background decoration */}
-
+
= ({
)} - + = ({ errorMessage={errorMessage} setErrorMessage={setErrorMessage} /> + {/* Debug Panel */} {showDebugPanel && ( diff --git a/packages/kbot/package.json b/packages/kbot/package.json index 025f72a6..fa3cc386 100644 --- a/packages/kbot/package.json +++ b/packages/kbot/package.json @@ -27,6 +27,7 @@ "gui:build": "cd ./gui/tauri-app/ && npm run build", "gui:dist": "cd ./gui/tauri-app/ && sh ./scripts/build.sh", "gui:dist:android": "cd ./gui/tauri-app/ && sh ./scripts/build-android.sh", + "gui:android:dev": "cd ./gui/tauri-app/ && npm run tauri android dev", "register-commands": "pm-cli register-commands --config=salamand.json --group=kbot", "test": "vitest run", "test:basic": "vitest run tests/unit/basic.test.ts", diff --git a/packages/kbot/src/top.txt b/packages/kbot/src/top.txt new file mode 100644 index 00000000..d43f07f6 --- /dev/null +++ b/packages/kbot/src/top.txt @@ -0,0 +1,20 @@ +Hey everyone, + +It came to our attention that once again, open source folks are being tricked with false statements and promises. This is not the first time, and over time most of the claims have been debunked as scams, also by ex-PreciousPlastic team members who left shocked. + +We're currently compiling a dossier which covers most of the ongoing fraud, targeting young and vulnerable people, apparently enticed to collect grants for often overpriced and immature machines. We know dozens who lost their savings and will have to pay a bitter price for a very long time. + +Currently involved and actively profiting from this scam are MadPlastic, Sustainable Design Studio, Dave Hakkens & his gang, and unfortunately The Flipflopi. Let me remind you that the v4 team introduced not just taxes and restrictions for everyone else but also kicked long-established vendors from the Bazaar with bogus reasons. During the same time, African NGOs have been charged systematically horrific prices. + +The complete report will also be sent to all FabLabs, news outlets, and related organizations. In the meantime, we're doing our best to warn newcomers; hundreds by now. + + + + + + + + + + + diff --git a/packages/kbot/status.jpg b/packages/kbot/status.jpg new file mode 100644 index 00000000..72e5130b Binary files /dev/null and b/packages/kbot/status.jpg differ