+ {/* Plugin Header */}
+
+
+ {plugin.name}
+
+
+ {plugin.description}
+
+
+
+ {/* Tabs */}
+
+
+ {hasSettings && (
+
+ )}
+ {hasEnvVars && (
+
+ )}
+
+
+ {/* Tab Content */}
+
+ {activeTab === 'orchestration' && (
+
+ )}
+
+ {activeTab === 'settings' && hasSettings && (
+
+
+
+
+ Plugin Settings
+
+
+
+
+ {settingsKeys.map((key) => {
+ const fieldSchema = plugin.config_schema.settings[key]
+ const value = config.settings[key]
+ const error = errors[`settings.${key}`]
+
+ return (
+ handleSettingsChange(key, newValue)}
+ error={error}
+ disabled={disabled}
+ />
+ )
+ })}
+
+
+ )}
+
+ {activeTab === 'secrets' && hasEnvVars && (
+
+ )}
+
+
+ {/* Test Result Display */}
+ {testResult && (
+
+
+ {testResult.success ? (
+
+ ) : (
+
+ )}
+
+
+ {testResult.message}
+
+ {testResult.details && (
+
+ {JSON.stringify(testResult.details, null, 2)}
+
+ )}
+
+
+
+ )}
+
+ {/* Action Buttons */}
+
+
+ {plugin.supports_testing && onTestConnection && (
+
+ )}
+
+
+
+
+
+
+
+ )
+}
diff --git a/backends/advanced/webui/src/components/plugins/PluginListSidebar.tsx b/backends/advanced/webui/src/components/plugins/PluginListSidebar.tsx
new file mode 100644
index 00000000..bf842620
--- /dev/null
+++ b/backends/advanced/webui/src/components/plugins/PluginListSidebar.tsx
@@ -0,0 +1,162 @@
+import { CheckCircle, Circle, AlertTriangle } from 'lucide-react'
+
+interface Plugin {
+ plugin_id: string
+ name: string
+ description: string
+ enabled: boolean
+ status: 'active' | 'disabled' | 'error'
+}
+
+interface PluginListSidebarProps {
+ plugins: Plugin[]
+ selectedPluginId: string | null
+ onSelectPlugin: (pluginId: string) => void
+ onToggleEnabled: (pluginId: string, enabled: boolean) => void
+ loading?: boolean
+}
+
+export default function PluginListSidebar({
+ plugins,
+ selectedPluginId,
+ onSelectPlugin,
+ onToggleEnabled,
+ loading = false
+}: PluginListSidebarProps) {
+ const getStatusIcon = (status: string, enabled: boolean) => {
+ if (!enabled) {
+ return
+ {plugins.map((plugin) => {
+ const isSelected = selectedPluginId === plugin.plugin_id
+
+ return (
+
onSelectPlugin(plugin.plugin_id)}
+ className={`
+ p-4 rounded-lg border cursor-pointer transition-all
+ ${
+ isSelected
+ ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
+ : 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 bg-white dark:bg-gray-800'
+ }
+ `}
+ >
+ {/* Plugin Header */}
+
+
+ {getStatusIcon(plugin.status, plugin.enabled)}
+
+
+ {plugin.name}
+
+
+
+
+
+ {/* Plugin Description */}
+
+ {plugin.description}
+
+
+ {/* Plugin Status and Toggle */}
+
+ {getStatusBadge(plugin.status, plugin.enabled)}
+
+
+
+
+ )
+ })}
+
+ )
+}
diff --git a/backends/advanced/webui/src/hooks/useSimpleAudioRecording.ts b/backends/advanced/webui/src/hooks/useSimpleAudioRecording.ts
index 91f394c9..d34c8ea6 100644
--- a/backends/advanced/webui/src/hooks/useSimpleAudioRecording.ts
+++ b/backends/advanced/webui/src/hooks/useSimpleAudioRecording.ts
@@ -228,6 +228,38 @@ export const useSimpleAudioRecording = (): SimpleAudioRecordingReturn => {
ws.onmessage = (event) => {
console.log('📨 Received message from server:', event.data)
setDebugStats(prev => ({ ...prev, messagesReceived: prev.messagesReceived + 1 }))
+
+ // Parse server messages
+ try {
+ const message = JSON.parse(event.data)
+
+ // Handle error messages from backend
+ if (message.type === 'error') {
+ const errorMsg = message.message || 'Unknown error from server'
+ console.error('❌ Server error:', errorMsg)
+
+ setError(errorMsg)
+ setCurrentStep('error')
+ setDebugStats(prev => ({
+ ...prev,
+ lastError: errorMsg,
+ lastErrorTime: new Date()
+ }))
+
+ // Stop recording and cleanup
+ cleanup()
+ setIsRecording(false)
+ }
+
+ // Handle other message types (interim_transcript, etc.)
+ else if (message.type === 'interim_transcript') {
+ console.log('📝 Received interim transcript:', message.data)
+ }
+
+ } catch (e) {
+ // Not JSON, ignore
+ console.log('📨 Non-JSON message:', event.data)
+ }
}
})
}, [])
diff --git a/backends/advanced/webui/src/pages/Conversations.tsx b/backends/advanced/webui/src/pages/Conversations.tsx
index ad1de51c..ef57e738 100644
--- a/backends/advanced/webui/src/pages/Conversations.tsx
+++ b/backends/advanced/webui/src/pages/Conversations.tsx
@@ -31,6 +31,8 @@ interface Conversation {
active_memory_version?: string
transcript_version_count?: number
memory_version_count?: number
+ active_transcript_version_number?: number
+ active_memory_version_number?: number
deleted?: boolean
deletion_reason?: string
deleted_at?: string
@@ -719,7 +721,9 @@ export default function Conversations() {
transcript_count: conversation.transcript_version_count || 0,
memory_count: conversation.memory_version_count || 0,
active_transcript_version: conversation.active_transcript_version,
- active_memory_version: conversation.active_memory_version
+ active_memory_version: conversation.active_memory_version,
+ active_transcript_version_number: conversation.active_transcript_version_number,
+ active_memory_version_number: conversation.active_memory_version_number
}}
onVersionChange={async () => {
// Update only this specific conversation without reloading all conversations
diff --git a/backends/advanced/webui/src/pages/Plugins.tsx b/backends/advanced/webui/src/pages/Plugins.tsx
index f28921f5..adb85930 100644
--- a/backends/advanced/webui/src/pages/Plugins.tsx
+++ b/backends/advanced/webui/src/pages/Plugins.tsx
@@ -1,9 +1,39 @@
+import { useState } from 'react'
+import { Code, Layout } from 'lucide-react'
import PluginSettings from '../components/PluginSettings'
+import PluginSettingsForm from '../components/PluginSettingsForm'
export default function Plugins() {
+ const [useFormUI, setUseFormUI] = useState(true)
+
return (
-
-
+
+ {/* Toggle Button */}
+
+
+
+
+ {/* Content */}
+ {useFormUI ? (
+
+ ) : (
+
+ )}
)
}
diff --git a/backends/advanced/webui/src/pages/System.tsx b/backends/advanced/webui/src/pages/System.tsx
index a628d70d..dfe662f7 100644
--- a/backends/advanced/webui/src/pages/System.tsx
+++ b/backends/advanced/webui/src/pages/System.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
-import { Settings, RefreshCw, CheckCircle, XCircle, AlertCircle, Activity, Users, Database, Server, Volume2, Mic, Brain } from 'lucide-react'
+import { Settings, RefreshCw, CheckCircle, XCircle, AlertCircle, Activity, Users, Database, Server, Volume2, Mic, Brain, Sliders } from 'lucide-react'
import { systemApi, speakerApi } from '../services/api'
import { useAuth } from '../contexts/AuthContext'
import MemorySettings from '../components/MemorySettings'
@@ -92,6 +92,14 @@ export default function System() {
const [providerLoading, setProviderLoading] = useState(false)
const [providerMessage, setProviderMessage] = useState('')
+ // Miscellaneous settings state
+ const [miscSettings, setMiscSettings] = useState({
+ always_persist_enabled: false,
+ use_provider_segments: false
+ })
+ const [miscLoading, setMiscLoading] = useState(false)
+ const [miscMessage, setMiscMessage] = useState('')
+
const { isAdmin } = useAuth()
const loadSystemData = async () => {
@@ -167,6 +175,38 @@ export default function System() {
}
}
+ const loadMiscSettings = async () => {
+ try {
+ setMiscLoading(true)
+ const response = await systemApi.getMiscSettings()
+ if (response.data.status === 'success') {
+ setMiscSettings(response.data.settings)
+ }
+ } catch (err: any) {
+ console.error('Failed to load misc settings:', err)
+ } finally {
+ setMiscLoading(false)
+ }
+ }
+
+ const saveMiscSettings = async () => {
+ try {
+ setMiscLoading(true)
+ setMiscMessage('')
+ const response = await systemApi.saveMiscSettings(miscSettings)
+ if (response.data.status === 'success') {
+ setMiscMessage('Settings saved successfully')
+ setTimeout(() => setMiscMessage(''), 3000)
+ } else {
+ setMiscMessage('Failed to save settings')
+ }
+ } catch (err: any) {
+ setMiscMessage('Error: ' + (err.response?.data?.detail || err.message))
+ } finally {
+ setMiscLoading(false)
+ }
+ }
+
const saveMemoryProvider = async () => {
if (selectedProvider === currentProvider) {
setProviderMessage('Provider is already set to ' + selectedProvider)
@@ -211,6 +251,7 @@ export default function System() {
loadSystemData()
loadDiarizationSettings()
loadMemoryProvider()
+ loadMiscSettings()
}, [isAdmin])
const getStatusIcon = (healthy: boolean) => {
@@ -804,6 +845,86 @@ export default function System() {
+ {/* Miscellaneous Configuration */}
+