import React from "react"; import { useTranslation } from "react-i18next"; import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop"; import { MODAL_MAX_WIDTH_VIEWPORT, modalWidthClassName, } from "#/components/shared/modals/modal-body"; import { ModalCloseButton } from "#/components/shared/modals/modal-close-button"; import { BrandButton } from "#/components/features/settings/brand-button"; import { I18nKey } from "#/i18n/declaration"; import { PluginSpec } from "#/api/conversation-service/agent-server-conversation-service.types"; import { Typography } from "#/ui/typography"; import { cn } from "#/utils/utils"; import { PluginLaunchPluginSection } from "./plugin-launch-plugin-section"; interface PluginLaunchModalProps { plugins: PluginSpec[]; message?: string; isLoading?: boolean; onStartConversation: (plugins: PluginSpec[], initialMessage?: string) => void; onClose: () => void; } interface ExpandedState { [key: number]: boolean; } export function PluginLaunchModal({ plugins, message, isLoading = false, onStartConversation, onClose, }: PluginLaunchModalProps) { const { t } = useTranslation("openhands"); const [pluginConfigs, setPluginConfigs] = React.useState(plugins); const [expandedSections, setExpandedSections] = React.useState( () => { // Initially expand plugins that have parameters const initial: ExpandedState = {}; plugins.forEach((plugin, index) => { if (plugin.parameters && Object.keys(plugin.parameters).length > 0) { initial[index] = true; } }); return initial; }, ); const [trustConfirmed, setTrustConfirmed] = React.useState(false); const pluginsWithParams = pluginConfigs.filter( (p) => p.parameters && Object.keys(p.parameters).length > 0, ); const pluginsWithoutParams = pluginConfigs.filter( (p) => !p.parameters || Object.keys(p.parameters).length === 0, ); const toggleSection = (index: number) => { setExpandedSections((prev) => ({ ...prev, [index]: !prev[index], })); }; const updateParameter = ( pluginIndex: number, paramKey: string, value: unknown, ) => { setPluginConfigs((prev) => { const updated = [...prev]; const plugin = { ...updated[pluginIndex] }; plugin.parameters = { ...plugin.parameters, [paramKey]: value, }; updated[pluginIndex] = plugin; return updated; }); }; const getPluginDisplayName = (plugin: PluginSpec): string => { const { source, repo_path: repoPath } = plugin; // If repo_path is specified, show the plugin name from the path if (repoPath) { const pathParts = repoPath.split("/"); const pluginName = pathParts[pathParts.length - 1]; return pluginName; } // Otherwise show the repo name if (source.startsWith("github:")) { return source.replace("github:", ""); } if (source.includes("/")) { const parts = source.split("/"); return parts[parts.length - 1].replace(".git", ""); } return source; }; const getPluginSourceInfo = (plugin: PluginSpec): string => { const { source } = plugin; if (source.startsWith("github:")) { return source.replace("github:", ""); } if (source.includes("github.com/")) { return source.split("github.com/")[1]?.replace(".git", "") || source; } return source; }; const getUniqueSources = (): string[] => { const sources = pluginConfigs.map((plugin) => getPluginSourceInfo(plugin)); return [...new Set(sources)]; }; const handleStartConversation = () => { onStartConversation(pluginConfigs, message); }; const modalTitle = pluginConfigs.length === 1 ? getPluginDisplayName(pluginConfigs[0]) : t(I18nKey.LAUNCH$MODAL_TITLE_GENERIC); return (
{t(I18nKey.LAUNCH$MODAL_TITLE)} {modalTitle} {message &&

{message}

}
{pluginsWithParams.length > 0 && (
{pluginConfigs.map((plugin, index) => ( toggleSection(index)} getPluginDisplayName={getPluginDisplayName} onParameterChange={updateParameter} /> ))}
)} {pluginsWithoutParams.length > 0 && (
0 && "mt-4")}> {pluginsWithParams.length > 0 ? t(I18nKey.LAUNCH$ADDITIONAL_PLUGINS) : t(I18nKey.LAUNCH$PLUGINS)}
{pluginsWithoutParams.map((plugin, index) => (
{getPluginDisplayName(plugin)}
{getPluginSourceInfo(plugin)} {plugin.repo_path && ( / {plugin.repo_path} )} {plugin.ref && ( @ {plugin.ref} )}
))}
)}
setTrustConfirmed(e.target.checked)} className="mt-1 h-4 w-4 flex-shrink-0" />
{isLoading ? t(I18nKey.LAUNCH$STARTING) : t(I18nKey.LAUNCH$START_CONVERSATION)}
); }