Files
PiAgentUI/src/utils/settingsBackup.ts
T
Evandro b3489e1ede
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s
🎉 Initial commit: PiAgentUI — AI Agent Desktop & Web UI
A professional workspace for AI coding agents with:
- Real-time streaming chat with SSE
- Session management (fork, clone, tree navigation)
- MCP tools browser & metadata panel
- Skills browser (global, project, package sources)
- Voice input & STT provider configuration
- Integrated terminal & file explorer
- Model selector with capability metadata
- Keyboard-first UI with command palette
- 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi)
- Tauri 2 desktop app
- React 19 + TypeScript + Vite 8 + Tailwind CSS 4
- Pi Agent extension runtime

Originally forked from OpenCodeUI, now an independent project.
License: GPL-3.0-only
2026-06-01 00:50:06 -03:00

190 lines
5.8 KiB
TypeScript

import { STORAGE_KEY_NOTIFICATIONS_ENABLED } from '../constants/storage'
import {
exportLayoutBackup,
exportNotificationEventSettingsBackup,
exportNotificationPreferencesBackup,
exportServerSettingsBackup,
exportServiceSettingsBackup,
exportThemeBackup,
exportUpdateSettingsBackup,
importLayoutBackup,
importNotificationEventSettingsBackup,
importNotificationPreferencesBackup,
importServerSettingsBackup,
importServiceSettingsBackup,
importThemeBackup,
importUpdateSettingsBackup,
type LayoutBackup,
type NotificationEventSettingsBackup,
type NotificationPreferencesBackup,
type ServerSettingsBackup,
type ServiceSettingsBackup,
type ThemeBackup,
type UpdateSettingsBackup,
} from '../store'
import { exportKeybindingBackup, importKeybindingBackup, type KeybindingBackup } from '../store/keybindingStore'
import { exportSoundBackup, importSoundBackup, type SoundBackup } from '../store/soundStore'
import {
exportPerServerStorageBackup,
importPerServerStorageBackup,
type PerServerStorageBackup,
} from './perServerStorage'
const BACKUP_KIND = 'settings-backup'
const BACKUP_SCHEMA_VERSION = 2
export interface NotificationBackup {
browserNotificationsEnabled: boolean
toast: NotificationPreferencesBackup
events: NotificationEventSettingsBackup
}
export interface SettingsBackupModules {
theme: ThemeBackup
layout: LayoutBackup
servers: ServerSettingsBackup
perServerStorage: PerServerStorageBackup
service: ServiceSettingsBackup
keybindings: KeybindingBackup
notifications: NotificationBackup
sound: SoundBackup
update: UpdateSettingsBackup
}
export interface SettingsBackupFile {
app: 'OpenCodeUI'
kind: typeof BACKUP_KIND
schemaVersion: typeof BACKUP_SCHEMA_VERSION
createdAt: string
modules: SettingsBackupModules
}
function exportNotificationBackup(): NotificationBackup {
return {
browserNotificationsEnabled: localStorage.getItem(STORAGE_KEY_NOTIFICATIONS_ENABLED) === 'true',
toast: exportNotificationPreferencesBackup(),
events: exportNotificationEventSettingsBackup(),
}
}
function importNotificationBackup(raw: unknown): void {
const parsed = raw && typeof raw === 'object' ? (raw as Record<string, unknown>) : undefined
const browserNotificationsEnabled = parsed?.browserNotificationsEnabled === true
if (browserNotificationsEnabled) {
localStorage.setItem(STORAGE_KEY_NOTIFICATIONS_ENABLED, 'true')
} else {
localStorage.removeItem(STORAGE_KEY_NOTIFICATIONS_ENABLED)
}
importNotificationPreferencesBackup(parsed?.toast)
importNotificationEventSettingsBackup(parsed?.events)
}
function normalizeBackupFile(raw: unknown): SettingsBackupFile {
if (!raw || typeof raw !== 'object') {
throw new Error('Invalid backup file')
}
const parsed = raw as Record<string, unknown>
if (parsed.app !== 'OpenCodeUI' || parsed.kind !== BACKUP_KIND || parsed.schemaVersion !== BACKUP_SCHEMA_VERSION) {
throw new Error('Unsupported backup format')
}
if (!parsed.modules || typeof parsed.modules !== 'object') {
throw new Error('Missing backup modules')
}
const modules = parsed.modules as Record<string, unknown>
const requiredModules: Array<keyof SettingsBackupModules> = [
'theme',
'layout',
'servers',
'perServerStorage',
'service',
'keybindings',
'notifications',
'sound',
'update',
]
for (const id of requiredModules) {
if (!(id in modules)) {
throw new Error(`Missing backup module: ${id}`)
}
}
return {
app: 'OpenCodeUI',
kind: BACKUP_KIND,
schemaVersion: BACKUP_SCHEMA_VERSION,
createdAt: typeof parsed.createdAt === 'string' ? parsed.createdAt : new Date().toISOString(),
modules: modules as unknown as SettingsBackupModules,
}
}
function buildBackupFileName(createdAt: string): string {
const safeTimestamp = createdAt.replace(/[:]/g, '-').replace(/\.\d+Z$/, 'Z')
return `opencodeui-settings-backup-${safeTimestamp}.json`
}
export async function exportSettingsBackup(): Promise<{ fileName: string; data: Uint8Array }> {
const createdAt = new Date().toISOString()
const backup: SettingsBackupFile = {
app: 'OpenCodeUI',
kind: BACKUP_KIND,
schemaVersion: BACKUP_SCHEMA_VERSION,
createdAt,
modules: {
theme: exportThemeBackup(),
layout: exportLayoutBackup(),
servers: exportServerSettingsBackup(),
perServerStorage: exportPerServerStorageBackup(),
service: exportServiceSettingsBackup(),
keybindings: exportKeybindingBackup(),
notifications: exportNotificationBackup(),
sound: await exportSoundBackup(),
update: exportUpdateSettingsBackup(),
},
}
return {
fileName: buildBackupFileName(createdAt),
data: new TextEncoder().encode(`${JSON.stringify(backup, null, 2)}\n`),
}
}
export async function importSettingsBackup(file: File): Promise<void> {
const text = await file.text()
let parsed: unknown
try {
parsed = JSON.parse(text)
} catch {
throw new Error('Invalid backup file')
}
const backup = normalizeBackupFile(parsed)
importThemeBackup(backup.modules.theme)
importLayoutBackup(backup.modules.layout)
importServerSettingsBackup(backup.modules.servers)
importPerServerStorageBackup(backup.modules.perServerStorage)
importServiceSettingsBackup(backup.modules.service)
importKeybindingBackup(backup.modules.keybindings)
importNotificationBackup(backup.modules.notifications)
await importSoundBackup(backup.modules.sound)
importUpdateSettingsBackup(backup.modules.update)
}
export function previewBackupMeta(file: File): Promise<{ createdAt: string | null }> {
return file.text().then(text => {
try {
const parsed = JSON.parse(text) as Record<string, unknown>
return { createdAt: typeof parsed.createdAt === 'string' ? parsed.createdAt : null }
} catch {
return { createdAt: null }
}
})
}