From 34ae36064c7ad6440e22e72294d8e12c376f1910 Mon Sep 17 00:00:00 2001 From: Aaron Goldsmith Date: Sat, 28 Jun 2025 20:26:07 -0700 Subject: [PATCH 01/14] feat: add parameter handling in recipe and chat components --- ui/desktop/src/components/ChatView.tsx | 75 +++++++++++++++-- .../src/components/ParameterInputModal.css | 39 +++++++++ .../src/components/ParameterInputModal.tsx | 84 +++++++++++++++++++ .../components/parameter/ParameterInput.tsx | 66 +++++++++++++++ ui/desktop/src/recipe/index.ts | 7 ++ 5 files changed, 264 insertions(+), 7 deletions(-) create mode 100644 ui/desktop/src/components/ParameterInputModal.css create mode 100644 ui/desktop/src/components/ParameterInputModal.tsx create mode 100644 ui/desktop/src/components/parameter/ParameterInput.tsx diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index 23407e8b3627..eb4c2e6faabc 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -26,6 +26,7 @@ import { fetchSessionDetails, generateSessionId } from '../sessions'; import 'react-toastify/dist/ReactToastify.css'; import { useMessageStream } from '../hooks/useMessageStream'; import { SessionSummaryModal } from './context_management/SessionSummaryModal'; +import ParameterInputModal from './ParameterInputModal'; import { Recipe } from '../recipe'; import { ChatContextManagerProvider, @@ -69,6 +70,15 @@ const isUserMessage = (message: Message): boolean => { return true; }; +const substituteParameters = (prompt: string, params: Record): string => { + let substitutedPrompt = prompt; + for (const key in params) { + const regex = new RegExp(`{{\\s?${key}\\s?}}`, 'g'); + substitutedPrompt = substitutedPrompt.replace(regex, params[key]); + } + return substitutedPrompt; +}; + export default function ChatView({ chat, setChat, @@ -114,6 +124,8 @@ function ChatContent({ const [localOutputTokens, setLocalOutputTokens] = useState(0); const [ancestorMessages, setAncestorMessages] = useState([]); const [droppedFiles, setDroppedFiles] = useState([]); + const [isParameterModalOpen, setIsParameterModalOpen] = useState(false); + const [recipeParameters, setRecipeParameters] = useState | null>(null); const [sessionCosts, setSessionCosts] = useState<{ [key: string]: { inputTokens: number; @@ -152,6 +164,17 @@ function ChatContent({ // Get recipeConfig directly from appConfig const recipeConfig = window.appConfig.get('recipeConfig') as Recipe | null; + // TODO: if recipeConfig and recipeConfig.parameters are defined, we should show a modal to input parameters + // before allowing the user to interact with the chat. This is not implemented yet. + useEffect(() => { + if (recipeConfig?.parameters && recipeConfig.parameters.length > 0) { + // If we have parameters and they haven't been set yet, open the modal. + if (!recipeParameters) { + setIsParameterModalOpen(true); + } + } + }, [recipeConfig, recipeParameters]); + // Store message in global history when it's added const storeMessageInHistory = useCallback((message: Message) => { if (isUserMessage(message)) { @@ -282,6 +305,7 @@ function ChatContent({ name: response.recipe.title || 'Untitled Recipe', // Does not exist on recipe type title: response.recipe.title || 'Untitled Recipe', description: response.recipe.description || '', + parameters: response.recipe.parameters || [], instructions: response.recipe.instructions || '', activities: response.recipe.activities || [], prompt: response.recipe.prompt || '', @@ -326,27 +350,48 @@ function ChatContent({ // Pre-fill input with recipe prompt instead of auto-sending it const initialPrompt = useMemo(() => { - return recipeConfig?.prompt || ''; - }, [recipeConfig?.prompt]); + if (!recipeConfig?.prompt) return ''; + + const hasRequiredParams = recipeConfig.parameters && recipeConfig.parameters.length > 0; + + // If params are required and have been collected, substitute them into the prompt. + if (hasRequiredParams && recipeParameters) { + return substituteParameters(recipeConfig.prompt, recipeParameters); + } + + // If there are no parameters, return the original prompt. + if (!hasRequiredParams) { + return recipeConfig.prompt; + } + + // Otherwise, we are waiting for parameters, so the input should be empty. + return ''; + }, [recipeConfig, recipeParameters]); // Auto-send the prompt for scheduled executions - useEffect(() => { + useEffect(() => { + const hasRequiredParams = recipeConfig?.parameters && recipeConfig.parameters.length > 0; + if ( recipeConfig?.isScheduledExecution && recipeConfig?.prompt && + (!hasRequiredParams || recipeParameters) && messages.length === 0 && !isLoading && readyForAutoUserPrompt ) { - console.log('Auto-sending prompt for scheduled execution:', recipeConfig.prompt); + // Substitute parameters if they exist + const finalPrompt = recipeParameters + ? substituteParameters(recipeConfig.prompt, recipeParameters) + : recipeConfig.prompt; + + console.log('Auto-sending substituted prompt for scheduled execution:', finalPrompt); - // Create and send the user message - const userMessage = createUserMessage(recipeConfig.prompt); + const userMessage = createUserMessage(finalPrompt); setLastInteractionTime(Date.now()); window.electron.startPowerSaveBlocker(); append(userMessage); - // Scroll to bottom after sending setTimeout(() => { if (scrollRef.current?.scrollToBottom) { scrollRef.current.scrollToBottom(); @@ -356,6 +401,7 @@ function ChatContent({ }, [ recipeConfig?.isScheduledExecution, recipeConfig?.prompt, + recipeParameters, messages.length, isLoading, readyForAutoUserPrompt, @@ -363,6 +409,11 @@ function ChatContent({ setLastInteractionTime, ]); + const handleParameterSubmit = (inputValues: Record) => { + setRecipeParameters(inputValues); + setIsParameterModalOpen(false); + }; + // Handle submit const handleSubmit = (e: React.FormEvent) => { window.electron.startPowerSaveBlocker(); @@ -818,6 +869,16 @@ function ChatContent({ }} summaryContent={summaryContent} /> + {isParameterModalOpen && recipeConfig?.parameters && ( + { + // We might want to prevent closing if fields are required. + setIsParameterModalOpen(false); + }} + /> + )} ); diff --git a/ui/desktop/src/components/ParameterInputModal.css b/ui/desktop/src/components/ParameterInputModal.css new file mode 100644 index 000000000000..46775b79afdb --- /dev/null +++ b/ui/desktop/src/components/ParameterInputModal.css @@ -0,0 +1,39 @@ +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; +} + +.modal form { + background: white; + padding: 20px; + border-radius: 5px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.modal h2 { + margin-bottom: 20px; +} + +.modal label { + display: block; + margin-bottom: 8px; +} + +.modal input { + width: 100%; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + margin-bottom: 10px; +} + +.modal button { + margin-right: 10px; +} diff --git a/ui/desktop/src/components/ParameterInputModal.tsx b/ui/desktop/src/components/ParameterInputModal.tsx new file mode 100644 index 000000000000..fe5f3bf0e400 --- /dev/null +++ b/ui/desktop/src/components/ParameterInputModal.tsx @@ -0,0 +1,84 @@ +import React, { useState, useEffect } from 'react'; + +// Assuming Parameter type is defined somewhere accessible, like in your recipe types +interface Parameter { + name: string; + promptMessage: string; + defaultValue?: string; + requirement: 'required' | 'optional' | 'interactive'; +} + +const ParameterInputModal = ({ parameters, onSubmit, onClose }) => { + const [inputValues, setInputValues] = useState({}); + + // Pre-fill the form with default values from the recipe + useEffect(() => { + const initialValues = {}; + parameters.forEach(param => { + if (param.defaultValue) { + initialValues[param.name] = param.defaultValue; + } + }); + setInputValues(initialValues); + }, [parameters]); + + const handleChange = (name, value) => { + setInputValues(prevValues => ({ ...prevValues, [name]: value })); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + // Check if all *required* parameters are filled + const requiredParams = parameters.filter(p => p.requirement === 'required'); + const missingFields = requiredParams.filter(p => !inputValues[p.name]?.trim()); + + if (missingFields.length > 0) { + alert(`Please fill in all required fields: ${missingFields.map(p => p.name).join(', ')}`); + return; + } + onSubmit(inputValues); + }; + + return ( + // This styling creates a dark, semi-transparent overlay that centers the modal. +
+
+

Recipe Parameters

+
+ {parameters.map(param => ( +
+ + handleChange(param.name, e.target.value)} + className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" + placeholder={param.defaultValue || `Enter value for ${param.name}...`} + /> +
+ ))} +
+ + +
+
+
+
+ ); +}; + +export default ParameterInputModal; \ No newline at end of file diff --git a/ui/desktop/src/components/parameter/ParameterInput.tsx b/ui/desktop/src/components/parameter/ParameterInput.tsx new file mode 100644 index 000000000000..b440b85bf083 --- /dev/null +++ b/ui/desktop/src/components/parameter/ParameterInput.tsx @@ -0,0 +1,66 @@ +import React, { useState } from 'react'; + +export interface Parameter { + name: string; + promptMessage: string; + defaultValue?: string; + requirement: 'required' | 'optional' | 'interactive'; +} + +interface ParameterInputProps { + parameter: Parameter; + value: string; + onChange: (name: string, value: Partial) => void; +} + +const ParameterInput: React.FC = ({ parameter, value, onChange }) => { + const [requirement, setRequirement] = useState(parameter.requirement || 'required'); + const [defaultValue, setDefaultValue] = useState(parameter.defaultValue || ''); + + const handleRequirementChange = (newRequirement: 'required' | 'optional' | 'interactive') => { + setRequirement(newRequirement); + onChange(parameter.name, { requirement: newRequirement, defaultValue }); + }; + + return ( +
+ + onChange(parameter.name, { defaultValue: e.target.value })} + className="w-full p-3 border rounded-lg bg-bgApp text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" + placeholder={parameter.defaultValue || ''} + disabled={requirement === 'interactive'} + /> +
+ + +
+ {requirement === 'optional' && ( +
+ + setDefaultValue(e.target.value)} + className="w-full p-2 border rounded" + placeholder="Enter default value" + /> +
+ )} +
+ ); +}; + +export default ParameterInput; diff --git a/ui/desktop/src/recipe/index.ts b/ui/desktop/src/recipe/index.ts index df8c802d6c74..b87150782997 100644 --- a/ui/desktop/src/recipe/index.ts +++ b/ui/desktop/src/recipe/index.ts @@ -2,12 +2,19 @@ import { Message } from '../types/message'; import { getApiUrl } from '../config'; import { FullExtensionConfig } from '../extensions'; +export interface Parameter { + name: string; + promptMessage: string; + defaultValue?: string; +} + export interface Recipe { title: string; description: string; instructions: string; prompt?: string; activities?: string[]; + parameters?: Parameter[]; author?: { contact?: string; metadata?: string; From ffedce477c4b0e6c4298d037e550e88c9db04a34 Mon Sep 17 00:00:00 2001 From: Aaron Goldsmith Date: Sun, 29 Jun 2025 07:31:05 -0700 Subject: [PATCH 02/14] wip: add parameter interface --- .../src/components/ParameterInputModal.tsx | 21 +-- ui/desktop/src/components/RecipeEditor.tsx | 142 ++++++++++++++++-- .../components/parameter/ParameterInput.tsx | 116 ++++++++------ ui/desktop/src/recipe/index.ts | 10 ++ 4 files changed, 217 insertions(+), 72 deletions(-) diff --git a/ui/desktop/src/components/ParameterInputModal.tsx b/ui/desktop/src/components/ParameterInputModal.tsx index fe5f3bf0e400..e4bf494e3d0f 100644 --- a/ui/desktop/src/components/ParameterInputModal.tsx +++ b/ui/desktop/src/components/ParameterInputModal.tsx @@ -2,9 +2,10 @@ import React, { useState, useEffect } from 'react'; // Assuming Parameter type is defined somewhere accessible, like in your recipe types interface Parameter { - name: string; - promptMessage: string; - defaultValue?: string; + key: string; + description: string; + input_type: string + default?: string requirement: 'required' | 'optional' | 'interactive'; } @@ -16,7 +17,7 @@ const ParameterInputModal = ({ parameters, onSubmit, onClose }) => { const initialValues = {}; parameters.forEach(param => { if (param.defaultValue) { - initialValues[param.name] = param.defaultValue; + initialValues[param.key] = param.defaultValue; } }); setInputValues(initialValues); @@ -30,7 +31,7 @@ const ParameterInputModal = ({ parameters, onSubmit, onClose }) => { event.preventDefault(); // Check if all *required* parameters are filled const requiredParams = parameters.filter(p => p.requirement === 'required'); - const missingFields = requiredParams.filter(p => !inputValues[p.name]?.trim()); + const missingFields = requiredParams.filter(p => !inputValues[p.key]?.trim()); if (missingFields.length > 0) { alert(`Please fill in all required fields: ${missingFields.map(p => p.name).join(', ')}`); @@ -46,17 +47,17 @@ const ParameterInputModal = ({ parameters, onSubmit, onClose }) => {

Recipe Parameters

{parameters.map(param => ( -
+
handleChange(param.name, e.target.value)} + value={inputValues[param.key] || ''} + onChange={(e) => handleChange(param.key, e.target.value)} className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" - placeholder={param.defaultValue || `Enter value for ${param.name}...`} + placeholder={param.defaultValue || `Enter value for ${param.key}...`} />
))} diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index e062357fbd76..0b8ab214ecdb 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -1,5 +1,8 @@ +import yaml from 'js-yaml'; import { useState, useEffect } from 'react'; -import { Recipe } from '../recipe'; +import { Recipe, Parameter } from '../recipe'; + + import { Buffer } from 'buffer'; import { FullExtensionConfig } from '../extensions'; import { Geese } from './icons/Geese'; @@ -11,6 +14,7 @@ import RecipeActivityEditor from './RecipeActivityEditor'; import RecipeInfoModal from './RecipeInfoModal'; import RecipeExpandableInfo from './RecipeExpandableInfo'; import { ScheduleFromRecipeModal } from './schedule/ScheduleFromRecipeModal'; +import ParameterInput from './parameter/ParameterInput'; interface RecipeEditorProps { config?: Recipe; @@ -22,6 +26,23 @@ function generateDeepLink(recipe: Recipe): string { return `goose://recipe?config=${configBase64}`; } +// Function to serialize parameters to YAML format +function serializeParametersToYAML(parameters: Parameter[]): string { + const yamlParams = parameters.map(param => { + const paramYaml: any = { + key: param.key, + input_type: 'string', // Assuming input type is string; change if applicable + requirement: param.requirement, + description: param.promptMessage, + }; + if (param.requirement === 'optional' && param.defaultValue) { + paramYaml.default = param.defaultValue; + } + return paramYaml; + }); + return yaml.dump({ parameters: yamlParams }); +} + export default function RecipeEditor({ config }: RecipeEditorProps) { const { getExtensions } = useConfig(); const [recipeConfig] = useState(config); @@ -30,6 +51,10 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { const [instructions, setInstructions] = useState(config?.instructions || ''); const [prompt, setPrompt] = useState(config?.prompt || ''); const [activities, setActivities] = useState(config?.activities || []); + const [parameters, setParameters] = useState(parseParametersFromInstructions(instructions)); + + const [parametersValues, setParametersValues] = useState<{ [name: string]: string }>({}); + const [extensionOptions, setExtensionOptions] = useState([]); const [extensionsLoaded, setExtensionsLoaded] = useState(false); const [copied, setCopied] = useState(false); @@ -62,6 +87,38 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { const [activeSection, _] = useState<'none' | 'activities' | 'instructions' | 'extensions'>( 'none' ); + const [showParameterModal, setShowParameterModal] = useState(false); + const [currentParameter, setCurrentParameter] = useState(null); + const [parameterInputs, setParameterInputs] = useState<{ [key: string]: string }>({}); + + useEffect(() => { + // When the recipe is loaded, check if there are parameters that require input + const needsInput = parameters.some(param => param.requirement === 'required' || param.requirement === 'interactive'); + // If any such parameters exist, show the input modal for them + if (needsInput) { + setCurrentParameter(parameters.find(param => param.requirement === 'required' || param.requirement === 'interactive') || null); + setShowParameterModal(true); + } + }, [parameters]); + + const handleParameterInputSave = () => { + if (currentParameter) { + setParameterInputs(prev => ({ ...prev, [currentParameter.name]: parametersValues[currentParameter.name] || '' })); + // Close the modal or move to the next parameter if needed + const nextParameter = parameters.find(param => param.key !== currentParameter.name && (param.requirement === 'required' || param.requirement === 'interactive')); + if (nextParameter) { + setCurrentParameter(nextParameter); + } else { + setShowParameterModal(false); // Close modal if no more parameters + } + } + }; + + const handleParameterModalClose = () => { + setShowParameterModal(false); + }; + + // Load extensions when component mounts and when switching to extensions section useEffect(() => { @@ -101,22 +158,41 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [recipeExtensions, extensionsLoaded]); + // Use effect to set parameters whenever instructions change + useEffect(() => { + setParameters(parseParametersFromInstructions(instructions)); + }, [instructions]); + const getCurrentConfig = (): Recipe => { - console.log('Creating config with:', { - selectedExtensions: recipeExtensions, - availableExtensions: extensionOptions, - recipeConfig, - }); + // NEW: Transform the internal parameters state into the desired output format. + const formattedParameters = parameters.map(param => { + const formattedParam: any = { + key: param.key, + input_type: 'string', // As specified + requirement: param.requirement, + description: param.promptMessage, + }; - const config = { - ...recipeConfig, - title, - description, - instructions, - activities, - prompt, - extensions: recipeExtensions - .map((name) => { + // Add the 'default' key ONLY if the parameter is optional and has a default value. + if (param.requirement === 'optional' && param.defaultValue) { + // Note: `default` is a reserved keyword in JS, but assigning it as a property key like this is valid. + formattedParam.default = param.defaultValue; + } + + return formattedParam; + }); + + const config = { + ...recipeConfig, + title, + description, + instructions, + activities, + prompt, + // Use the newly formatted parameters array in the final config object. + parameters: formattedParameters, + extensions: recipeExtensions + .map((name) => { const extension = extensionOptions.find((e) => e.name === name); console.log('Looking for extension:', name, 'Found:', extension); if (!extension) return null; @@ -135,6 +211,10 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { .filter(Boolean) as FullExtensionConfig[], }; console.log('Final config extensions:', config.extensions); + + const yamlString = serializeParametersToYAML(parameters); + console.log('Serialized Parameters to YAML:', yamlString); + return config; }; @@ -163,6 +243,14 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { return Object.keys(newErrors).length === 0; }; + const handleParameterChange = (name: string, value: Partial) => { + setParameters((prev) => prev.map(param => + param.key === name + ? { ...param, ...value } + : param + )); + }; + const deeplink = generateDeepLink(getCurrentConfig()); const handleCopy = () => { @@ -203,6 +291,20 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { const subtitle = config?.title ? "You can edit the recipe below to change the agent's behavior in a new session." : 'Your custom agent recipe can be shared with others. Fill in the sections below to create!'; + + function parseParametersFromInstructions(instructions: string): Parameter[] { + const regex = /\{\{(.*?)\}\}/g; + const matches = [...instructions.matchAll(regex)]; + + return matches.map((match) => { + return { + name: match[1].trim(), + promptMessage: `Enter value for ${match[1].trim()}`, + requirement: 'required', + }; + }); + } + return (
{activeSection === 'none' && ( @@ -281,6 +383,14 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
{errors.instructions}
)}
+ {parameters.map((parameter) => ( + handleParameterChange(name, value)} + /> + ))}
); -} +} \ No newline at end of file diff --git a/ui/desktop/src/components/parameter/ParameterInput.tsx b/ui/desktop/src/components/parameter/ParameterInput.tsx index b440b85bf083..ffe3b7d052aa 100644 --- a/ui/desktop/src/components/parameter/ParameterInput.tsx +++ b/ui/desktop/src/components/parameter/ParameterInput.tsx @@ -1,66 +1,90 @@ -import React, { useState } from 'react'; +import React from 'react'; +// The Parameter interface remains the same export interface Parameter { name: string; promptMessage: string; defaultValue?: string; requirement: 'required' | 'optional' | 'interactive'; } +// TODO: add consistent interface +// interface Parameter { +// key: string; +// description: string; +// input_type: string +// default?: string +// requirement: 'required' | 'optional' | 'interactive'; +// } interface ParameterInputProps { parameter: Parameter; - value: string; - onChange: (name: string, value: Partial) => void; + onChange: (name: string, updatedParameter: Partial) => void; } -const ParameterInput: React.FC = ({ parameter, value, onChange }) => { - const [requirement, setRequirement] = useState(parameter.requirement || 'required'); - const [defaultValue, setDefaultValue] = useState(parameter.defaultValue || ''); - - const handleRequirementChange = (newRequirement: 'required' | 'optional' | 'interactive') => { - setRequirement(newRequirement); - onChange(parameter.name, { requirement: newRequirement, defaultValue }); - }; +const ParameterInput: React.FC = ({ parameter, onChange }) => { + // All values are derived directly from props, maintaining the controlled component pattern + const { name, promptMessage, requirement, defaultValue } = parameter; return ( -
- - onChange(parameter.name, { defaultValue: e.target.value })} - className="w-full p-3 border rounded-lg bg-bgApp text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" - placeholder={parameter.defaultValue || ''} - disabled={requirement === 'interactive'} - /> -
- - +
+ {/* NEW: Static title to show which parameter is being configured. + This replaces the first input box. + */} +

+ Parameter: {name} +

+ + {/* Input for the user-facing prompt message */} +
+ + onChange(name, { promptMessage: e.target.value })} + className="w-full p-3 border rounded-lg bg-bgApp text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" + placeholder={`E.g., "Enter the name for the new component"`} + disabled={requirement === 'interactive'} + /> +

This is the message the end-user will see.

- {requirement === 'optional' && ( -
- - setDefaultValue(e.target.value)} - className="w-full p-2 border rounded" - placeholder="Enter default value" - /> + + {/* Controls for requirement and default value */} +
+
+ +
- )} + + {/* The default value input is only shown for optional parameters */} + {requirement === 'optional' && ( +
+ + onChange(name, { defaultValue: e.target.value })} + className="w-full p-3 border rounded-lg bg-bgApp text-textStandard" + placeholder="Enter default value" + /> +
+ )} +
); }; -export default ParameterInput; +export default ParameterInput; \ No newline at end of file diff --git a/ui/desktop/src/recipe/index.ts b/ui/desktop/src/recipe/index.ts index b87150782997..572c44bc958b 100644 --- a/ui/desktop/src/recipe/index.ts +++ b/ui/desktop/src/recipe/index.ts @@ -2,10 +2,20 @@ import { Message } from '../types/message'; import { getApiUrl } from '../config'; import { FullExtensionConfig } from '../extensions'; +// TODO: implement consistent interface for parameters +// interface Parameter { +// key: string; +// description: string; +// input_type: string +// default?: string +// requirement: 'required' | 'optional' | 'interactive'; +// } + export interface Parameter { name: string; promptMessage: string; defaultValue?: string; + requirement: 'required' | 'optional' | 'interactive'; } export interface Recipe { From 370cbfdb7f2525874fa988e840adef9d38bc29ba Mon Sep 17 00:00:00 2001 From: Aaron Goldsmith Date: Sun, 29 Jun 2025 08:35:59 -0700 Subject: [PATCH 03/14] remove unused imports and fix eslint issues --- ui/desktop/package-lock.json | 8 + ui/desktop/package.json | 1 + .../src/components/ParameterInputModal.tsx | 156 +++++++++--------- ui/desktop/src/components/RecipeEditor.tsx | 135 ++++++--------- .../components/parameter/ParameterInput.tsx | 52 +++--- ui/desktop/src/main.ts | 2 +- ui/desktop/src/recipe/index.ts | 15 +- 7 files changed, 166 insertions(+), 203 deletions(-) diff --git a/ui/desktop/package-lock.json b/ui/desktop/package-lock.json index 493373e61a68..80e6412953e7 100644 --- a/ui/desktop/package-lock.json +++ b/ui/desktop/package-lock.json @@ -78,6 +78,7 @@ "@types/electron-squirrel-startup": "^1.0.2", "@types/electron-window-state": "^2.0.34", "@types/express": "^5.0.0", + "@types/js-yaml": "^4.0.9", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.3.3", @@ -4266,6 +4267,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", diff --git a/ui/desktop/package.json b/ui/desktop/package.json index e3a07c1094d8..99a43338e868 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -55,6 +55,7 @@ "@types/electron-squirrel-startup": "^1.0.2", "@types/electron-window-state": "^2.0.34", "@types/express": "^5.0.0", + "@types/js-yaml": "^4.0.9", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.3.3", diff --git a/ui/desktop/src/components/ParameterInputModal.tsx b/ui/desktop/src/components/ParameterInputModal.tsx index e4bf494e3d0f..105836be4048 100644 --- a/ui/desktop/src/components/ParameterInputModal.tsx +++ b/ui/desktop/src/components/ParameterInputModal.tsx @@ -1,85 +1,93 @@ import React, { useState, useEffect } from 'react'; - -// Assuming Parameter type is defined somewhere accessible, like in your recipe types -interface Parameter { - key: string; - description: string; - input_type: string - default?: string - requirement: 'required' | 'optional' | 'interactive'; +import { Parameter } from '../recipe'; +interface ParameterInputModalProps { + parameters: Parameter[]; + onSubmit: (values: Record) => void; + onClose: () => void; } -const ParameterInputModal = ({ parameters, onSubmit, onClose }) => { - const [inputValues, setInputValues] = useState({}); +const ParameterInputModal: React.FC = ({ + parameters, + onSubmit, + onClose, +}) => { + const [inputValues, setInputValues] = useState>({}); + + // Pre-fill the form with default values from the recipe + useEffect(() => { + const initialValues: Record = {}; + parameters.forEach((param) => { + if (param.default) { + initialValues[param.key] = param.default; + } + }); + setInputValues(initialValues); + }, [parameters]); - // Pre-fill the form with default values from the recipe - useEffect(() => { - const initialValues = {}; - parameters.forEach(param => { - if (param.defaultValue) { - initialValues[param.key] = param.defaultValue; - } - }); - setInputValues(initialValues); - }, [parameters]); + interface MissingField { + key: string; + name?: string; + requirement?: string; + } - const handleChange = (name, value) => { - setInputValues(prevValues => ({ ...prevValues, [name]: value })); - }; + const handleChange = (name: string, value: string): void => { + setInputValues((prevValues: Record) => ({ ...prevValues, [name]: value })); + }; - const handleSubmit = (event) => { - event.preventDefault(); - // Check if all *required* parameters are filled - const requiredParams = parameters.filter(p => p.requirement === 'required'); - const missingFields = requiredParams.filter(p => !inputValues[p.key]?.trim()); + const handleSubmit = (): void => { + // event.preventDefault(); + // Check if all *required* parameters are filled + const requiredParams: Parameter[] = parameters.filter((p) => p.requirement === 'required'); + const missingFields: MissingField[] = requiredParams.filter((p) => !inputValues[p.key]?.trim()); - if (missingFields.length > 0) { - alert(`Please fill in all required fields: ${missingFields.map(p => p.name).join(', ')}`); - return; - } - onSubmit(inputValues); - }; + if (missingFields.length > 0) { + // TODO: Show a user-friendly message instead of an alert + // alert(`Please fill in all required fields: ${missingFields.map(p => p.name).join(', ')}`); + return; + } + onSubmit(inputValues); + }; - return ( - // This styling creates a dark, semi-transparent overlay that centers the modal. -
-
-

Recipe Parameters

- - {parameters.map(param => ( -
- - handleChange(param.key, e.target.value)} - className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" - placeholder={param.defaultValue || `Enter value for ${param.key}...`} - /> -
- ))} -
- - -
- + return ( + // This styling creates a dark, semi-transparent overlay that centers the modal. +
+
+

Recipe Parameters

+
+ {parameters.map((param) => ( +
+ + handleChange(param.key, e.target.value)} + className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" + placeholder={param.default || `Enter value for ${param.key}...`} + />
-
- ); + ))} +
+ + +
+ +
+
+ ); }; -export default ParameterInputModal; \ No newline at end of file +export default ParameterInputModal; diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index 0b8ab214ecdb..61a7f21baa16 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -1,7 +1,7 @@ import yaml from 'js-yaml'; import { useState, useEffect } from 'react'; -import { Recipe, Parameter } from '../recipe'; - +import { Recipe } from '../recipe'; +import { Parameter } from '../recipe/index'; import { Buffer } from 'buffer'; import { FullExtensionConfig } from '../extensions'; @@ -11,7 +11,6 @@ import { Check } from 'lucide-react'; import { useConfig } from './ConfigContext'; import { FixedExtensionEntry } from './ConfigContext'; import RecipeActivityEditor from './RecipeActivityEditor'; -import RecipeInfoModal from './RecipeInfoModal'; import RecipeExpandableInfo from './RecipeExpandableInfo'; import { ScheduleFromRecipeModal } from './schedule/ScheduleFromRecipeModal'; import ParameterInput from './parameter/ParameterInput'; @@ -28,15 +27,15 @@ function generateDeepLink(recipe: Recipe): string { // Function to serialize parameters to YAML format function serializeParametersToYAML(parameters: Parameter[]): string { - const yamlParams = parameters.map(param => { - const paramYaml: any = { + const yamlParams = parameters.map((param) => { + const paramYaml: Parameter = { key: param.key, input_type: 'string', // Assuming input type is string; change if applicable requirement: param.requirement, - description: param.promptMessage, + description: param.description, }; - if (param.requirement === 'optional' && param.defaultValue) { - paramYaml.default = param.defaultValue; + if (param.requirement === 'optional' && param.default) { + paramYaml.default = param.default; } return paramYaml; }); @@ -51,16 +50,16 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { const [instructions, setInstructions] = useState(config?.instructions || ''); const [prompt, setPrompt] = useState(config?.prompt || ''); const [activities, setActivities] = useState(config?.activities || []); - const [parameters, setParameters] = useState(parseParametersFromInstructions(instructions)); - - const [parametersValues, setParametersValues] = useState<{ [name: string]: string }>({}); + const [parameters, setParameters] = useState( + parseParametersFromInstructions(instructions) + ); const [extensionOptions, setExtensionOptions] = useState([]); const [extensionsLoaded, setExtensionsLoaded] = useState(false); const [copied, setCopied] = useState(false); - const [isRecipeInfoModalOpen, setRecipeInfoModalOpen] = useState(false); + const [_isRecipeInfoModalOpen, setRecipeInfoModalOpen] = useState(false); const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false); - const [recipeInfoModelProps, setRecipeInfoModelProps] = useState<{ + const [_recipeInfoModelProps, setRecipeInfoModelProps] = useState<{ label: string; value: string; setValue: (value: string) => void; @@ -87,38 +86,6 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { const [activeSection, _] = useState<'none' | 'activities' | 'instructions' | 'extensions'>( 'none' ); - const [showParameterModal, setShowParameterModal] = useState(false); - const [currentParameter, setCurrentParameter] = useState(null); - const [parameterInputs, setParameterInputs] = useState<{ [key: string]: string }>({}); - - useEffect(() => { - // When the recipe is loaded, check if there are parameters that require input - const needsInput = parameters.some(param => param.requirement === 'required' || param.requirement === 'interactive'); - // If any such parameters exist, show the input modal for them - if (needsInput) { - setCurrentParameter(parameters.find(param => param.requirement === 'required' || param.requirement === 'interactive') || null); - setShowParameterModal(true); - } - }, [parameters]); - - const handleParameterInputSave = () => { - if (currentParameter) { - setParameterInputs(prev => ({ ...prev, [currentParameter.name]: parametersValues[currentParameter.name] || '' })); - // Close the modal or move to the next parameter if needed - const nextParameter = parameters.find(param => param.key !== currentParameter.name && (param.requirement === 'required' || param.requirement === 'interactive')); - if (nextParameter) { - setCurrentParameter(nextParameter); - } else { - setShowParameterModal(false); // Close modal if no more parameters - } - } - }; - - const handleParameterModalClose = () => { - setShowParameterModal(false); - }; - - // Load extensions when component mounts and when switching to extensions section useEffect(() => { @@ -165,34 +132,34 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { const getCurrentConfig = (): Recipe => { // NEW: Transform the internal parameters state into the desired output format. - const formattedParameters = parameters.map(param => { - const formattedParam: any = { - key: param.key, - input_type: 'string', // As specified - requirement: param.requirement, - description: param.promptMessage, - }; + const formattedParameters = parameters.map((param) => { + const formattedParam: Parameter = { + key: param.key, + input_type: 'string', // As specified + requirement: param.requirement, + description: param.description, + }; - // Add the 'default' key ONLY if the parameter is optional and has a default value. - if (param.requirement === 'optional' && param.defaultValue) { - // Note: `default` is a reserved keyword in JS, but assigning it as a property key like this is valid. - formattedParam.default = param.defaultValue; - } - - return formattedParam; - }); + // Add the 'default' key ONLY if the parameter is optional and has a default value. + if (param.requirement === 'optional' && param.default) { + // Note: `default` is a reserved keyword in JS, but assigning it as a property key like this is valid. + formattedParam.default = param.default; + } + + return formattedParam; + }); - const config = { - ...recipeConfig, - title, - description, - instructions, - activities, - prompt, - // Use the newly formatted parameters array in the final config object. - parameters: formattedParameters, - extensions: recipeExtensions - .map((name) => { + const config = { + ...recipeConfig, + title, + description, + instructions, + activities, + prompt, + // Use the newly formatted parameters array in the final config object. + parameters: formattedParameters, + extensions: recipeExtensions + .map((name) => { const extension = extensionOptions.find((e) => e.name === name); console.log('Looking for extension:', name, 'Found:', extension); if (!extension) return null; @@ -214,7 +181,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { const yamlString = serializeParametersToYAML(parameters); console.log('Serialized Parameters to YAML:', yamlString); - + return config; }; @@ -244,11 +211,9 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { }; const handleParameterChange = (name: string, value: Partial) => { - setParameters((prev) => prev.map(param => - param.key === name - ? { ...param, ...value } - : param - )); + setParameters((prev) => + prev.map((param) => (param.key === name ? { ...param, ...value } : param)) + ); }; const deeplink = generateDeepLink(getCurrentConfig()); @@ -298,9 +263,10 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { return matches.map((match) => { return { - name: match[1].trim(), - promptMessage: `Enter value for ${match[1].trim()}`, + key: match[1].trim(), + description: `Enter value for ${match[1].trim()}`, requirement: 'required', + input_type: 'string', // Default to string; can be changed based on requirements }; }); } @@ -383,11 +349,10 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
{errors.instructions}
)}
- {parameters.map((parameter) => ( + {parameters.map((parameter: Parameter) => ( handleParameterChange(name, value)} /> ))} @@ -461,13 +426,13 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
- setRecipeInfoModalOpen(false)} onSaveValue={recipeInfoModelProps?.setValue} - /> + /> */} setIsScheduleModalOpen(false)} @@ -488,4 +453,4 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { />
); -} \ No newline at end of file +} diff --git a/ui/desktop/src/components/parameter/ParameterInput.tsx b/ui/desktop/src/components/parameter/ParameterInput.tsx index ffe3b7d052aa..1887882c673d 100644 --- a/ui/desktop/src/components/parameter/ParameterInput.tsx +++ b/ui/desktop/src/components/parameter/ParameterInput.tsx @@ -1,20 +1,12 @@ import React from 'react'; -// The Parameter interface remains the same -export interface Parameter { - name: string; - promptMessage: string; - defaultValue?: string; +interface Parameter { + key: string; + description: string; + input_type: string; + default?: string; requirement: 'required' | 'optional' | 'interactive'; } -// TODO: add consistent interface -// interface Parameter { -// key: string; -// description: string; -// input_type: string -// default?: string -// requirement: 'required' | 'optional' | 'interactive'; -// } interface ParameterInputProps { parameter: Parameter; @@ -23,26 +15,22 @@ interface ParameterInputProps { const ParameterInput: React.FC = ({ parameter, onChange }) => { // All values are derived directly from props, maintaining the controlled component pattern - const { name, promptMessage, requirement, defaultValue } = parameter; + const { key, description, requirement } = parameter; + const defaultValue = parameter.default || ''; return (
- {/* NEW: Static title to show which parameter is being configured. - This replaces the first input box. - */}

- Parameter: {name} + Parameter:{' '} + {JSON.stringify(parameter)}

- {/* Input for the user-facing prompt message */}
- + onChange(name, { promptMessage: e.target.value })} + value={description || ''} + onChange={(e) => onChange(key, { description: e.target.value })} className="w-full p-3 border rounded-lg bg-bgApp text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" placeholder={`E.g., "Enter the name for the new component"`} disabled={requirement === 'interactive'} @@ -53,17 +41,17 @@ const ParameterInput: React.FC = ({ parameter, onChange }) {/* Controls for requirement and default value */}
- +
@@ -75,8 +63,8 @@ const ParameterInput: React.FC = ({ parameter, onChange }) onChange(name, { defaultValue: e.target.value })} + value={defaultValue} + onChange={(e) => onChange(key, { default: e.target.value })} className="w-full p-3 border rounded-lg bg-bgApp text-textStandard" placeholder="Enter default value" /> @@ -87,4 +75,4 @@ const ParameterInput: React.FC = ({ parameter, onChange }) ); }; -export default ParameterInput; \ No newline at end of file +export default ParameterInput; diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index f310eb162f02..ffc6945b8faf 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -1750,7 +1750,7 @@ app.whenReady().then(async () => { // Log the recipeConfig for debugging console.log('Creating chat window with recipeConfig:', recipeConfig); - + // TODO: handle parameters // Pass recipeConfig as part of viewOptions when viewType is recipeEditor createChat(app, query, dir, version, resumeSessionId, recipeConfig, viewType); } diff --git a/ui/desktop/src/recipe/index.ts b/ui/desktop/src/recipe/index.ts index 572c44bc958b..1004951621cc 100644 --- a/ui/desktop/src/recipe/index.ts +++ b/ui/desktop/src/recipe/index.ts @@ -3,18 +3,11 @@ import { getApiUrl } from '../config'; import { FullExtensionConfig } from '../extensions'; // TODO: implement consistent interface for parameters -// interface Parameter { -// key: string; -// description: string; -// input_type: string -// default?: string -// requirement: 'required' | 'optional' | 'interactive'; -// } - export interface Parameter { - name: string; - promptMessage: string; - defaultValue?: string; + key: string; + description: string; + input_type: string; + default?: string; requirement: 'required' | 'optional' | 'interactive'; } From 400850c12c901cc8698a1d6adc0990988702c57a Mon Sep 17 00:00:00 2001 From: Aaron Goldsmith Date: Sun, 29 Jun 2025 08:39:19 -0700 Subject: [PATCH 04/14] update todo --- ui/desktop/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index ffc6945b8faf..25e9ca826f2f 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -1750,7 +1750,7 @@ app.whenReady().then(async () => { // Log the recipeConfig for debugging console.log('Creating chat window with recipeConfig:', recipeConfig); - // TODO: handle parameters + // TODO: replace parameters with provided values // Pass recipeConfig as part of viewOptions when viewType is recipeEditor createChat(app, query, dir, version, resumeSessionId, recipeConfig, viewType); } From 3ffb6e10f167bebea19beeca98fbac033c9e8036 Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Sun, 29 Jun 2025 09:41:13 -0700 Subject: [PATCH 05/14] feat: enhance parameter handling with logging and validation improvements --- ui/desktop/src/components/ChatView.tsx | 21 ++++++++-- .../src/components/ParameterInputModal.css | 39 ------------------- .../src/components/ParameterInputModal.tsx | 2 - ui/desktop/src/components/RecipeEditor.tsx | 8 +--- .../components/parameter/ParameterInput.tsx | 9 +---- ui/desktop/src/main.ts | 2 +- 6 files changed, 20 insertions(+), 61 deletions(-) delete mode 100644 ui/desktop/src/components/ParameterInputModal.css diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index eb4c2e6faabc..a0af44a72a2a 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -72,10 +72,22 @@ const isUserMessage = (message: Message): boolean => { const substituteParameters = (prompt: string, params: Record): string => { let substitutedPrompt = prompt; + + console.log('Substituting parameters:', { prompt, params }); + for (const key in params) { - const regex = new RegExp(`{{\\s?${key}\\s?}}`, 'g'); + // Escape special characters in the key (parameter) and match optional whitespace + const regex = new RegExp(`{{\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*}}`, 'g'); + const beforeSubstitution = substitutedPrompt; substitutedPrompt = substitutedPrompt.replace(regex, params[key]); + + // Log each substitution for debugging + if (beforeSubstitution !== substitutedPrompt) { + console.log(`Replaced {{${key}}} with "${params[key]}"`); + } } + + console.log('Final substituted prompt:', substitutedPrompt); return substitutedPrompt; }; @@ -164,8 +176,8 @@ function ChatContent({ // Get recipeConfig directly from appConfig const recipeConfig = window.appConfig.get('recipeConfig') as Recipe | null; - // TODO: if recipeConfig and recipeConfig.parameters are defined, we should show a modal to input parameters - // before allowing the user to interact with the chat. This is not implemented yet. + // Show parameter modal if recipe has parameters and they haven't been set yet + // TODO: Allow user to close the recipe if they don't want to set parameters useEffect(() => { if (recipeConfig?.parameters && recipeConfig.parameters.length > 0) { // If we have parameters and they haven't been set yet, open the modal. @@ -369,7 +381,7 @@ function ChatContent({ }, [recipeConfig, recipeParameters]); // Auto-send the prompt for scheduled executions - useEffect(() => { + useEffect(() => { const hasRequiredParams = recipeConfig?.parameters && recipeConfig.parameters.length > 0; if ( @@ -401,6 +413,7 @@ function ChatContent({ }, [ recipeConfig?.isScheduledExecution, recipeConfig?.prompt, + recipeConfig?.parameters, recipeParameters, messages.length, isLoading, diff --git a/ui/desktop/src/components/ParameterInputModal.css b/ui/desktop/src/components/ParameterInputModal.css deleted file mode 100644 index 46775b79afdb..000000000000 --- a/ui/desktop/src/components/ParameterInputModal.css +++ /dev/null @@ -1,39 +0,0 @@ -.modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; -} - -.modal form { - background: white; - padding: 20px; - border-radius: 5px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); -} - -.modal h2 { - margin-bottom: 20px; -} - -.modal label { - display: block; - margin-bottom: 8px; -} - -.modal input { - width: 100%; - padding: 10px; - border: 1px solid #ccc; - border-radius: 4px; - margin-bottom: 10px; -} - -.modal button { - margin-right: 10px; -} diff --git a/ui/desktop/src/components/ParameterInputModal.tsx b/ui/desktop/src/components/ParameterInputModal.tsx index 105836be4048..906131db967b 100644 --- a/ui/desktop/src/components/ParameterInputModal.tsx +++ b/ui/desktop/src/components/ParameterInputModal.tsx @@ -35,14 +35,12 @@ const ParameterInputModal: React.FC = ({ }; const handleSubmit = (): void => { - // event.preventDefault(); // Check if all *required* parameters are filled const requiredParams: Parameter[] = parameters.filter((p) => p.requirement === 'required'); const missingFields: MissingField[] = requiredParams.filter((p) => !inputValues[p.key]?.trim()); if (missingFields.length > 0) { // TODO: Show a user-friendly message instead of an alert - // alert(`Please fill in all required fields: ${missingFields.map(p => p.name).join(', ')}`); return; } onSubmit(inputValues); diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index 61a7f21baa16..a054d658bf90 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -426,13 +426,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
- {/* setRecipeInfoModalOpen(false)} - onSaveValue={recipeInfoModelProps?.setValue} - /> */} + setIsScheduleModalOpen(false)} diff --git a/ui/desktop/src/components/parameter/ParameterInput.tsx b/ui/desktop/src/components/parameter/ParameterInput.tsx index 1887882c673d..f67794b2d0a0 100644 --- a/ui/desktop/src/components/parameter/ParameterInput.tsx +++ b/ui/desktop/src/components/parameter/ParameterInput.tsx @@ -1,12 +1,5 @@ import React from 'react'; - -interface Parameter { - key: string; - description: string; - input_type: string; - default?: string; - requirement: 'required' | 'optional' | 'interactive'; -} +import { Parameter } from '../../recipe'; interface ParameterInputProps { parameter: Parameter; diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 25e9ca826f2f..9d9878f4d764 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -1750,7 +1750,7 @@ app.whenReady().then(async () => { // Log the recipeConfig for debugging console.log('Creating chat window with recipeConfig:', recipeConfig); - // TODO: replace parameters with provided values + // TODO: Consider adding parameter validation here before creating chat window // Pass recipeConfig as part of viewOptions when viewType is recipeEditor createChat(app, query, dir, version, resumeSessionId, recipeConfig, viewType); } From 8e89bf4b8565d2d28756b8df22c951a409db51f4 Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Sun, 29 Jun 2025 14:34:51 -0700 Subject: [PATCH 06/14] feat: implement parameter substitution in system prompt update --- ui/desktop/src/components/ChatView.tsx | 10 +++- ui/desktop/src/components/RecipeEditor.tsx | 12 ++++- ui/desktop/src/utils/providerUtils.ts | 63 ++++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index a0af44a72a2a..fac7f9133883 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -36,6 +36,7 @@ import { ContextHandler } from './context_management/ContextHandler'; import { LocalMessageStorage } from '../utils/localMessageStorage'; import { useModelAndProvider } from './ModelAndProviderContext'; import { getCostForModel } from '../utils/costDatabase'; +import { updateSystemPromptWithParameters } from '../utils/providerUtils'; import { Message, createUserMessage, @@ -422,9 +423,16 @@ function ChatContent({ setLastInteractionTime, ]); - const handleParameterSubmit = (inputValues: Record) => { + const handleParameterSubmit = async (inputValues: Record) => { setRecipeParameters(inputValues); setIsParameterModalOpen(false); + + // Update the system prompt with parameter-substituted instructions + try { + await updateSystemPromptWithParameters(inputValues); + } catch (error) { + console.error('Failed to update system prompt with parameters:', error); + } }; // Handle submit diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index a054d658bf90..6781d7a87852 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -11,6 +11,7 @@ import { Check } from 'lucide-react'; import { useConfig } from './ConfigContext'; import { FixedExtensionEntry } from './ConfigContext'; import RecipeActivityEditor from './RecipeActivityEditor'; +import RecipeInfoModal from './RecipeInfoModal'; import RecipeExpandableInfo from './RecipeExpandableInfo'; import { ScheduleFromRecipeModal } from './schedule/ScheduleFromRecipeModal'; import ParameterInput from './parameter/ParameterInput'; @@ -57,9 +58,9 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { const [extensionOptions, setExtensionOptions] = useState([]); const [extensionsLoaded, setExtensionsLoaded] = useState(false); const [copied, setCopied] = useState(false); - const [_isRecipeInfoModalOpen, setRecipeInfoModalOpen] = useState(false); + const [isRecipeInfoModalOpen, setRecipeInfoModalOpen] = useState(false); const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false); - const [_recipeInfoModelProps, setRecipeInfoModelProps] = useState<{ + const [recipeInfoModelProps, setRecipeInfoModelProps] = useState<{ label: string; value: string; setValue: (value: string) => void; @@ -427,6 +428,13 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { + setRecipeInfoModalOpen(false)} + onSaveValue={recipeInfoModelProps?.setValue} + /> setIsScheduleModalOpen(false)} diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index a26f9f1a542a..c71bb6a0bf13 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -52,6 +52,69 @@ You can also validate your output after you have generated it to ensure it meets There may be (but not always) some tools mentioned in the instructions which you can check are available to this instance of goose (and try to help the user if they are not or find alternatives). `; +// Helper function to substitute parameters in text +const substituteParameters = (text: string, params: Record): string => { + let substitutedText = text; + + console.log('Substituting parameters in instructions:', { text, params }); + + for (const key in params) { + // Escape special characters in the key (parameter) and match optional whitespace + const regex = new RegExp(`{{\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*}}`, 'g'); + const beforeSubstitution = substitutedText; + substitutedText = substitutedText.replace(regex, params[key]); + + // Log each substitution for debugging + if (beforeSubstitution !== substitutedText) { + console.log(`Replaced {{${key}}} with "${params[key]}" in instructions`); + } + } + + console.log('Final substituted instructions:', substitutedText); + return substitutedText; +}; + +/** + * Updates the system prompt with parameter-substituted instructions + * This should be called after recipe parameters are collected + */ +export const updateSystemPromptWithParameters = async ( + recipeParameters: Record +): Promise => { + try { + const recipeConfig = window.appConfig?.get?.('recipeConfig'); + const originalInstructions = (recipeConfig as { instructions?: string })?.instructions; + + if (!originalInstructions) { + console.log('No instructions to substitute parameters in'); + return; + } + + // Substitute parameters in the instructions + const substitutedInstructions = substituteParameters(originalInstructions, recipeParameters); + + // Update the system prompt with substituted instructions + const response = await fetch(getApiUrl('/agent/prompt'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Secret-Key': getSecretKey(), + }, + body: JSON.stringify({ + extension: `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${substitutedInstructions}`, + }), + }); + + if (!response.ok) { + console.warn(`Failed to update system prompt with parameters: ${response.statusText}`); + } else { + console.log('Successfully updated system prompt with parameter-substituted instructions'); + } + } catch (error) { + console.error('Error updating system prompt with parameters:', error); + } +}; + /** * Migrates extensions from localStorage to config.yaml (settings v2) * This function handles the migration from settings v1 to v2 by: From a585e978d939f8b3d1710f54d322313b5a039c5d Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Sun, 29 Jun 2025 15:23:58 -0700 Subject: [PATCH 07/14] feat: enhance ParameterInputModal with validation and cancel options --- .../src/components/ParameterInputModal.tsx | 158 +++++++++++++----- .../components/parameter/ParameterInput.tsx | 3 +- 2 files changed, 116 insertions(+), 45 deletions(-) diff --git a/ui/desktop/src/components/ParameterInputModal.tsx b/ui/desktop/src/components/ParameterInputModal.tsx index 906131db967b..eb1a630bc169 100644 --- a/ui/desktop/src/components/ParameterInputModal.tsx +++ b/ui/desktop/src/components/ParameterInputModal.tsx @@ -1,5 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Parameter } from '../recipe'; +import { Button } from './ui/button'; + interface ParameterInputModalProps { parameters: Parameter[]; onSubmit: (values: Record) => void; @@ -12,6 +14,8 @@ const ParameterInputModal: React.FC = ({ onClose, }) => { const [inputValues, setInputValues] = useState>({}); + const [validationErrors, setValidationErrors] = useState>({}); + const [showCancelOptions, setShowCancelOptions] = useState(false); // Pre-fill the form with default values from the recipe useEffect(() => { @@ -24,66 +28,134 @@ const ParameterInputModal: React.FC = ({ setInputValues(initialValues); }, [parameters]); - interface MissingField { - key: string; - name?: string; - requirement?: string; - } - const handleChange = (name: string, value: string): void => { setInputValues((prevValues: Record) => ({ ...prevValues, [name]: value })); }; const handleSubmit = (): void => { + // Clear previous validation errors + setValidationErrors({}); + // Check if all *required* parameters are filled const requiredParams: Parameter[] = parameters.filter((p) => p.requirement === 'required'); - const missingFields: MissingField[] = requiredParams.filter((p) => !inputValues[p.key]?.trim()); + const errors: Record = {}; - if (missingFields.length > 0) { - // TODO: Show a user-friendly message instead of an alert + requiredParams.forEach((param) => { + const value = inputValues[param.key]?.trim(); + if (!value) { + errors[param.key] = `${param.description || param.key} is required`; + } + }); + + if (Object.keys(errors).length > 0) { + setValidationErrors(errors); return; } + onSubmit(inputValues); }; + const handleCancel = (): void => { + // Always show cancel options if recipe has any parameters (required or optional) + const hasAnyParams = parameters.length > 0; + + if (hasAnyParams) { + setShowCancelOptions(true); + } else { + onClose(); + } + }; + + const handleCancelOption = (option: 'new-chat' | 'back-to-form'): void => { + if (option === 'new-chat') { + // Create a new chat window without recipe config + try { + const workingDir = window.appConfig.get('GOOSE_WORKING_DIR'); + console.log(`Creating new chat window without recipe, working dir: ${workingDir}`); + window.electron.createChatWindow(undefined, workingDir as string); + // Close the current window after creating the new one + window.electron.hideWindow(); + } catch (error) { + console.error('Error creating new window:', error); + // Fallback: just close the modal + onClose(); + } + } else { + setShowCancelOptions(false); // Go back to the parameter form + } + }; + return ( - // This styling creates a dark, semi-transparent overlay that centers the modal. -
-
-

Recipe Parameters

-
- {parameters.map((param) => ( -
- - handleChange(param.key, e.target.value)} - className="w-full p-3 border border-borderSubtle rounded-lg bg-bgSubtle text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" - placeholder={param.default || `Enter value for ${param.key}...`} - /> -
- ))} -
- - + Start New Chat (No Recipe) +
-
-
+
+ ) : ( + // Main parameter form +
+

Recipe Parameters

+
+ {parameters.map((param) => ( +
+ + handleChange(param.key, e.target.value)} + className={`w-full p-3 border rounded-lg bg-bgSubtle text-textStandard focus:outline-none focus:ring-2 ${ + validationErrors[param.key] + ? 'border-red-500 focus:ring-red-500' + : 'border-borderSubtle focus:ring-borderProminent' + }`} + placeholder={param.default || `Enter value for ${param.key}...`} + /> + {validationErrors[param.key] && ( +

{validationErrors[param.key]}

+ )} +
+ ))} +
+ + +
+
+
+ )} ); }; diff --git a/ui/desktop/src/components/parameter/ParameterInput.tsx b/ui/desktop/src/components/parameter/ParameterInput.tsx index f67794b2d0a0..bfe10678d865 100644 --- a/ui/desktop/src/components/parameter/ParameterInput.tsx +++ b/ui/desktop/src/components/parameter/ParameterInput.tsx @@ -14,8 +14,7 @@ const ParameterInput: React.FC = ({ parameter, onChange }) return (

- Parameter:{' '} - {JSON.stringify(parameter)} + Parameter: {parameter.key}

From 6f0c02c875273f0afe1179f83ad4362b9dfa9a76 Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Sun, 29 Jun 2025 15:26:37 -0700 Subject: [PATCH 08/14] remove todos --- ui/desktop/src/main.ts | 1 - ui/desktop/src/recipe/index.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 9d9878f4d764..9bc783b4e373 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -1750,7 +1750,6 @@ app.whenReady().then(async () => { // Log the recipeConfig for debugging console.log('Creating chat window with recipeConfig:', recipeConfig); - // TODO: Consider adding parameter validation here before creating chat window // Pass recipeConfig as part of viewOptions when viewType is recipeEditor createChat(app, query, dir, version, resumeSessionId, recipeConfig, viewType); } diff --git a/ui/desktop/src/recipe/index.ts b/ui/desktop/src/recipe/index.ts index 1004951621cc..1d8567ddf26d 100644 --- a/ui/desktop/src/recipe/index.ts +++ b/ui/desktop/src/recipe/index.ts @@ -2,7 +2,6 @@ import { Message } from '../types/message'; import { getApiUrl } from '../config'; import { FullExtensionConfig } from '../extensions'; -// TODO: implement consistent interface for parameters export interface Parameter { key: string; description: string; From 6f08a0326afa5e954580986540563840982ffa46 Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Sun, 29 Jun 2025 17:00:11 -0700 Subject: [PATCH 09/14] feat: update parameter requirement type from 'interactive' to 'user_prompt' and remove unused options in ParameterInput refactor: simplify onClose handler in ParameterInputModal and clean up styling comments --- ui/desktop/src/components/ChatView.tsx | 6 +----- ui/desktop/src/components/ParameterInputModal.tsx | 1 - ui/desktop/src/components/RecipeEditor.tsx | 6 +++--- ui/desktop/src/components/parameter/ParameterInput.tsx | 2 -- ui/desktop/src/main.ts | 1 + ui/desktop/src/recipe/index.ts | 2 +- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index fac7f9133883..09f7ec603e30 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -178,7 +178,6 @@ function ChatContent({ const recipeConfig = window.appConfig.get('recipeConfig') as Recipe | null; // Show parameter modal if recipe has parameters and they haven't been set yet - // TODO: Allow user to close the recipe if they don't want to set parameters useEffect(() => { if (recipeConfig?.parameters && recipeConfig.parameters.length > 0) { // If we have parameters and they haven't been set yet, open the modal. @@ -894,10 +893,7 @@ function ChatContent({ { - // We might want to prevent closing if fields are required. - setIsParameterModalOpen(false); - }} + onClose={() => setIsParameterModalOpen(false)} /> )}
diff --git a/ui/desktop/src/components/ParameterInputModal.tsx b/ui/desktop/src/components/ParameterInputModal.tsx index eb1a630bc169..481f13653022 100644 --- a/ui/desktop/src/components/ParameterInputModal.tsx +++ b/ui/desktop/src/components/ParameterInputModal.tsx @@ -86,7 +86,6 @@ const ParameterInputModal: React.FC = ({ }; return ( - // Modified styling to allow background graphics to show through
{showCancelOptions ? ( // Cancel options modal diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index 6781d7a87852..8417627d55fc 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -31,7 +31,7 @@ function serializeParametersToYAML(parameters: Parameter[]): string { const yamlParams = parameters.map((param) => { const paramYaml: Parameter = { key: param.key, - input_type: 'string', // Assuming input type is string; change if applicable + input_type: 'string', requirement: param.requirement, description: param.description, }; @@ -132,11 +132,11 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { }, [instructions]); const getCurrentConfig = (): Recipe => { - // NEW: Transform the internal parameters state into the desired output format. + // Transform the internal parameters state into the desired output format. const formattedParameters = parameters.map((param) => { const formattedParam: Parameter = { key: param.key, - input_type: 'string', // As specified + input_type: 'string', requirement: param.requirement, description: param.description, }; diff --git a/ui/desktop/src/components/parameter/ParameterInput.tsx b/ui/desktop/src/components/parameter/ParameterInput.tsx index bfe10678d865..b6e9ca4e00f3 100644 --- a/ui/desktop/src/components/parameter/ParameterInput.tsx +++ b/ui/desktop/src/components/parameter/ParameterInput.tsx @@ -25,7 +25,6 @@ const ParameterInput: React.FC = ({ parameter, onChange }) onChange={(e) => onChange(key, { description: e.target.value })} className="w-full p-3 border rounded-lg bg-bgApp text-textStandard focus:outline-none focus:ring-2 focus:ring-borderProminent" placeholder={`E.g., "Enter the name for the new component"`} - disabled={requirement === 'interactive'} />

This is the message the end-user will see.

@@ -43,7 +42,6 @@ const ParameterInput: React.FC = ({ parameter, onChange }) > - {/* */}
diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 9bc783b4e373..f310eb162f02 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -1750,6 +1750,7 @@ app.whenReady().then(async () => { // Log the recipeConfig for debugging console.log('Creating chat window with recipeConfig:', recipeConfig); + // Pass recipeConfig as part of viewOptions when viewType is recipeEditor createChat(app, query, dir, version, resumeSessionId, recipeConfig, viewType); } diff --git a/ui/desktop/src/recipe/index.ts b/ui/desktop/src/recipe/index.ts index 1d8567ddf26d..5bf85f850465 100644 --- a/ui/desktop/src/recipe/index.ts +++ b/ui/desktop/src/recipe/index.ts @@ -7,7 +7,7 @@ export interface Parameter { description: string; input_type: string; default?: string; - requirement: 'required' | 'optional' | 'interactive'; + requirement: 'required' | 'optional' | 'user_prompt'; } export interface Recipe { From 9e4bd22adee19a015fba2d26599c98ecba5b546e Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Mon, 30 Jun 2025 08:39:44 -0700 Subject: [PATCH 10/14] fix: update parameter handling in RecipeEditor to include prompt changes and avoid duplicates --- ui/desktop/src/components/RecipeEditor.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index 8417627d55fc..6b14ebc6eab5 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -126,10 +126,21 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [recipeExtensions, extensionsLoaded]); - // Use effect to set parameters whenever instructions change + // Use effect to set parameters whenever instructions or prompt changes useEffect(() => { - setParameters(parseParametersFromInstructions(instructions)); - }, [instructions]); + const instructionsParams = parseParametersFromInstructions(instructions); + const promptParams = parseParametersFromInstructions(prompt); + + // Combine parameters, ensuring no duplicates by key + const allParams = [...instructionsParams]; + promptParams.forEach((promptParam) => { + if (!allParams.some((param) => param.key === promptParam.key)) { + allParams.push(promptParam); + } + }); + + setParameters(allParams); + }, [instructions, prompt]); const getCurrentConfig = (): Recipe => { // Transform the internal parameters state into the desired output format. From 4ad4ea9a8f9e944e37911aa182ba228cc2e74daa Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Mon, 30 Jun 2025 13:09:13 -0700 Subject: [PATCH 11/14] refactor: remove console logging from parameter substitution functions --- ui/desktop/src/components/ChatView.tsx | 10 ---------- ui/desktop/src/utils/providerUtils.ts | 9 --------- 2 files changed, 19 deletions(-) diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index 09f7ec603e30..4d586bfd6002 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -74,21 +74,11 @@ const isUserMessage = (message: Message): boolean => { const substituteParameters = (prompt: string, params: Record): string => { let substitutedPrompt = prompt; - console.log('Substituting parameters:', { prompt, params }); - for (const key in params) { // Escape special characters in the key (parameter) and match optional whitespace const regex = new RegExp(`{{\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*}}`, 'g'); - const beforeSubstitution = substitutedPrompt; substitutedPrompt = substitutedPrompt.replace(regex, params[key]); - - // Log each substitution for debugging - if (beforeSubstitution !== substitutedPrompt) { - console.log(`Replaced {{${key}}} with "${params[key]}"`); - } } - - console.log('Final substituted prompt:', substitutedPrompt); return substitutedPrompt; }; diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index c71bb6a0bf13..4d8f8fbc219e 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -56,21 +56,12 @@ There may be (but not always) some tools mentioned in the instructions which you const substituteParameters = (text: string, params: Record): string => { let substitutedText = text; - console.log('Substituting parameters in instructions:', { text, params }); - for (const key in params) { // Escape special characters in the key (parameter) and match optional whitespace const regex = new RegExp(`{{\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*}}`, 'g'); - const beforeSubstitution = substitutedText; substitutedText = substitutedText.replace(regex, params[key]); - - // Log each substitution for debugging - if (beforeSubstitution !== substitutedText) { - console.log(`Replaced {{${key}}} with "${params[key]}" in instructions`); - } } - console.log('Final substituted instructions:', substitutedText); return substitutedText; }; From 8e8b042cfa2d4593673f3333b2148f19c3d7bb5b Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Wed, 2 Jul 2025 09:26:40 -0700 Subject: [PATCH 12/14] address feedback --- ui/desktop/src/components/RecipeEditor.tsx | 3 --- ui/desktop/src/utils/providerUtils.ts | 1 - 2 files changed, 4 deletions(-) diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index 6b14ebc6eab5..1bc84948e79f 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -191,9 +191,6 @@ export default function RecipeEditor({ config }: RecipeEditorProps) { }; console.log('Final config extensions:', config.extensions); - const yamlString = serializeParametersToYAML(parameters); - console.log('Serialized Parameters to YAML:', yamlString); - return config; }; diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index 4d8f8fbc219e..154071729131 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -77,7 +77,6 @@ export const updateSystemPromptWithParameters = async ( const originalInstructions = (recipeConfig as { instructions?: string })?.instructions; if (!originalInstructions) { - console.log('No instructions to substitute parameters in'); return; } From 2f29d52668ce9fd4eec0a6a371a837d14a0d831f Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Wed, 2 Jul 2025 09:28:15 -0700 Subject: [PATCH 13/14] Removed unused js-yaml --- ui/desktop/package-lock.json | 8 -------- ui/desktop/package.json | 1 - ui/desktop/src/components/RecipeEditor.tsx | 18 ------------------ 3 files changed, 27 deletions(-) diff --git a/ui/desktop/package-lock.json b/ui/desktop/package-lock.json index 80e6412953e7..493373e61a68 100644 --- a/ui/desktop/package-lock.json +++ b/ui/desktop/package-lock.json @@ -78,7 +78,6 @@ "@types/electron-squirrel-startup": "^1.0.2", "@types/electron-window-state": "^2.0.34", "@types/express": "^5.0.0", - "@types/js-yaml": "^4.0.9", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.3.3", @@ -4267,13 +4266,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/js-yaml": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 99a43338e868..e3a07c1094d8 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -55,7 +55,6 @@ "@types/electron-squirrel-startup": "^1.0.2", "@types/electron-window-state": "^2.0.34", "@types/express": "^5.0.0", - "@types/js-yaml": "^4.0.9", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "@vitejs/plugin-react": "^4.3.3", diff --git a/ui/desktop/src/components/RecipeEditor.tsx b/ui/desktop/src/components/RecipeEditor.tsx index 1bc84948e79f..39168b89a282 100644 --- a/ui/desktop/src/components/RecipeEditor.tsx +++ b/ui/desktop/src/components/RecipeEditor.tsx @@ -1,4 +1,3 @@ -import yaml from 'js-yaml'; import { useState, useEffect } from 'react'; import { Recipe } from '../recipe'; import { Parameter } from '../recipe/index'; @@ -26,23 +25,6 @@ function generateDeepLink(recipe: Recipe): string { return `goose://recipe?config=${configBase64}`; } -// Function to serialize parameters to YAML format -function serializeParametersToYAML(parameters: Parameter[]): string { - const yamlParams = parameters.map((param) => { - const paramYaml: Parameter = { - key: param.key, - input_type: 'string', - requirement: param.requirement, - description: param.description, - }; - if (param.requirement === 'optional' && param.default) { - paramYaml.default = param.default; - } - return paramYaml; - }); - return yaml.dump({ parameters: yamlParams }); -} - export default function RecipeEditor({ config }: RecipeEditorProps) { const { getExtensions } = useConfig(); const [recipeConfig] = useState(config); From e314a5892a6beca21e0af3e1c4a4bc9e9398ce8a Mon Sep 17 00:00:00 2001 From: AaronGoldsmith Date: Wed, 2 Jul 2025 10:19:01 -0700 Subject: [PATCH 14/14] refactor: remove success log from updateSystemPromptWithParameters --- ui/desktop/src/utils/providerUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/desktop/src/utils/providerUtils.ts b/ui/desktop/src/utils/providerUtils.ts index 154071729131..ad48462e6859 100644 --- a/ui/desktop/src/utils/providerUtils.ts +++ b/ui/desktop/src/utils/providerUtils.ts @@ -97,8 +97,6 @@ export const updateSystemPromptWithParameters = async ( if (!response.ok) { console.warn(`Failed to update system prompt with parameters: ${response.statusText}`); - } else { - console.log('Successfully updated system prompt with parameter-substituted instructions'); } } catch (error) { console.error('Error updating system prompt with parameters:', error);