Refactor plugin management system

- Updated PluginLoader to utilize the new plugin marketplace manager for loading and managing plugins.
- Simplified the plugin loading process by removing unnecessary state management and directly integrating with the plugin marketplace.
- Enhanced error handling during plugin installation and uninstallation processes.
- Removed legacy code related to Electron-specific plugin handling, streamlining the codebase for web compatibility.
- Updated Plugins component to fetch marketplace plugins and installed plugins using the new plugin marketplace manager.
- Refactored types related to plugins to import from the new plugin manager module, ensuring consistency and reducing redundancy.
- Removed unused calling configuration from client settings and adjusted related types accordingly.
- Cleaned up room state events by removing references to LiveKit service URLs, aligning with the updated architecture.
This commit is contained in:
2026-04-19 06:52:42 +10:00
parent d1d3033c15
commit 52e9cc360f
18 changed files with 283 additions and 1375 deletions

View File

@@ -24,21 +24,13 @@ import {
import { HexColorPicker } from 'react-colorful';
import FocusTrap from 'focus-trap-react';
import { Page, PageContent, PageHeader } from '../../../components/page';
import { PluginTab, PluginMetadata, PluginIndex, InstalledPlugin } from './types';
import { PluginTab, PluginMetadata, InstalledPlugin } from './types';
import { SequenceCard } from '../../../components/sequence-card';
import { SettingTile } from '../../../components/setting-tile';
import { SequenceCardStyle } from '../styles.css';
import { pluginRegistry, SettingsSchema, SettingDefinition } from './PluginAPI';
import { pluginMarketplaceManager, pluginRegistry, SettingDefinition } from './PluginAPI';
import { stopPropagation } from '../../../utils/keyboard';
const PLUGIN_INDEX_URL =
'https://raw.githubusercontent.com/Paarrot/Plugin-Directory/refs/heads/main/plugins/index.json';
const PLUGIN_BASE_URL =
'https://raw.githubusercontent.com/Paarrot/Plugin-Directory/refs/heads/main/plugins/';
// Check if running in Electron
const isElectron = typeof window !== 'undefined' && (window as any).electron?.plugins;
/**
* Renders settings UI for a plugin based on its settings schema
*/
@@ -257,27 +249,21 @@ export function Plugins({ requestClose }: PluginsProps) {
const [searchQuery, setSearchQuery] = useState('');
const [expandedSettings, setExpandedSettings] = useState<string | null>(null);
const refreshInstalledPlugins = useCallback(async () => {
try {
setInstalledPlugins(await pluginMarketplaceManager.listInstalledPlugins());
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to list installed plugins');
console.error('Failed to list installed plugins:', err);
}
}, []);
const fetchMarketplacePlugins = useCallback(async () => {
setLoading(true);
setError(null);
try {
const indexResponse = await fetch(PLUGIN_INDEX_URL);
if (!indexResponse.ok) {
throw new Error('Failed to fetch plugin index');
}
const index: PluginIndex = await indexResponse.json();
const pluginPromises = index.plugins.map(async (pluginId) => {
const pluginResponse = await fetch(`${PLUGIN_BASE_URL}${pluginId}.json`);
if (!pluginResponse.ok) {
console.error(`Failed to fetch plugin ${pluginId}`);
return null;
}
return pluginResponse.json();
});
const plugins = await Promise.all(pluginPromises);
setMarketplacePlugins(plugins.filter((p): p is PluginMetadata => p !== null));
setMarketplacePlugins(await pluginMarketplaceManager.fetchMarketplacePlugins());
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch plugins');
console.error('Error fetching marketplace plugins:', err);
@@ -293,112 +279,38 @@ export function Plugins({ requestClose }: PluginsProps) {
}, [activeTab, fetchMarketplacePlugins]);
useEffect(() => {
const loadInstalledPlugins = async () => {
if (isElectron) {
// Load from filesystem via Electron
try {
const result = await (window as any).electron.plugins.list();
if (result.success && result.data) {
// Merge filesystem data with metadata from marketplace
const fsPlugins = result.data.map((fsPlugin: any) => ({
...fsPlugin,
enabled: true, // Default to enabled
}));
setInstalledPlugins(fsPlugins);
}
} catch (err) {
console.error('Failed to list plugins from filesystem:', err);
}
} else {
// Fallback to localStorage for web
const stored = localStorage.getItem('installedPlugins');
if (stored) {
try {
setInstalledPlugins(JSON.parse(stored));
} catch (err) {
console.error('Failed to parse installed plugins:', err);
}
}
}
};
loadInstalledPlugins();
}, []);
refreshInstalledPlugins();
}, [refreshInstalledPlugins]);
const handleInstall = useCallback(
async (plugin: PluginMetadata) => {
if (isElectron) {
// Download and install to filesystem via Electron
try {
setLoading(true);
const result = await (window as any).electron.plugins.download(
plugin.id,
plugin.downloadUrl,
plugin.name
);
if (result.success) {
const newPlugin: InstalledPlugin = {
...plugin,
installedDate: new Date().toISOString(),
enabled: true,
};
setInstalledPlugins([...installedPlugins, newPlugin]);
} else {
setError(result.error || 'Failed to install plugin');
}
} catch (err) {
console.error('Failed to install plugin:', err);
setError(err instanceof Error ? err.message : 'Failed to install plugin');
} finally {
setLoading(false);
}
} else {
// Fallback to localStorage for web
const newPlugin: InstalledPlugin = {
...plugin,
installedDate: new Date().toISOString(),
enabled: true,
};
const updated = [...installedPlugins, newPlugin];
setInstalledPlugins(updated);
localStorage.setItem('installedPlugins', JSON.stringify(updated));
try {
setLoading(true);
setError(null);
await pluginMarketplaceManager.installPlugin(plugin);
await refreshInstalledPlugins();
} catch (err) {
console.error('Failed to install plugin:', err);
setError(err instanceof Error ? err.message : 'Failed to install plugin');
} finally {
setLoading(false);
}
},
[installedPlugins]
[refreshInstalledPlugins]
);
const handleUninstall = useCallback(
async (pluginId: string) => {
// First, unregister the plugin to trigger onUnload
try {
await pluginRegistry.unregisterPlugin(pluginId);
setError(null);
await pluginMarketplaceManager.uninstallPlugin(pluginId);
await refreshInstalledPlugins();
} catch (err) {
console.error(`Failed to unregister plugin ${pluginId}:`, err);
}
if (isElectron) {
// Uninstall from filesystem via Electron
try {
const result = await (window as any).electron.plugins.uninstall(pluginId);
if (result.success) {
const updated = installedPlugins.filter((p) => p.id !== pluginId);
setInstalledPlugins(updated);
} else {
setError(result.error || 'Failed to uninstall plugin');
}
} catch (err) {
console.error('Failed to uninstall plugin:', err);
setError(err instanceof Error ? err.message : 'Failed to uninstall plugin');
}
} else {
// Fallback to localStorage for web
const updated = installedPlugins.filter((p) => p.id !== pluginId);
setInstalledPlugins(updated);
localStorage.setItem('installedPlugins', JSON.stringify(updated));
console.error('Failed to uninstall plugin:', err);
setError(err instanceof Error ? err.message : 'Failed to uninstall plugin');
}
},
[installedPlugins]
[refreshInstalledPlugins]
);
const handleToggleEnabled = useCallback(
@@ -408,36 +320,19 @@ export function Plugins({ requestClose }: PluginsProps) {
const newEnabledState = !plugin.enabled;
if (plugin.enabled) {
// Disabling: unregister the plugin to trigger onUnload
try {
await pluginRegistry.unregisterPlugin(pluginId);
console.log(`[Plugins] Plugin ${pluginId} disabled and unloaded`);
} catch (err) {
console.error(`Failed to unregister plugin ${pluginId}:`, err);
try {
setError(null);
await pluginMarketplaceManager.setPluginEnabled(pluginId, newEnabledState);
await refreshInstalledPlugins();
if (newEnabledState) {
console.log(`[Plugins] Plugin ${pluginId} enabled. Restart app to load it.`);
}
}
// Update installedPlugins list
const updated = installedPlugins.map((p) =>
p.id === pluginId ? { ...p, enabled: newEnabledState } : p
);
setInstalledPlugins(updated);
localStorage.setItem('installedPlugins', JSON.stringify(updated));
// Also update enabledPlugins for PluginLoader
const enabledPluginsStr = localStorage.getItem('enabledPlugins');
const enabledPlugins = enabledPluginsStr ? JSON.parse(enabledPluginsStr) : {};
enabledPlugins[pluginId] = newEnabledState;
localStorage.setItem('enabledPlugins', JSON.stringify(enabledPlugins));
// Note: Enabling a plugin requires app restart to load it
// PluginLoader only loads plugins on mount
if (newEnabledState) {
console.log(`[Plugins] Plugin ${pluginId} enabled. Restart app to load it.`);
} catch (err) {
console.error(`Failed to toggle plugin ${pluginId}:`, err);
setError(err instanceof Error ? err.message : 'Failed to update plugin state');
}
},
[installedPlugins]
[installedPlugins, refreshInstalledPlugins]
);
const isPluginInstalled = useCallback(