import React from "react"; import { useNavigate, useSearchParams } from "react-router"; import { useTranslation } from "react-i18next"; import { PluginLaunchModal } from "#/components/features/launch/plugin-launch-modal"; import { PluginSpec } from "#/api/conversation-service/agent-server-conversation-service.types"; import { useCreateConversation } from "#/hooks/mutation/use-create-conversation"; import { I18nKey } from "#/i18n/declaration"; type ErrorType = "no_plugins" | "invalid_format" | "creation_failed"; interface ParseResult { plugins: PluginSpec[]; message?: string; error?: ErrorType; } function sanitizeMessage(message: string | null): string | undefined { if (!message) return undefined; // Strip ALL HTML tags for plain text display const stripped = message.replace(/<[^>]*>/g, "").slice(0, 500); return stripped || undefined; } function parsePluginsFromUrl(searchParams: URLSearchParams): ParseResult { // Try base64 encoded plugins parameter first (production format) const pluginsParam = searchParams.get("plugins"); if (pluginsParam) { try { const decoded = atob(pluginsParam); const parsed = JSON.parse(decoded); if (!Array.isArray(parsed)) { return { plugins: [], error: "invalid_format" }; } // Validate each plugin has at least a source const validPlugins: PluginSpec[] = []; for (const item of parsed) { if ( typeof item !== "object" || !item || typeof item.source !== "string" ) { return { plugins: [], error: "invalid_format" }; } validPlugins.push({ source: item.source, ref: item.ref ?? null, repo_path: item.repo_path ?? null, parameters: item.parameters ?? null, }); } if (validPlugins.length === 0) { return { plugins: [], error: "no_plugins" }; } return { plugins: validPlugins, message: sanitizeMessage(searchParams.get("message")), }; } catch { return { plugins: [], error: "invalid_format" }; } } // Fallback: simple params format for dev/testing const pluginSource = searchParams.get("plugin_source"); if (pluginSource) { const plugin: PluginSpec = { source: pluginSource, ref: searchParams.get("plugin_ref") ?? null, repo_path: searchParams.get("plugin_repo_path") ?? null, parameters: null, }; return { plugins: [plugin], message: sanitizeMessage(searchParams.get("message")), }; } return { plugins: [], error: "no_plugins" }; } function ErrorDisplay({ errorType, onGoHome, onTryAgain, }: { errorType: ErrorType; onGoHome: () => void; onTryAgain?: () => void; }) { const { t } = useTranslation("openhands"); const errorMessages: Record = { no_plugins: t(I18nKey.LAUNCH$ERROR_NO_PLUGINS), invalid_format: t(I18nKey.LAUNCH$ERROR_INVALID_FORMAT), creation_failed: t(I18nKey.LAUNCH$ERROR_CREATION_FAILED), }; return (

{t(I18nKey.LAUNCH$ERROR_TITLE)}

{errorMessages[errorType]}

{onTryAgain && ( )}
); } export default function LaunchRoute() { const [searchParams] = useSearchParams(); const navigate = useNavigate(); const { t } = useTranslation("openhands"); const createConversation = useCreateConversation(); const [creationError, setCreationError] = React.useState(null); const parseResult = React.useMemo( () => parsePluginsFromUrl(searchParams), [searchParams], ); const handleStartConversation = async ( plugins: PluginSpec[], initialMessage?: string, ) => { setCreationError(null); try { const result = await createConversation.mutateAsync({ plugins, query: initialMessage, entryPoint: "launch_deeplink", }); navigate(`/conversations/${result.conversation_id}`); } catch { setCreationError(t(I18nKey.LAUNCH$ERROR_CREATION_UNKNOWN)); } }; const handleGoHome = () => { navigate("/conversations"); }; const handleTryAgain = () => { setCreationError(null); window.location.reload(); }; // Show error if parsing failed if (parseResult.error) { return ( ); } // Show error if conversation creation failed if (creationError) { return ( ); } return (
); }