+
+
Copy this link to share with friends or paste directly in Chrome to open
)}
{requiredFieldsAreFilled() && (
-
validateForm() && handleCopy()}
- className={`text-sm truncate dark:text-white font-mono ${!title.trim() || !description.trim() ? 'text-textDisabled' : 'text-textStandard'}`}
- >
- {deeplink}
+
+
validateForm() && handleCopy()}
+ className={`text-sm dark:text-white font-mono cursor-pointer hover:bg-background-default p-2 rounded transition-colors overflow-x-auto whitespace-nowrap ${!title.trim() || !description.trim() ? 'text-textDisabled' : 'text-textStandard'}`}
+ style={{ maxWidth: '500px', width: '100%' }}
+ >
+ {deeplink}
+
)}
@@ -507,17 +510,9 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
onClose={() => setIsScheduleModalOpen(false)}
recipe={getCurrentConfig()}
onCreateSchedule={(deepLink) => {
- // Open the schedules view with the deep link pre-filled
- window.electron.createChatWindow(
- undefined,
- undefined,
- undefined,
- undefined,
- undefined,
- 'schedules'
- );
- // Store the deep link in localStorage for the schedules view to pick up
+ // Navigate to the schedules view with the deep link pre-filled
localStorage.setItem('pendingScheduleDeepLink', deepLink);
+ navigate('/schedules');
}}
/>
diff --git a/ui/desktop/src/components/settings/models/bottom_bar/ModelsBottomBar.tsx b/ui/desktop/src/components/settings/models/bottom_bar/ModelsBottomBar.tsx
index 68ddd737f0..832b1296d4 100644
--- a/ui/desktop/src/components/settings/models/bottom_bar/ModelsBottomBar.tsx
+++ b/ui/desktop/src/components/settings/models/bottom_bar/ModelsBottomBar.tsx
@@ -25,12 +25,14 @@ interface ModelsBottomBarProps {
setView: (view: View) => void;
alerts: Alert[];
recipeConfig?: Recipe | null;
+ hasMessages?: boolean; // Add prop to know if there are messages to create a recipe from
}
export default function ModelsBottomBar({
dropdownRef,
setView,
alerts,
recipeConfig,
+ hasMessages = false,
}: ModelsBottomBarProps) {
const {
currentModel,
@@ -243,10 +245,18 @@ export default function ModelsBottomBar({
)}
-
setView('recipeEditor')}>
- Create a recipe
-
-
+ {/* Only show "Create a recipe from this session" when there are messages to create from */}
+ {hasMessages && (
+
{
+ // Signal to create an agent from the current chat
+ window.dispatchEvent(new CustomEvent('make-agent-from-chat'));
+ }}
+ >
+ Create a recipe from this session
+
+
+ )}
diff --git a/ui/desktop/src/hooks/useRecipeManager.ts b/ui/desktop/src/hooks/useRecipeManager.ts
index 6e805604af..fca9210cc3 100644
--- a/ui/desktop/src/hooks/useRecipeManager.ts
+++ b/ui/desktop/src/hooks/useRecipeManager.ts
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useState } from 'react';
+import { useEffect, useMemo, useState, useRef } from 'react';
import { createRecipe, Recipe } from '../recipe';
import { Message, createUserMessage } from '../types/message';
import { updateSystemPromptWithParameters } from '../utils/providerUtils';
@@ -15,10 +15,20 @@ export const useRecipeManager = (messages: Message[], locationState?: LocationSt
const [isParameterModalOpen, setIsParameterModalOpen] = useState(false);
const [recipeParameters, setRecipeParameters] = useState
| null>(null);
const [readyForAutoUserPrompt, setReadyForAutoUserPrompt] = useState(false);
+ const [recipeError, setRecipeError] = useState(null);
// Get chat context to access persisted recipe
const chatContext = useChatContext();
+ // Use a ref to capture the current messages for the event handler
+ const messagesRef = useRef(messages);
+ const isCreatingRecipeRef = useRef(false);
+
+ // Update the ref when messages change
+ useEffect(() => {
+ messagesRef.current = messages;
+ }, [messages]);
+
// Get recipeConfig from multiple sources with priority:
// 1. Chat context (persisted recipe)
// 2. Navigation state (from recipes view)
@@ -149,13 +159,27 @@ export const useRecipeManager = (messages: Message[], locationState?: LocationSt
// Listen for make-agent-from-chat event
useEffect(() => {
const handleMakeAgent = async () => {
+ // Prevent duplicate calls using global flag
+ if (window.isCreatingRecipe) {
+ return;
+ }
+
+ // Prevent duplicate calls using local ref
+ if (isCreatingRecipeRef.current) {
+ return;
+ }
+
window.electron.logInfo('Making recipe from chat...');
+
+ // Set both local and global flags
+ isCreatingRecipeRef.current = true;
+ window.isCreatingRecipe = true;
setIsGeneratingRecipe(true);
try {
- // Create recipe directly from chat messages
+ // Create recipe directly from chat messages using the ref to get current messages
const createRecipeRequest = {
- messages: messages,
+ messages: messagesRef.current,
title: '',
description: '',
};
@@ -166,16 +190,14 @@ export const useRecipeManager = (messages: Message[], locationState?: LocationSt
throw new Error(`Failed to create recipe: ${response.error}`);
}
- window.electron.logInfo('Created recipe:');
- window.electron.logInfo(JSON.stringify(response.recipe, null, 2));
+ window.electron.logInfo('Created recipe successfully');
- // First, verify the recipe data
+ // Verify the recipe data
if (!response.recipe) {
throw new Error('No recipe data received');
}
// Create a new window for the recipe editor
- console.log('Opening recipe editor with config:', response.recipe);
const recipeConfig = {
id: response.recipe.title || 'untitled',
title: response.recipe.title || 'Untitled Recipe',
@@ -184,6 +206,12 @@ export const useRecipeManager = (messages: Message[], locationState?: LocationSt
activities: response.recipe.activities || [],
prompt: response.recipe.prompt || '',
};
+
+ // Set a flag to prevent the current window from reacting to recipe config changes
+ // This prevents navigation conflicts when creating new windows
+ window.sessionStorage.setItem('ignoreRecipeConfigChanges', 'true');
+
+ // Create the new window
window.electron.createChatWindow(
undefined, // query
undefined, // dir
@@ -194,11 +222,21 @@ export const useRecipeManager = (messages: Message[], locationState?: LocationSt
);
window.electron.logInfo('Opening recipe editor window');
+
+ // Clear the ignore flag after a short delay
+ setTimeout(() => {
+ window.sessionStorage.removeItem('ignoreRecipeConfigChanges');
+ }, 1000);
} catch (error) {
window.electron.logInfo('Failed to create recipe:');
const errorMessage = error instanceof Error ? error.message : String(error);
window.electron.logInfo(errorMessage);
+
+ // Set the error state to show in modal
+ setRecipeError(errorMessage);
} finally {
+ isCreatingRecipeRef.current = false;
+ window.isCreatingRecipe = false;
setIsGeneratingRecipe(false);
}
};
@@ -208,7 +246,7 @@ export const useRecipeManager = (messages: Message[], locationState?: LocationSt
return () => {
window.removeEventListener('make-agent-from-chat', handleMakeAgent);
};
- }, [messages]);
+ }, []);
return {
recipeConfig,
@@ -219,5 +257,7 @@ export const useRecipeManager = (messages: Message[], locationState?: LocationSt
recipeParameters,
handleParameterSubmit,
handleAutoExecution,
+ recipeError,
+ setRecipeError,
};
};
diff --git a/ui/desktop/src/json.d.ts b/ui/desktop/src/vite-env.d.ts
similarity index 81%
rename from ui/desktop/src/json.d.ts
rename to ui/desktop/src/vite-env.d.ts
index 02b4714e65..61bdfbe0a0 100644
--- a/ui/desktop/src/json.d.ts
+++ b/ui/desktop/src/vite-env.d.ts
@@ -1,3 +1,5 @@
+///
+
declare module '*.json' {
const value: Record;
export default value;
@@ -49,3 +51,12 @@ declare namespace React {
WebkitAppRegion?: 'drag' | 'no-drag';
}
}
+
+// Extend Window interface to include global recipe creation flag
+declare global {
+ interface Window {
+ isCreatingRecipe?: boolean;
+ }
+}
+
+export {};