Enable recipe deeplink parameters for pre-population (#5757)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -449,6 +449,10 @@ function BaseChatContent({
|
||||
parameters={recipe.parameters}
|
||||
onSubmit={setRecipeUserParams}
|
||||
onClose={() => setView('chat')}
|
||||
initialValues={
|
||||
(window.appConfig?.get('recipeParameters') as Record<string, string> | undefined) ||
|
||||
undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -6,29 +6,31 @@ interface ParameterInputModalProps {
|
||||
parameters: Parameter[];
|
||||
onSubmit: (values: Record<string, string>) => void;
|
||||
onClose: () => void;
|
||||
initialValues?: Record<string, string>;
|
||||
}
|
||||
|
||||
const ParameterInputModal: React.FC<ParameterInputModalProps> = ({
|
||||
parameters,
|
||||
onSubmit,
|
||||
onClose,
|
||||
initialValues,
|
||||
}) => {
|
||||
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
||||
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
|
||||
const [showCancelOptions, setShowCancelOptions] = useState(false);
|
||||
|
||||
// Pre-fill the form with default values from the recipe
|
||||
// Pre-fill the form with default values from the recipe and initialValues from deeplink
|
||||
useEffect(() => {
|
||||
const initialValues: Record<string, string> = {};
|
||||
const defaultValues: Record<string, string> = {};
|
||||
parameters.forEach((param) => {
|
||||
if (param.requirement === 'optional' && param.default) {
|
||||
const defaultValue =
|
||||
defaultValues[param.key] =
|
||||
param.input_type === 'boolean' ? param.default.toLowerCase() : param.default;
|
||||
initialValues[param.key] = defaultValue;
|
||||
}
|
||||
});
|
||||
setInputValues(initialValues);
|
||||
}, [parameters]);
|
||||
|
||||
setInputValues({ ...defaultValues, ...initialValues });
|
||||
}, [parameters, initialValues]);
|
||||
|
||||
const handleChange = (name: string, value: string): void => {
|
||||
setInputValues((prevValues: Record<string, string>) => ({ ...prevValues, [name]: value }));
|
||||
|
||||
@@ -22,6 +22,12 @@ export const useRecipeManager = (chat: ChatType, recipe?: Recipe | null) => {
|
||||
const chatContext = useChatContext();
|
||||
const messages = chat.messages;
|
||||
|
||||
// Get recipe parameters from deeplink if available
|
||||
const paramsFromConfig =
|
||||
(window.appConfig?.get('recipeParameters') as Record<string, string> | null | undefined) ??
|
||||
null;
|
||||
const recipeParametersFromConfig = useRef<Record<string, string> | null>(paramsFromConfig);
|
||||
|
||||
const messagesRef = useRef(messages);
|
||||
const isCreatingRecipeRef = useRef(false);
|
||||
const hasCheckedRecipeRef = useRef(false);
|
||||
@@ -32,6 +38,27 @@ export const useRecipeManager = (chat: ChatType, recipe?: Recipe | null) => {
|
||||
|
||||
const finalRecipe = chat.recipe;
|
||||
const resolvedRecipe = chat.resolvedRecipe;
|
||||
|
||||
// Initialize parameters from deeplink when recipe is loaded (from backend/deeplink)
|
||||
useEffect(() => {
|
||||
if (!chatContext || !finalRecipe) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only initialize if we have params from config and haven't set them yet
|
||||
const hasNoParameters =
|
||||
!chat.recipeParameterValues ||
|
||||
(typeof chat.recipeParameterValues === 'object' &&
|
||||
Object.keys(chat.recipeParameterValues).length === 0);
|
||||
|
||||
if (recipeParametersFromConfig.current && hasNoParameters) {
|
||||
chatContext.setChat({
|
||||
...chatContext.chat,
|
||||
recipeParameterValues: recipeParametersFromConfig.current,
|
||||
});
|
||||
}
|
||||
}, [chatContext, finalRecipe, chat]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chatContext) return;
|
||||
|
||||
@@ -55,10 +82,13 @@ export const useRecipeManager = (chat: ChatType, recipe?: Recipe | null) => {
|
||||
setIsRecipeWarningModalOpen(false);
|
||||
hasCheckedRecipeRef.current = false; // Reset check flag for new recipe
|
||||
|
||||
// Initialize with parameters from deeplink if available
|
||||
const initialParameterValues = recipeParametersFromConfig.current || null;
|
||||
|
||||
chatContext.setChat({
|
||||
...chatContext.chat,
|
||||
recipe: recipe,
|
||||
recipeParameterValues: null,
|
||||
recipeParameterValues: initialParameterValues,
|
||||
messages: [],
|
||||
});
|
||||
}
|
||||
|
||||
+54
-18
@@ -181,7 +181,7 @@ if (process.platform !== 'darwin') {
|
||||
const recentDirs = loadRecentDirs();
|
||||
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
|
||||
|
||||
const recipeDeeplink = parseRecipeDeeplink(protocolUrl);
|
||||
const deeplinkData = parseRecipeDeeplink(protocolUrl);
|
||||
const scheduledJobId = parsedUrl.searchParams.get('scheduledJob');
|
||||
|
||||
createChat(
|
||||
@@ -191,9 +191,10 @@ if (process.platform !== 'darwin') {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
recipeDeeplink || undefined,
|
||||
deeplinkData?.config,
|
||||
scheduledJobId || undefined,
|
||||
undefined
|
||||
undefined,
|
||||
deeplinkData?.parameters
|
||||
);
|
||||
});
|
||||
return; // Skip the rest of the handler
|
||||
@@ -279,7 +280,7 @@ async function processProtocolUrl(parsedUrl: URL, window: BrowserWindow) {
|
||||
} else if (parsedUrl.hostname === 'sessions') {
|
||||
window.webContents.send('open-shared-session', pendingDeepLink);
|
||||
} else if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') {
|
||||
const recipeDeeplink = parseRecipeDeeplink(parsedUrl.toString());
|
||||
const deeplinkData = parseRecipeDeeplink(pendingDeepLink ?? parsedUrl.toString());
|
||||
const scheduledJobId = parsedUrl.searchParams.get('scheduledJob');
|
||||
|
||||
// Create a new window and ignore the passed-in window
|
||||
@@ -290,9 +291,10 @@ async function processProtocolUrl(parsedUrl: URL, window: BrowserWindow) {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
recipeDeeplink || undefined,
|
||||
deeplinkData?.config,
|
||||
scheduledJobId || undefined,
|
||||
undefined
|
||||
undefined,
|
||||
deeplinkData?.parameters
|
||||
);
|
||||
pendingDeepLink = null;
|
||||
}
|
||||
@@ -310,8 +312,8 @@ app.on('open-url', async (_event, url) => {
|
||||
console.log('[Main] Received open-url event:', url);
|
||||
if (parsedUrl.hostname === 'bot' || parsedUrl.hostname === 'recipe') {
|
||||
console.log('[Main] Detected bot/recipe URL, creating new chat window');
|
||||
let recipeDeeplink = parseRecipeDeeplink(url);
|
||||
if (recipeDeeplink) {
|
||||
const deeplinkData = parseRecipeDeeplink(url);
|
||||
if (deeplinkData) {
|
||||
windowDeeplinkURL = url;
|
||||
}
|
||||
const scheduledJobId = parsedUrl.searchParams.get('scheduledJob');
|
||||
@@ -324,9 +326,10 @@ app.on('open-url', async (_event, url) => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
recipeDeeplink || undefined,
|
||||
deeplinkData?.config,
|
||||
scheduledJobId || undefined,
|
||||
undefined
|
||||
undefined,
|
||||
deeplinkData?.parameters
|
||||
);
|
||||
windowDeeplinkURL = null;
|
||||
return; // Skip the rest of the handler
|
||||
@@ -495,7 +498,8 @@ const createChat = async (
|
||||
viewType?: string,
|
||||
recipeDeeplink?: string, // Raw deeplink decoded on server
|
||||
scheduledJobId?: string, // Scheduled job ID if applicable
|
||||
recipeId?: string
|
||||
recipeId?: string,
|
||||
recipeParameters?: Record<string, string> // Recipe parameter values from deeplink URL
|
||||
) => {
|
||||
updateEnvironmentVariables(envToggles);
|
||||
|
||||
@@ -544,6 +548,7 @@ const createChat = async (
|
||||
GOOSE_VERSION: version,
|
||||
recipeId: recipeId,
|
||||
recipeDeeplink: recipeDeeplink,
|
||||
recipeParameters: recipeParameters,
|
||||
scheduledJobId: scheduledJobId,
|
||||
}),
|
||||
],
|
||||
@@ -1017,9 +1022,9 @@ const openDirectoryDialog = async (): Promise<OpenDialogReturnValue> => {
|
||||
|
||||
addRecentDir(dirToAdd);
|
||||
|
||||
let recipeDeeplink: string | undefined = undefined;
|
||||
let deeplinkData: RecipeDeeplinkData | undefined = undefined;
|
||||
if (windowDeeplinkURL) {
|
||||
recipeDeeplink = parseRecipeDeeplink(windowDeeplinkURL);
|
||||
deeplinkData = parseRecipeDeeplink(windowDeeplinkURL);
|
||||
}
|
||||
// Create a new window with the selected directory
|
||||
await createChat(
|
||||
@@ -1029,14 +1034,21 @@ const openDirectoryDialog = async (): Promise<OpenDialogReturnValue> => {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
deeplinkData?.config,
|
||||
undefined,
|
||||
recipeDeeplink
|
||||
undefined,
|
||||
deeplinkData?.parameters
|
||||
);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
function parseRecipeDeeplink(url: string): string | undefined {
|
||||
interface RecipeDeeplinkData {
|
||||
config: string;
|
||||
parameters?: Record<string, string>;
|
||||
}
|
||||
|
||||
function parseRecipeDeeplink(url: string): RecipeDeeplinkData | undefined {
|
||||
const parsedUrl = new URL(url);
|
||||
let recipeDeeplink = parsedUrl.searchParams.get('config');
|
||||
if (recipeDeeplink && !url.includes(recipeDeeplink)) {
|
||||
@@ -1055,10 +1067,34 @@ function parseRecipeDeeplink(url: string): string | undefined {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (recipeDeeplink) {
|
||||
return recipeDeeplink;
|
||||
if (!recipeDeeplink) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
// Extract all query parameters except 'config' and 'scheduledJob' as recipe parameters
|
||||
// Use raw query string parsing to preserve '+' characters (consistent with config handling)
|
||||
const parameters: Record<string, string> = {};
|
||||
const search = parsedUrl.search || '';
|
||||
const paramMatches = search.matchAll(/[?&]([^=&]+)=([^&]*)/g);
|
||||
|
||||
for (const match of paramMatches) {
|
||||
const key = match[1];
|
||||
const rawValue = match[2];
|
||||
|
||||
if (key !== 'config' && key !== 'scheduledJob') {
|
||||
try {
|
||||
parameters[key] = decodeURIComponent(rawValue);
|
||||
} catch {
|
||||
// If decoding fails, use raw value
|
||||
parameters[key] = rawValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
config: recipeDeeplink,
|
||||
parameters: Object.keys(parameters).length > 0 ? parameters : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// Global error handler
|
||||
|
||||
Reference in New Issue
Block a user