Files
agent-ui/frontend/src/stores/projectCanvas.ts
josedario87 e1aa8b1bdb feat: integrate Tauri v2 with Android widget and voice assistant
- Add Tauri v2 shell (Cargo, tauri.conf.json, capabilities, plugins)
- Migrate all fetch() calls to apiFetch() for Tauri-aware HTTP
- Migrate WebSocket endpoints to resolveEndpoints() for dynamic URLs
- Add ServerConfigDialog for remote server URL configuration
- Add tauri.ts lib with isTauri detection, apiFetch wrapper, plugin helpers
- Add server-config Pinia store with persistence via plugin-store
- Conditional PWA (disabled in Tauri builds)
- Android: home screen transcript widget (last 5 messages, 30s refresh)
- Android: voice command / share activity (SpeechRecognizer + WebSocket)
- Android: signed release APK with auto-copy to installers/
- Remove stale frontend/src-tauri directory
2026-02-23 15:33:43 -06:00

346 lines
10 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { apiFetch } from '@/lib/tauri'
import type { ProjectCanvas, CanvasComponent, ComponentUsage } from '../types/canvas'
// Uses relative URLs - works with Vite proxy in dev and Traefik in production
const API_URL = ''
export const useProjectCanvasStore = defineStore('projectCanvas', () => {
// State
const canvases = ref<ProjectCanvas[]>([])
const activeCanvas = ref<ProjectCanvas | null>(null)
const activeCanvasComponents = ref<CanvasComponent[]>([])
const loading = ref(false)
const saving = ref(false)
const error = ref<string | null>(null)
// State adicional
const toolbarCanvases = ref<ProjectCanvas[]>([])
const defaultCanvas = ref<ProjectCanvas | null>(null)
// Getters
const projectCanvases = computed(() =>
canvases.value.filter(c => c.type === 'project')
)
const systemCanvases = computed(() =>
canvases.value.filter(c => c.type === 'system')
)
const dynamicCanvas = computed(() =>
canvases.value.find(c => c.type === 'dynamic')
)
const activeCanvasesList = computed(() =>
canvases.value.filter(c => c.status !== 'archived')
)
const archivedCanvases = computed(() =>
canvases.value.filter(c => c.status === 'archived')
)
const canvasCount = computed(() => canvases.value.length)
const hasActiveCanvas = computed(() => activeCanvas.value !== null)
const hasDefaultCanvas = computed(() => defaultCanvas.value !== null)
// Actions
async function fetchCanvases(includeArchived = false) {
loading.value = true
error.value = null
try {
const url = includeArchived ? `${API_URL}/api/canvas?include_archived=true` : `${API_URL}/api/canvas`
const res = await apiFetch(url)
if (!res.ok) throw new Error('Failed to fetch canvases')
canvases.value = await res.json()
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error'
console.error('Error fetching canvases:', e)
} finally {
loading.value = false
}
}
async function fetchToolbarCanvases() {
try {
const res = await apiFetch(`${API_URL}/api/canvas/toolbar`)
if (!res.ok) throw new Error('Failed to fetch toolbar canvases')
toolbarCanvases.value = await res.json()
} catch (e) {
console.error('Error fetching toolbar canvases:', e)
toolbarCanvases.value = []
}
}
async function fetchDefaultCanvas(): Promise<ProjectCanvas | null> {
try {
const res = await apiFetch(`${API_URL}/api/canvas/default`)
if (!res.ok) throw new Error('Failed to fetch default canvas')
const data = await res.json()
if (data.hasDefault) {
defaultCanvas.value = data.canvas
return data.canvas
}
defaultCanvas.value = null
return null
} catch (e) {
console.error('Error fetching default canvas:', e)
defaultCanvas.value = null
return null
}
}
async function fetchCanvasById(id: string): Promise<ProjectCanvas | null> {
try {
const res = await apiFetch(`${API_URL}/api/canvas/${id}`)
if (!res.ok) {
if (res.status === 404) return null
throw new Error('Failed to fetch canvas')
}
return await res.json()
} catch (e) {
console.error('Error fetching canvas:', e)
return null
}
}
async function createCanvas(data: Partial<ProjectCanvas>): Promise<string | null> {
saving.value = true
error.value = null
try {
const res = await apiFetch(`${API_URL}/api/canvas`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
if (!res.ok) throw new Error('Failed to create canvas')
const result = await res.json()
await fetchCanvases()
return result.id
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error'
console.error('Error creating canvas:', e)
return null
} finally {
saving.value = false
}
}
async function updateCanvas(id: string, data: Partial<ProjectCanvas>): Promise<boolean> {
saving.value = true
error.value = null
try {
const res = await apiFetch(`${API_URL}/api/canvas/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
if (!res.ok) {
const errorData = await res.json()
throw new Error(errorData.error || 'Failed to update canvas')
}
await fetchCanvases()
// Update active canvas if it's the one being updated
if (activeCanvas.value?.id === id) {
activeCanvas.value = await fetchCanvasById(id)
}
return true
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error'
console.error('Error updating canvas:', e)
return false
} finally {
saving.value = false
}
}
async function deleteCanvas(id: string): Promise<boolean> {
error.value = null
try {
const res = await apiFetch(`${API_URL}/api/canvas/${id}`, {
method: 'DELETE'
})
if (!res.ok) {
const errorData = await res.json()
throw new Error(errorData.error || 'Failed to delete canvas')
}
await fetchCanvases()
if (activeCanvas.value?.id === id) {
clearActiveCanvas()
}
return true
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error'
console.error('Error deleting canvas:', e)
return false
}
}
async function restoreCanvas(id: string): Promise<boolean> {
return updateCanvas(id, { status: 'active' } as any)
}
async function cloneCanvas(id: string, newName?: string): Promise<string | null> {
saving.value = true
error.value = null
try {
const res = await apiFetch(`${API_URL}/api/canvas/${id}/clone`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: newName })
})
if (!res.ok) throw new Error('Failed to clone canvas')
const result = await res.json()
await fetchCanvases()
return result.id
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error'
console.error('Error cloning canvas:', e)
return null
} finally {
saving.value = false
}
}
// Canvas Components
async function fetchCanvasComponents(canvasId: string) {
loading.value = true
try {
const res = await apiFetch(`${API_URL}/api/canvas/${canvasId}/components`)
if (!res.ok) throw new Error('Failed to fetch canvas components')
activeCanvasComponents.value = await res.json()
} catch (e) {
console.error('Error fetching canvas components:', e)
activeCanvasComponents.value = []
} finally {
loading.value = false
}
}
async function addComponentToCanvas(
canvasId: string,
componentId: string,
props?: Record<string, any>,
position?: number
): Promise<boolean> {
try {
const res = await apiFetch(`${API_URL}/api/canvas/${canvasId}/components`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ component_id: componentId, props, position })
})
if (!res.ok) throw new Error('Failed to add component to canvas')
await fetchCanvasComponents(canvasId)
return true
} catch (e) {
console.error('Error adding component to canvas:', e)
return false
}
}
async function removeComponentFromCanvas(canvasId: string, componentId: string): Promise<boolean> {
try {
const res = await apiFetch(`${API_URL}/api/canvas/${canvasId}/components/${componentId}`, {
method: 'DELETE'
})
if (!res.ok) throw new Error('Failed to remove component from canvas')
await fetchCanvasComponents(canvasId)
return true
} catch (e) {
console.error('Error removing component from canvas:', e)
return false
}
}
async function updateCanvasComponent(
canvasId: string,
componentId: string,
data: { position?: number; props?: Record<string, any>; layout?: any; is_visible?: boolean }
): Promise<boolean> {
try {
const res = await apiFetch(`${API_URL}/api/canvas/${canvasId}/components/${componentId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
if (!res.ok) throw new Error('Failed to update canvas component')
await fetchCanvasComponents(canvasId)
return true
} catch (e) {
console.error('Error updating canvas component:', e)
return false
}
}
// Component Usage
async function getComponentUsage(componentId: string): Promise<ComponentUsage | null> {
try {
const res = await apiFetch(`${API_URL}/api/components/${componentId}/usage`)
if (!res.ok) throw new Error('Failed to get component usage')
return await res.json()
} catch (e) {
console.error('Error getting component usage:', e)
return null
}
}
// Canvas Activation
function setActiveCanvas(canvas: ProjectCanvas) {
activeCanvas.value = canvas
}
function clearActiveCanvas() {
activeCanvas.value = null
activeCanvasComponents.value = []
}
async function activateCanvas(id: string): Promise<boolean> {
const canvas = await fetchCanvasById(id)
if (!canvas) return false
setActiveCanvas(canvas)
await fetchCanvasComponents(id)
return true
}
return {
// State
canvases,
activeCanvas,
activeCanvasComponents,
toolbarCanvases,
defaultCanvas,
loading,
saving,
error,
// Getters
projectCanvases,
systemCanvases,
dynamicCanvas,
activeCanvasesList,
archivedCanvases,
canvasCount,
hasActiveCanvas,
hasDefaultCanvas,
// Actions
fetchCanvases,
fetchToolbarCanvases,
fetchDefaultCanvas,
fetchCanvasById,
createCanvas,
updateCanvas,
deleteCanvas,
restoreCanvas,
cloneCanvas,
fetchCanvasComponents,
addComponentToCanvas,
removeComponentFromCanvas,
updateCanvasComponent,
getComponentUsage,
setActiveCanvas,
clearActiveCanvas,
activateCanvas
}
})