Create / edit recipe form unification and improvements (#4693)
This commit is contained in:
@@ -247,16 +247,6 @@ describe('providerUtils', () => {
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle non-array parameters', () => {
|
||||
const parameters = {} as unknown as RecipeParameter[]; // Invalid type
|
||||
const recipeContent = {
|
||||
prompt: 'Use {{some_param}}',
|
||||
};
|
||||
|
||||
const result = filterValidUsedParameters(parameters, recipeContent);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty recipe content', () => {
|
||||
const parameters = [createParameter('param1'), createParameter('param2')];
|
||||
const recipeContent = {};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NavigateFunction } from 'react-router-dom';
|
||||
import { Recipe } from '../api/types.gen';
|
||||
|
||||
export type View =
|
||||
| 'welcome'
|
||||
@@ -15,7 +16,6 @@ export type View =
|
||||
| 'schedules'
|
||||
| 'sharedSession'
|
||||
| 'loading'
|
||||
| 'recipeEditor'
|
||||
| 'recipes'
|
||||
| 'permission';
|
||||
|
||||
@@ -27,13 +27,14 @@ export type ViewOptions = {
|
||||
sessionDetails?: unknown;
|
||||
error?: string;
|
||||
baseUrl?: string;
|
||||
config?: unknown;
|
||||
recipe?: Recipe;
|
||||
parentView?: View;
|
||||
parentViewOptions?: ViewOptions;
|
||||
disableAnimation?: boolean;
|
||||
initialMessage?: string;
|
||||
resetChat?: boolean;
|
||||
shareToken?: string;
|
||||
resumeSessionId?: string;
|
||||
};
|
||||
|
||||
export const createNavigationHandler = (navigate: NavigateFunction) => {
|
||||
@@ -66,9 +67,7 @@ export const createNavigationHandler = (navigate: NavigateFunction) => {
|
||||
case 'sharedSession':
|
||||
navigate('/shared-session', { state: options });
|
||||
break;
|
||||
case 'recipeEditor':
|
||||
navigate('/recipe-editor', { state: options });
|
||||
break;
|
||||
|
||||
case 'welcome':
|
||||
navigate('/welcome', { state: options });
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { ExtensionConfig, FixedExtensionEntry } from '../components/ConfigC
|
||||
import { addSubRecipesToAgent } from '../recipe/add_sub_recipe_on_agent';
|
||||
import {
|
||||
extendPrompt,
|
||||
Recipe,
|
||||
RecipeParameter,
|
||||
SubRecipe,
|
||||
updateAgentProvider,
|
||||
@@ -78,9 +79,9 @@ const isValidParameterName = (variable: string): boolean => {
|
||||
// Helper function to filter recipe parameters to only show valid ones that are actually used
|
||||
export const filterValidUsedParameters = (
|
||||
parameters: RecipeParameter[] | undefined,
|
||||
recipeContent: { prompt?: string; instructions?: string }
|
||||
recipeContent: { prompt?: string; instructions?: string; activities?: string[] }
|
||||
): RecipeParameter[] => {
|
||||
if (!parameters || !Array.isArray(parameters)) {
|
||||
if (!parameters) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -91,7 +92,13 @@ export const filterValidUsedParameters = (
|
||||
const instructionVariables = recipeContent.instructions
|
||||
? extractTemplateVariables(recipeContent.instructions)
|
||||
: [];
|
||||
const allUsedVariables = [...new Set([...promptVariables, ...instructionVariables])];
|
||||
|
||||
// Extract variables from activities using flatMap
|
||||
const activityVariables = recipeContent.activities?.flatMap(extractTemplateVariables) ?? [];
|
||||
|
||||
const allUsedVariables = [
|
||||
...new Set([...promptVariables, ...instructionVariables, ...activityVariables]),
|
||||
];
|
||||
|
||||
// Filter parameters to only include:
|
||||
// 1. Parameters with valid names (no spaces, dots, pipes, etc.)
|
||||
@@ -142,15 +149,15 @@ export const substituteParameters = (text: string, params: Record<string, string
|
||||
export const updateSystemPromptWithParameters = async (
|
||||
sessionId: string,
|
||||
recipeParameters: Record<string, string>,
|
||||
recipeConfig?: {
|
||||
recipe?: {
|
||||
instructions?: string | null;
|
||||
sub_recipes?: SubRecipe[] | null;
|
||||
parameters?: RecipeParameter[] | null;
|
||||
}
|
||||
): Promise<void> => {
|
||||
const subRecipes = recipeConfig?.sub_recipes;
|
||||
const subRecipes = recipe?.sub_recipes;
|
||||
try {
|
||||
const originalInstructions = recipeConfig?.instructions;
|
||||
const originalInstructions = recipe?.instructions;
|
||||
|
||||
if (!originalInstructions) {
|
||||
return;
|
||||
@@ -191,6 +198,8 @@ export const initializeSystem = async (
|
||||
getExtensions?: (b: boolean) => Promise<FixedExtensionEntry[]>;
|
||||
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
setIsExtensionsLoading?: (loading: boolean) => void;
|
||||
recipeParameters?: Record<string, string> | null;
|
||||
recipe?: Recipe;
|
||||
}
|
||||
) => {
|
||||
try {
|
||||
@@ -215,18 +224,26 @@ export const initializeSystem = async (
|
||||
console.log('This will not end well');
|
||||
}
|
||||
|
||||
// Get recipeConfig directly here
|
||||
const recipeConfig = window.appConfig?.get?.('recipe');
|
||||
const recipe_instructions = (recipeConfig as { instructions?: string })?.instructions;
|
||||
const responseConfig = (recipeConfig as { response?: { json_schema?: unknown } })?.response;
|
||||
const subRecipes = (recipeConfig as { sub_recipes?: SubRecipe[] })?.sub_recipes;
|
||||
const parameters = (recipeConfig as { parameters?: RecipeParameter[] })?.parameters;
|
||||
const hasParameters = parameters && parameters?.length > 0;
|
||||
// Get recipe - prefer from options (session metadata) over app config
|
||||
const recipe = options?.recipe || window.appConfig?.get?.('recipe');
|
||||
const recipe_instructions = (recipe as { instructions?: string })?.instructions;
|
||||
const responseConfig = (recipe as { response?: { json_schema?: unknown } })?.response;
|
||||
const subRecipes = (recipe as { sub_recipes?: SubRecipe[] })?.sub_recipes;
|
||||
const hasSubRecipes = subRecipes && subRecipes?.length > 0;
|
||||
const recipeParameters = options?.recipeParameters;
|
||||
|
||||
// Determine the system prompt
|
||||
let prompt = desktopPrompt;
|
||||
if (!hasParameters && recipe_instructions) {
|
||||
prompt = `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${recipe_instructions}`;
|
||||
|
||||
// If we have recipe instructions, add them to the system prompt with parameter substitution
|
||||
if (recipe_instructions) {
|
||||
const substitutedInstructions = recipeParameters
|
||||
? substituteParameters(recipe_instructions, recipeParameters)
|
||||
: recipe_instructions;
|
||||
|
||||
prompt = `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${substitutedInstructions}`;
|
||||
}
|
||||
|
||||
// Extend the system prompt with desktop-specific information
|
||||
await extendPrompt({
|
||||
body: {
|
||||
@@ -235,9 +252,27 @@ export const initializeSystem = async (
|
||||
},
|
||||
});
|
||||
|
||||
if (!hasParameters && hasSubRecipes) {
|
||||
await addSubRecipesToAgent(sessionId, subRecipes);
|
||||
if (hasSubRecipes) {
|
||||
let finalSubRecipes = subRecipes;
|
||||
|
||||
// If we have parameters, substitute them in sub-recipe values
|
||||
if (recipeParameters) {
|
||||
finalSubRecipes = subRecipes.map((subRecipe) => ({
|
||||
...subRecipe,
|
||||
values: subRecipe.values
|
||||
? Object.fromEntries(
|
||||
Object.entries(subRecipe.values).map(([key, value]) => [
|
||||
key,
|
||||
substituteParameters(value, recipeParameters),
|
||||
])
|
||||
)
|
||||
: subRecipe.values,
|
||||
}));
|
||||
}
|
||||
|
||||
await addSubRecipesToAgent(sessionId, finalSubRecipes);
|
||||
}
|
||||
|
||||
// Configure session with response config if present
|
||||
if (responseConfig?.json_schema) {
|
||||
const sessionConfigResponse = await updateSessionConfig({
|
||||
|
||||
@@ -3,9 +3,9 @@ import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import crypto from 'crypto';
|
||||
|
||||
function calculateRecipeHash(recipeConfig: unknown): string {
|
||||
function calculateRecipeHash(recipe: unknown): string {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(JSON.stringify(recipeConfig));
|
||||
hash.update(JSON.stringify(recipe));
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ async function getRecipeHashesDir(): Promise<string> {
|
||||
return hashesDir;
|
||||
}
|
||||
|
||||
ipcMain.handle('has-accepted-recipe-before', async (_event, recipeConfig) => {
|
||||
const hash = calculateRecipeHash(recipeConfig);
|
||||
ipcMain.handle('has-accepted-recipe-before', async (_event, recipe) => {
|
||||
const hash = calculateRecipeHash(recipe);
|
||||
const hashFile = path.join(await getRecipeHashesDir(), `${hash}.hash`);
|
||||
try {
|
||||
await fs.access(hashFile);
|
||||
@@ -30,8 +30,8 @@ ipcMain.handle('has-accepted-recipe-before', async (_event, recipeConfig) => {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('record-recipe-hash', async (_event, recipeConfig) => {
|
||||
const hash = calculateRecipeHash(recipeConfig);
|
||||
ipcMain.handle('record-recipe-hash', async (_event, recipe) => {
|
||||
const hash = calculateRecipeHash(recipe);
|
||||
const filePath = path.join(await getRecipeHashesDir(), `${hash}.hash`);
|
||||
const timestamp = new Date().toISOString();
|
||||
await fs.writeFile(filePath, timestamp);
|
||||
|
||||
Reference in New Issue
Block a user