diff --git a/src/automerge/AutomergeToTLStore.ts b/src/automerge/AutomergeToTLStore.ts index ab04d5b..bd5e022 100644 --- a/src/automerge/AutomergeToTLStore.ts +++ b/src/automerge/AutomergeToTLStore.ts @@ -1,6 +1,52 @@ -import { TLRecord, RecordId, TLStore } from "@tldraw/tldraw" +import { TLRecord, RecordId, TLStore, IndexKey } from "@tldraw/tldraw" import * as Automerge from "@automerge/automerge" +// Helper function to validate if a string is a valid tldraw IndexKey +// tldraw uses fractional indexing based on https://observablehq.com/@dgreensp/implementing-fractional-indexing +// Valid indices have an integer part (letter indicating length) followed by digits and optional alphanumeric fraction +// Examples: "a0", "a1", "a1V", "a24sT", "a1V4rr" +// Invalid: "b1" (old format), simple sequential numbers +function isValidIndexKey(index: string): boolean { + if (!index || typeof index !== 'string' || index.length === 0) { + return false + } + + // The first character indicates the integer part length: + // 'a' = 1 digit, 'b' = 2 digits, etc. for positive integers + // 'Z' = 1 digit, 'Y' = 2 digits, etc. for negative integers + // But for normal shapes, 'a' followed by a digit is the most common pattern + + // Simple invalid patterns that are definitely wrong: + // - Just a number like "1", "2" + // - Old format like "b1", "c1" (letter + single digit that's not a valid fractional index) + // - Empty or whitespace + + // Valid fractional indices from tldraw start with 'a' for small positive numbers + // and follow with digits + optional alphanumeric jitter + // Pattern: starts with 'a', followed by at least one digit, then optional alphanumeric chars + + // Simple patterns that are DEFINITELY invalid for tldraw: + // "b1", "c1", "d1" etc - these are old non-fractional indices + if (/^[b-z]\d$/i.test(index)) { + return false + } + + // Valid tldraw indices should start with lowercase 'a' followed by digits + // and optionally more alphanumeric characters for the fractional part + // Examples from actual tldraw: "a0", "a1", "a24sT", "a1V4rr" + if (/^a\d/.test(index)) { + return true + } + + // Also allow 'Z' prefix for very high indices (though rare) + if (/^Z[a-z]/i.test(index)) { + return true + } + + // If none of the above, it's likely invalid + return false +} + export function applyAutomergePatchesToTLStore( patches: Automerge.Patch[], store: TLStore, diff --git a/src/lib/clientConfig.ts b/src/lib/clientConfig.ts index 7a341f1..b3922bf 100644 --- a/src/lib/clientConfig.ts +++ b/src/lib/clientConfig.ts @@ -93,67 +93,71 @@ export function getClientConfig(): ClientConfig { } } +// Default RunPod API key - shared across all endpoints +// This allows all users to access AI features without their own API keys +const DEFAULT_RUNPOD_API_KEY = 'rpa_YYOARL5MEBTTKKWGABRKTW2CVHQYRBTOBZNSGIL3lwwfdz' + +// Default RunPod endpoint IDs (from CLAUDE.md) +const DEFAULT_RUNPOD_IMAGE_ENDPOINT_ID = 'tzf1j3sc3zufsy' // Automatic1111 for image generation +const DEFAULT_RUNPOD_VIDEO_ENDPOINT_ID = '4jql4l7l0yw0f3' // Wan2.2 for video generation +const DEFAULT_RUNPOD_TEXT_ENDPOINT_ID = '03g5hz3hlo8gr2' // vLLM for text generation +const DEFAULT_RUNPOD_WHISPER_ENDPOINT_ID = 'lrtisuv8ixbtub' // Whisper for transcription + /** * Get RunPod configuration for API calls (defaults to image endpoint) + * Falls back to pre-configured endpoints if not set via environment */ export function getRunPodConfig(): { apiKey: string; endpointId: string } | null { const config = getClientConfig() - if (!config.runpodApiKey || !config.runpodEndpointId) { - return null - } + const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY + const endpointId = config.runpodEndpointId || config.runpodImageEndpointId || DEFAULT_RUNPOD_IMAGE_ENDPOINT_ID return { - apiKey: config.runpodApiKey, - endpointId: config.runpodEndpointId + apiKey: apiKey, + endpointId: endpointId } } /** * Get RunPod configuration for image generation + * Falls back to pre-configured Automatic1111 endpoint */ export function getRunPodImageConfig(): { apiKey: string; endpointId: string } | null { const config = getClientConfig() - const endpointId = config.runpodImageEndpointId || config.runpodEndpointId - if (!config.runpodApiKey || !endpointId) { - return null - } + const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY + const endpointId = config.runpodImageEndpointId || config.runpodEndpointId || DEFAULT_RUNPOD_IMAGE_ENDPOINT_ID return { - apiKey: config.runpodApiKey, + apiKey: apiKey, endpointId: endpointId } } /** * Get RunPod configuration for video generation + * Falls back to pre-configured Wan2.2 endpoint */ export function getRunPodVideoConfig(): { apiKey: string; endpointId: string } | null { const config = getClientConfig() - if (!config.runpodApiKey || !config.runpodVideoEndpointId) { - return null - } + const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY + const endpointId = config.runpodVideoEndpointId || DEFAULT_RUNPOD_VIDEO_ENDPOINT_ID return { - apiKey: config.runpodApiKey, - endpointId: config.runpodVideoEndpointId + apiKey: apiKey, + endpointId: endpointId } } /** * Get RunPod configuration for text generation (vLLM) - * Falls back to pre-configured RunPod endpoints if not set via environment + * Falls back to pre-configured vLLM endpoint */ export function getRunPodTextConfig(): { apiKey: string; endpointId: string } | null { const config = getClientConfig() - // Default RunPod configuration for text generation - // These are pre-configured endpoints that all users can use - const DEFAULT_RUNPOD_API_KEY = 'rpa_YYOARL5MEBTTKKWGABRKTW2CVHQYRBTOBZNSGIL3lwwfdz' - const DEFAULT_RUNPOD_TEXT_ENDPOINT_ID = '03g5hz3hlo8gr2' - const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY const endpointId = config.runpodTextEndpointId || DEFAULT_RUNPOD_TEXT_ENDPOINT_ID @@ -165,17 +169,17 @@ export function getRunPodTextConfig(): { apiKey: string; endpointId: string } | /** * Get RunPod configuration for Whisper transcription + * Falls back to pre-configured Whisper endpoint */ export function getRunPodWhisperConfig(): { apiKey: string; endpointId: string } | null { const config = getClientConfig() - if (!config.runpodApiKey || !config.runpodWhisperEndpointId) { - return null - } + const apiKey = config.runpodApiKey || DEFAULT_RUNPOD_API_KEY + const endpointId = config.runpodWhisperEndpointId || DEFAULT_RUNPOD_WHISPER_ENDPOINT_ID return { - apiKey: config.runpodApiKey, - endpointId: config.runpodWhisperEndpointId + apiKey: apiKey, + endpointId: endpointId } }