Generated by Spark: I am having issues with this. Remember, we should be defaulting to IndexedDB, not the Flask API. Flask API is obtained by explicitly setting it in the UI or setting a env variable. export default function applyFetchPatch() {

if (ssrSafeWindow) {
    ssrSafeWindow.fetch = async (input: string | URL | Request, init?: RequestInit) => {
      try {
        const response = await globalFetch(input, init)
        sendFetchStats({input, error: !response.ok, status: response.status})
        return response
      } catch (error) {
        sendFetchStats({input, error: true, status: 'unknown'})
        throw error
      }
    }
  }
}
This commit is contained in:
2026-01-17 18:50:06 +00:00
committed by GitHub
parent 40b823dfe0
commit 7b91fe1975
4 changed files with 287 additions and 44 deletions

View File

@@ -1,8 +1,10 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useStorageBackend } from '@/hooks/use-unified-storage'
import { Database, HardDrive, Cloud, Download, Upload, CircleNotch } from '@phosphor-icons/react'
import { Database, HardDrive, Cloud, Download, Upload, CircleNotch, CloudArrowUp } from '@phosphor-icons/react'
import { toast } from 'sonner'
import { useState } from 'react'
@@ -10,6 +12,7 @@ export function StorageSettingsPanel() {
const {
backend,
isLoading,
switchToFlask,
switchToSQLite,
switchToIndexedDB,
exportData,
@@ -19,6 +22,29 @@ export function StorageSettingsPanel() {
const [isSwitching, setIsSwitching] = useState(false)
const [isExporting, setIsExporting] = useState(false)
const [isImporting, setIsImporting] = useState(false)
const [flaskUrl, setFlaskUrl] = useState(localStorage.getItem('codeforge-flask-url') || 'http://localhost:5001')
const handleSwitchToFlask = async () => {
if (backend === 'flask') {
toast.info('Already using Flask backend')
return
}
if (!flaskUrl) {
toast.error('Please enter a Flask backend URL')
return
}
setIsSwitching(true)
try {
await switchToFlask(flaskUrl)
toast.success('Switched to Flask backend')
} catch (error) {
toast.error(`Failed to switch to Flask: ${error instanceof Error ? error.message : 'Unknown error'}`)
} finally {
setIsSwitching(false)
}
}
const handleSwitchToSQLite = async () => {
if (backend === 'sqlite') {
@@ -46,7 +72,7 @@ export function StorageSettingsPanel() {
setIsSwitching(true)
try {
await switchToIndexedDB()
toast.success('Switched to IndexedDB storage')
toast.success('Switched to IndexedDB storage (default)')
} catch (error) {
toast.error(`Failed to switch to IndexedDB: ${error instanceof Error ? error.message : 'Unknown error'}`)
} finally {
@@ -102,6 +128,8 @@ export function StorageSettingsPanel() {
const getBackendIcon = () => {
switch (backend) {
case 'flask':
return <CloudArrowUp className="w-5 h-5" />
case 'sqlite':
return <HardDrive className="w-5 h-5" />
case 'indexeddb':
@@ -115,10 +143,12 @@ export function StorageSettingsPanel() {
const getBackendLabel = () => {
switch (backend) {
case 'flask':
return 'Flask Backend (Remote)'
case 'sqlite':
return 'SQLite (On-disk)'
case 'indexeddb':
return 'IndexedDB (Browser)'
return 'IndexedDB (Browser) - Default'
case 'sparkkv':
return 'Spark KV (Cloud)'
default:
@@ -128,10 +158,12 @@ export function StorageSettingsPanel() {
const getBackendDescription = () => {
switch (backend) {
case 'flask':
return 'Data stored on Flask server with SQLite (cross-device sync)'
case 'sqlite':
return 'Data stored in SQLite database persisted to localStorage'
case 'indexeddb':
return 'Data stored in browser IndexedDB (recommended for most users)'
return 'Data stored in browser IndexedDB (default, recommended for most users)'
case 'sparkkv':
return 'Data stored in Spark cloud key-value store'
default:
@@ -184,7 +216,51 @@ export function StorageSettingsPanel() {
<div className="space-y-3">
<h3 className="text-sm font-medium">Switch Storage Backend</h3>
<div className="space-y-3">
<div>
<Label htmlFor="flask-url" className="text-sm">Flask Backend URL (Optional)</Label>
<Input
id="flask-url"
type="text"
value={flaskUrl}
onChange={(e) => setFlaskUrl(e.target.value)}
placeholder="http://localhost:5001"
className="mt-1"
/>
<p className="text-xs text-muted-foreground mt-1">
Enter Flask backend URL or set VITE_FLASK_BACKEND_URL environment variable
</p>
</div>
</div>
<div className="flex flex-wrap gap-2">
<Button
onClick={handleSwitchToIndexedDB}
disabled={backend === 'indexeddb' || isSwitching}
variant={backend === 'indexeddb' ? 'default' : 'outline'}
size="sm"
>
{isSwitching ? (
<CircleNotch className="w-4 h-4 mr-2 animate-spin" />
) : (
<Database className="w-4 h-4 mr-2" />
)}
IndexedDB (Default)
</Button>
<Button
onClick={handleSwitchToFlask}
disabled={backend === 'flask' || isSwitching}
variant={backend === 'flask' ? 'default' : 'outline'}
size="sm"
>
{isSwitching ? (
<CircleNotch className="w-4 h-4 mr-2 animate-spin" />
) : (
<CloudArrowUp className="w-4 h-4 mr-2" />
)}
Flask Backend
</Button>
<Button
onClick={handleSwitchToSQLite}
disabled={backend === 'sqlite' || isSwitching}
@@ -198,22 +274,9 @@ export function StorageSettingsPanel() {
)}
SQLite
</Button>
<Button
onClick={handleSwitchToIndexedDB}
disabled={backend === 'indexeddb' || isSwitching}
variant={backend === 'indexeddb' ? 'default' : 'outline'}
size="sm"
>
{isSwitching ? (
<CircleNotch className="w-4 h-4 mr-2 animate-spin" />
) : (
<Database className="w-4 h-4 mr-2" />
)}
IndexedDB
</Button>
</div>
<p className="text-xs text-muted-foreground">
Switching storage backends will migrate all existing data
IndexedDB is the default and works offline. Flask backend enables cross-device sync.
</p>
</div>

View File

@@ -13,7 +13,7 @@ class FlaskBackendAdapter implements StorageAdapter {
private baseUrl: string
constructor(baseUrl?: string) {
this.baseUrl = baseUrl || localStorage.getItem('codeforge-flask-url') || 'http://localhost:5001'
this.baseUrl = baseUrl || localStorage.getItem('codeforge-flask-url') || import.meta.env.VITE_FLASK_BACKEND_URL || 'http://localhost:5001'
}
private async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
@@ -340,30 +340,31 @@ class UnifiedStorage {
this.initPromise = (async () => {
const preferFlask = localStorage.getItem('codeforge-prefer-flask') === 'true'
const flaskEnvUrl = import.meta.env.VITE_FLASK_BACKEND_URL
const preferSQLite = localStorage.getItem('codeforge-prefer-sqlite') === 'true'
if (preferFlask) {
if (preferFlask || flaskEnvUrl) {
try {
console.log('[Storage] Attempting to initialize Flask backend...')
const flaskAdapter = new FlaskBackendAdapter()
console.log('[Storage] Flask backend explicitly configured, attempting to initialize...')
const flaskAdapter = new FlaskBackendAdapter(flaskEnvUrl)
await flaskAdapter.get('_health_check')
this.adapter = flaskAdapter
this.backend = 'flask'
console.log('[Storage] ✓ Using Flask backend')
return
} catch (error) {
console.warn('[Storage] Flask backend not available:', error)
console.warn('[Storage] Flask backend not available, falling back to IndexedDB:', error)
}
}
if (typeof indexedDB !== 'undefined') {
try {
console.log('[Storage] Attempting to initialize IndexedDB...')
console.log('[Storage] Initializing default IndexedDB backend...')
const idbAdapter = new IndexedDBAdapter()
await idbAdapter.get('_health_check')
this.adapter = idbAdapter
this.backend = 'indexeddb'
console.log('[Storage] ✓ Using IndexedDB')
console.log('[Storage] ✓ Using IndexedDB (default)')
return
} catch (error) {
console.warn('[Storage] IndexedDB not available:', error)
@@ -372,7 +373,7 @@ class UnifiedStorage {
if (preferSQLite) {
try {
console.log('[Storage] Attempting to initialize SQLite...')
console.log('[Storage] SQLite fallback, attempting to initialize...')
const sqliteAdapter = new SQLiteAdapter()
await sqliteAdapter.get('_health_check')
this.adapter = sqliteAdapter
@@ -386,7 +387,7 @@ class UnifiedStorage {
if (window.spark?.kv) {
try {
console.log('[Storage] Attempting to initialize Spark KV...')
console.log('[Storage] Spark KV fallback, attempting to initialize...')
const sparkAdapter = new SparkKVAdapter()
await sparkAdapter.get('_health_check')
this.adapter = sparkAdapter