fix: sanitize shape indices and improve RunPod error handling
- Add index sanitization in Board.tsx to fix "Expected an index key" validation errors when selecting shapes with old format indices - Improve RunPod error handling to properly display status messages (IN_PROGRESS, IN_QUEUE, FAILED) instead of generic errors - Update wrangler.toml with current compatibility date and document RunPod endpoint configuration for reference - Add sanitizeIndex helper function to convert invalid indices like "b1" to valid tldraw fractional indices like "a1" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e0f8107e1d
commit
5a22786195
|
|
@ -1,7 +1,7 @@
|
|||
import { useAutomergeSync } from "@/automerge/useAutomergeSync"
|
||||
import { AutomergeHandleProvider } from "@/context/AutomergeHandleContext"
|
||||
import { useMemo, useEffect, useState, useRef } from "react"
|
||||
import { Tldraw, Editor, TLShapeId, TLRecord, useTldrawUser, TLUserPreferences } from "tldraw"
|
||||
import { Tldraw, Editor, TLShapeId, TLRecord, useTldrawUser, TLUserPreferences, IndexKey } from "tldraw"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { ChatBoxTool } from "@/tools/ChatBoxTool"
|
||||
import { ChatBoxShape } from "@/shapes/ChatBoxShapeUtil"
|
||||
|
|
@ -66,6 +66,24 @@ import "react-cmdk/dist/cmdk.css"
|
|||
import "@/css/style.css"
|
||||
import "@/css/obsidian-browser.css"
|
||||
|
||||
// Helper to validate and fix tldraw IndexKey format
|
||||
// Valid: "a0", "a1", "a24sT", "a1V4rr" - Invalid: "b1", "c1" (old format)
|
||||
function sanitizeIndex(index: any): IndexKey {
|
||||
if (!index || typeof index !== 'string' || index.length === 0) {
|
||||
return 'a1' as IndexKey
|
||||
}
|
||||
// Old format "b1", "c1" etc are invalid (single letter + single digit)
|
||||
if (/^[b-z]\d$/i.test(index)) {
|
||||
return 'a1' as IndexKey
|
||||
}
|
||||
// Valid: starts with 'a' followed by at least one digit
|
||||
if (/^a\d/.test(index)) {
|
||||
return index as IndexKey
|
||||
}
|
||||
// Fallback
|
||||
return 'a1' as IndexKey
|
||||
}
|
||||
|
||||
const collections: Collection[] = [GraphLayoutCollection]
|
||||
import { useAuth } from "../context/AuthContext"
|
||||
import { updateLastVisited } from "../lib/starredBoards"
|
||||
|
|
@ -594,33 +612,37 @@ export function Board() {
|
|||
// Fallback if store not available
|
||||
const fallbackX = (s.x !== undefined && typeof s.x === 'number' && !isNaN(s.x)) ? s.x : 0
|
||||
const fallbackY = (s.y !== undefined && typeof s.y === 'number' && !isNaN(s.y)) ? s.y : 0
|
||||
return { ...s, parentId: currentPageId, x: fallbackX, y: fallbackY } as TLRecord
|
||||
// CRITICAL: Sanitize index to prevent validation errors
|
||||
return { ...s, parentId: currentPageId, x: fallbackX, y: fallbackY, index: sanitizeIndex(s.index) } as TLRecord
|
||||
}
|
||||
|
||||
|
||||
const shapeFromStore = store.store.get(s.id)
|
||||
if (shapeFromStore && shapeFromStore.typeName === 'shape') {
|
||||
// CRITICAL: Get coordinates from store's current state (most reliable)
|
||||
// This ensures we preserve coordinates even if the shape object has been modified
|
||||
const storeX = (shapeFromStore as any).x
|
||||
const storeY = (shapeFromStore as any).y
|
||||
const originalX = (typeof storeX === 'number' && !isNaN(storeX) && storeX !== null && storeX !== undefined)
|
||||
? storeX
|
||||
const originalX = (typeof storeX === 'number' && !isNaN(storeX) && storeX !== null && storeX !== undefined)
|
||||
? storeX
|
||||
: (s.x !== undefined && typeof s.x === 'number' && !isNaN(s.x) ? s.x : 0)
|
||||
const originalY = (typeof storeY === 'number' && !isNaN(storeY) && storeY !== null && storeY !== undefined)
|
||||
? storeY
|
||||
: (s.y !== undefined && typeof s.y === 'number' && !isNaN(s.y) ? s.y : 0)
|
||||
|
||||
// Create fixed shape with preserved coordinates
|
||||
|
||||
// Create fixed shape with preserved coordinates and sanitized index
|
||||
const fixed: any = { ...shapeFromStore, parentId: currentPageId }
|
||||
// CRITICAL: Always preserve coordinates - never reset to 0,0 unless truly missing
|
||||
fixed.x = originalX
|
||||
fixed.y = originalY
|
||||
// CRITICAL: Sanitize index to prevent "Expected an index key" validation errors
|
||||
fixed.index = sanitizeIndex(fixed.index)
|
||||
return fixed as TLRecord
|
||||
}
|
||||
// Fallback if shape not in store - preserve coordinates from s
|
||||
const fallbackX = (s.x !== undefined && typeof s.x === 'number' && !isNaN(s.x)) ? s.x : 0
|
||||
const fallbackY = (s.y !== undefined && typeof s.y === 'number' && !isNaN(s.y)) ? s.y : 0
|
||||
return { ...s, parentId: currentPageId, x: fallbackX, y: fallbackY } as TLRecord
|
||||
// CRITICAL: Sanitize index to prevent validation errors
|
||||
return { ...s, parentId: currentPageId, x: fallbackX, y: fallbackY, index: sanitizeIndex(s.index) } as TLRecord
|
||||
})
|
||||
try {
|
||||
// CRITICAL: Use mergeRemoteChanges to prevent feedback loop
|
||||
|
|
|
|||
|
|
@ -433,8 +433,22 @@ export class ImageGenShape extends BaseBoxShapeUtil<IImageGen> {
|
|||
}
|
||||
} else if (data.error) {
|
||||
throw new Error(`RunPod API error: ${data.error}`)
|
||||
} else if (data.status) {
|
||||
// Handle RunPod status responses (no output yet)
|
||||
const status = data.status.toUpperCase()
|
||||
if (status === 'IN_PROGRESS' || status === 'IN_QUEUE') {
|
||||
throw new Error(`Image generation timed out (status: ${data.status}). The GPU may be experiencing a cold start. Please try again in a moment.`)
|
||||
} else if (status === 'FAILED') {
|
||||
throw new Error(`RunPod job failed: ${data.error || 'Unknown error'}`)
|
||||
} else if (status === 'CANCELLED') {
|
||||
throw new Error('Image generation was cancelled')
|
||||
} else {
|
||||
throw new Error(`Unexpected RunPod status: ${data.status}`)
|
||||
}
|
||||
} else {
|
||||
throw new Error("No valid response from RunPod API - missing output field")
|
||||
// Log full response for debugging
|
||||
console.error("❌ ImageGen: Unexpected response structure:", JSON.stringify(data, null, 2))
|
||||
throw new Error("No valid response from RunPod API - missing output field. Check console for details.")
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
# Worker configuration
|
||||
# Note: This wrangler.toml is for the Worker backend only.
|
||||
# Pages deployment is configured separately in the Cloudflare dashboard.
|
||||
# Frontend (Vite) environment variables should be set in Cloudflare Pages dashboard.
|
||||
main = "worker/worker.ts"
|
||||
compatibility_date = "2024-07-01"
|
||||
compatibility_date = "2024-11-01"
|
||||
name = "jeffemmett-canvas"
|
||||
account_id = "0e7b3338d5278ed1b148e6456b940913"
|
||||
|
||||
|
|
@ -11,6 +12,15 @@ account_id = "0e7b3338d5278ed1b148e6456b940913"
|
|||
# Workers & Pages → jeffemmett-canvas → Settings → Variables
|
||||
DAILY_DOMAIN = "mycopunks.daily.co"
|
||||
|
||||
# RunPod AI Service Configuration (defaults hardcoded in src/lib/clientConfig.ts)
|
||||
# These are documented here for reference - actual values are in the client code
|
||||
# to allow all users to access AI features without configuration
|
||||
# RUNPOD_API_KEY = "set via wrangler secret put"
|
||||
# RUNPOD_IMAGE_ENDPOINT_ID = "tzf1j3sc3zufsy" # Automatic1111
|
||||
# RUNPOD_VIDEO_ENDPOINT_ID = "4jql4l7l0yw0f3" # Wan2.2
|
||||
# RUNPOD_TEXT_ENDPOINT_ID = "03g5hz3hlo8gr2" # vLLM
|
||||
# RUNPOD_WHISPER_ENDPOINT_ID = "lrtisuv8ixbtub" # Whisper
|
||||
|
||||
[dev]
|
||||
port = 5172
|
||||
ip = "0.0.0.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue