Recipe variables (#5365)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Zane Staggs <zane@squareup.com>
This commit is contained in:
Douwe Osinga
2025-10-28 09:28:47 -04:00
committed by GitHub
parent 1156cbcf9d
commit fb95068e4a
2 changed files with 74 additions and 20 deletions
+26 -19
View File
@@ -23,6 +23,7 @@ import { useCostTracking } from '../hooks/useCostTracking';
import RecipeActivities from './recipes/RecipeActivities'; import RecipeActivities from './recipes/RecipeActivities';
import { useToolCount } from './alerts/useToolCount'; import { useToolCount } from './alerts/useToolCount';
import { getThinkingMessage } from '../types/message'; import { getThinkingMessage } from '../types/message';
import ParameterInputModal from './ParameterInputModal';
interface BaseChatProps { interface BaseChatProps {
setChat: (chat: ChatType) => void; setChat: (chat: ChatType) => void;
@@ -49,7 +50,7 @@ function BaseChatContent({
const disableAnimation = location.state?.disableAnimation || false; const disableAnimation = location.state?.disableAnimation || false;
const [hasStartedUsingRecipe, setHasStartedUsingRecipe] = React.useState(false); const [hasStartedUsingRecipe, setHasStartedUsingRecipe] = React.useState(false);
const [hasAcceptedRecipe, setHasAcceptedRecipe] = useState<boolean>(); const [hasNotAcceptedRecipe, setHasNotAcceptedRecipe] = useState<boolean>();
const [hasRecipeSecurityWarnings, setHasRecipeSecurityWarnings] = useState(false); const [hasRecipeSecurityWarnings, setHasRecipeSecurityWarnings] = useState(false);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
@@ -63,12 +64,19 @@ function BaseChatContent({
const onStreamFinish = useCallback(() => {}, []); const onStreamFinish = useCallback(() => {}, []);
const { session, messages, chatState, handleSubmit, stopStreaming, sessionLoadError } = const {
useChatStream({ session,
sessionId, messages,
onStreamFinish, chatState,
initialMessage, handleSubmit,
}); stopStreaming,
sessionLoadError,
setRecipeUserParams,
} = useChatStream({
sessionId,
onStreamFinish,
initialMessage,
});
const handleFormSubmit = (e: React.FormEvent) => { const handleFormSubmit = (e: React.FormEvent) => {
const customEvent = e as unknown as CustomEvent; const customEvent = e as unknown as CustomEvent;
@@ -95,7 +103,7 @@ function BaseChatContent({
(async () => { (async () => {
const accepted = await window.electron.hasAcceptedRecipeBefore(recipe); const accepted = await window.electron.hasAcceptedRecipeBefore(recipe);
setHasAcceptedRecipe(accepted); setHasNotAcceptedRecipe(!accepted);
if (!accepted) { if (!accepted) {
const scanResult = await scanRecipe(recipe); const scanResult = await scanRecipe(recipe);
@@ -107,7 +115,7 @@ function BaseChatContent({
const handleRecipeAccept = async (accept: boolean) => { const handleRecipeAccept = async (accept: boolean) => {
if (recipe && accept) { if (recipe && accept) {
await window.electron.recordRecipeHash(recipe); await window.electron.recordRecipeHash(recipe);
setHasAcceptedRecipe(true); setHasNotAcceptedRecipe(false);
} else { } else {
setView('chat'); setView('chat');
} }
@@ -283,7 +291,7 @@ function BaseChatContent({
sessionCosts={sessionCosts} sessionCosts={sessionCosts}
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
recipe={recipe} recipe={recipe}
recipeAccepted={hasAcceptedRecipe} recipeAccepted={!hasNotAcceptedRecipe}
initialPrompt={initialPrompt} initialPrompt={initialPrompt}
toolCount={toolCount || 0} toolCount={toolCount || 0}
autoSubmit={false} autoSubmit={false}
@@ -294,7 +302,7 @@ function BaseChatContent({
{recipe && ( {recipe && (
<RecipeWarningModal <RecipeWarningModal
isOpen={!hasAcceptedRecipe} isOpen={!!hasNotAcceptedRecipe}
onConfirm={() => handleRecipeAccept(true)} onConfirm={() => handleRecipeAccept(true)}
onCancel={() => handleRecipeAccept(false)} onCancel={() => handleRecipeAccept(false)}
recipeDetails={{ recipeDetails={{
@@ -306,14 +314,13 @@ function BaseChatContent({
/> />
)} )}
{/*/!* Recipe Parameter Modal *!/*/} {recipe?.parameters && recipe.parameters.length > 0 && !session?.user_recipe_values && (
{/*{isParameterModalOpen && filteredParameters.length > 0 && (*/} <ParameterInputModal
{/* <ParameterInputModal*/} parameters={recipe.parameters}
{/* parameters={filteredParameters}*/} onSubmit={setRecipeUserParams}
{/* onSubmit={handleParameterSubmit}*/} onClose={() => setView('chat')}
{/* onClose={() => setIsParameterModalOpen(false)}*/} />
{/* />*/} )}
{/*)}*/}
{/*/!* Create Recipe from Session Modal *!/*/} {/*/!* Create Recipe from Session Modal *!/*/}
{/*<CreateRecipeFromSessionModal*/} {/*<CreateRecipeFromSessionModal*/}
+48 -1
View File
@@ -1,6 +1,13 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { ChatState } from '../types/chatState'; import { ChatState } from '../types/chatState';
import { Conversation, Message, resumeAgent, Session } from '../api'; import {
Conversation,
Message,
resumeAgent,
Session,
updateFromSession,
updateSessionUserRecipeValues,
} from '../api';
import { getApiUrl } from '../config'; import { getApiUrl } from '../config';
import { createUserMessage, getCompactingMessage, getThinkingMessage } from '../types/message'; import { createUserMessage, getCompactingMessage, getThinkingMessage } from '../types/message';
@@ -71,6 +78,7 @@ interface UseChatStreamReturn {
messages: Message[]; messages: Message[];
chatState: ChatState; chatState: ChatState;
handleSubmit: (userMessage: string) => Promise<void>; handleSubmit: (userMessage: string) => Promise<void>;
setRecipeUserParams: (values: Record<string, string>) => Promise<void>;
stopStreaming: () => void; stopStreaming: () => void;
sessionLoadError?: string; sessionLoadError?: string;
} }
@@ -370,6 +378,44 @@ export function useChatStream({
[sessionId, setMessagesAndLog, onFinish] [sessionId, setMessagesAndLog, onFinish]
); );
const setRecipeUserParams = useCallback(
async (user_recipe_values: Record<string, string>) => {
if (session) {
await updateSessionUserRecipeValues({
path: {
session_id: sessionId,
},
body: {
userRecipeValues: user_recipe_values,
},
throwOnError: true,
});
// TODO(Douwe): get this from the server instead of emulating it here
setSession({
...session,
user_recipe_values,
});
} else {
setSessionLoadError("can't call setRecipeParams without a session");
}
},
[sessionId, session, setSessionLoadError]
);
useEffect(() => {
// This should happen on the server when the session is loaded or changed
// use session.id to support changing of sessions rather than depending on the
// stable sessionId.
if (session) {
updateFromSession({
body: {
session_id: session.id,
},
throwOnError: true,
});
}
}, [session]);
useEffect(() => { useEffect(() => {
if (initialMessage && session && messages.length === 0 && chatState === ChatState.Idle) { if (initialMessage && session && messages.length === 0 && chatState === ChatState.Idle) {
log.messages('auto-submit-initial', 0, { initialMessage: initialMessage.slice(0, 50) }); log.messages('auto-submit-initial', 0, { initialMessage: initialMessage.slice(0, 50) });
@@ -397,5 +443,6 @@ export function useChatStream({
chatState, chatState,
handleSubmit, handleSubmit,
stopStreaming, stopStreaming,
setRecipeUserParams,
}; };
} }