-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Add in ability to delete a single output by prompt name in editor #1465
Feat: Add in ability to delete a single output by prompt name in editor #1465
Conversation
8ae319f
to
0c0f78d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great overall!! Nice first diff 😃 -- just one suggestion about the button moved to https://github.com/lastmile-ai/aiconfig/blob/main/python/src/aiconfig/editor/client/src/components/prompt/PromptMenuButton.tsx
<PromptOutputBar /> | ||
{!readOnly && prompt.outputs?.length && deleteOutput ? ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding the button here, can we add it to the prompt menu?
I imagine we will have other capabilities like Copy/Duplicate prompt which will also be added in the future, so it will prevent too many buttons everywhere
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ship it! 🚀🚀🚀
python/src/aiconfig/editor/client/src/components/prompt/PromptMenuButton.tsx
Outdated
Show resolved
Hide resolved
@@ -66,8 +68,10 @@ export default memo(function PromptsContainer(props: Props) { | |||
<Flex mt="md"> | |||
{!readOnly && ( | |||
<PromptMenuButton | |||
prompt={prompt} | |||
promptId={prompt._ui.id} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we don't need to pass in promptId
anymore if passing in prompt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a comment above -- we shouldn't pass prompt and we can actually just pass callbacks (and a new showDeleteOutput
or canDeleteOutput
prop)
if (prompt._ui.id === action.id) { | ||
return { | ||
...prompt, | ||
outputs: undefined, | ||
}; | ||
} else { | ||
return prompt; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a utils reducer method called reduceReplacePrompt()
. Would prefer to use that to keep things cleaner and more abstracted
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, it will handle selecting the correct prompt by id
const deleteOutput = useCallback( | ||
async () => await onDeleteOutput(promptId), | ||
[promptId, onDeleteOutput] | ||
); | ||
|
||
const deletePrompt = useCallback( | ||
async () => await onDeletePrompt(promptId), | ||
[promptId, onDeletePrompt] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a front-end thing that Ryan taught me. Since PromptMenuButton
is already wrapped with a memo
component, it's actually more overhead for us to wrap these sub-functions in a useCallback()
hook. Here useCallback()
checks for changes to any dependencies, but in our case that's simply changes to the props, which is already memoized in the PromptMenuButton
component itself, so in this case it adds overhead. Would be better to either:
- Keep inlined
- Keep separate but don't wrap with
useCallback()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh just noticed as well but we should just construct the onDeletePrompt
and onDeleteOutput
callbacks in the parent, since the promptId is known there. Then promptId
wouldn't even need to be passed, just the callbacks. Similarly that would make PromptMenuButton memoization actually work as intended since the inline function currently prevents memoization from working (new function prop each time). So in PromptsContainer
we could have
const promptId = prompt._ui.id;
const onDeletePrompt = props.onDeletePrompt;
const onDeleteOutput = props.onDeleteOutput;
const deletePrompt = useCallback(() => onDeletePrompt(promptId), [onDeletePrompt, promptId]);
const deleteOutput = useCallback(() => onDeleteOutput(promptId), [onDeleteOutput, promptId]);
And they can be passed through the props to PromptMenuButton
<Menu.Item | ||
icon={<IconEraser size={16} />} | ||
color="white" | ||
onClick={deleteOutput} | ||
> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @rholinshead for whether this would work with light mode theming
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I think we can remove color here and it will have the correct color we want. To test this, you can set mode="gradio"
and themeMode="dark"
and themeMode="light"
props for AIConfigEditor in the LocalEditor
component
@@ -811,9 +812,32 @@ def _op( | |||
|
|||
signature: dict[str, Type[Any]] = {} | |||
return run_aiconfig_operation_with_request_json( | |||
aiconfig, request_json, f"method_", _op, signature | |||
aiconfig, request_json, f"method_{method_name}", _op, signature |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually upon further inspection, I think this should just be method_name
, not f"method_{method_name}"
, don't think we did this right the first time:
aiconfig/python/src/aiconfig/editor/server/server_utils.py
Lines 501 to 503 in f0f0522
return run_aiconfig_operation_with_op_args( | |
aiconfig, operation_name, operation, Ok(op_args_ok) | |
) |
).to_flask_format() | ||
|
||
signature: dict[str, Type[Any]] = {"prompt_name": str} | ||
operation = make_op_run_method(method_name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh cool, TIL that we already have existing API for this:
aiconfig/python/src/aiconfig/schema.py
Line 1005 in f0f0522
def delete_output(self, prompt_name: str): |
existing_outputs = prompt.outputs
. cc @Ankush-lastmile do you remember why this line might have been needed?
""" | ||
Clears a single outputs in the server state's AIConfig based on prompt name. | ||
""" | ||
method_name = MethodName("delete_output") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Based on comment above, we probably don't need to inline this since it's only used once in operation = ...
line, but not a blocker
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides the useCallbacks
and potential light mode issue, there are no functional blockers I see on my end. I'm happy to unblock and land for now and we can fix lingering nits in a followup! Let me know whether you prefer to fix these small things in this PR (will wait for you) or do a followup (will merge this for now)
Btw for failing blockers (on autoformatting), you can use either of these commands to reformat and get unblocked (I think, not 100% sure):
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks great, thanks! Just requesting changes for some small but important changes in the comments
@@ -190,7 +190,9 @@ export default memo(function PromptContainer({ | |||
|
|||
{prompt.outputs && prompt.outputs.length > 0 && ( | |||
<> | |||
<Flex justify="space-between" direction="column"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this look like / is this needed now that the button is moved to the menu?
onDeletePrompt, | ||
}: { | ||
promptId: string; | ||
prompt: ClientPrompt; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing prompt
through just to access the output length will mean this component is re-rendered any time any part of the prompt changes (e.g. if typing in the input). That will result in the React memo constantly re-calculating the diff to check if it should re-render, and also re-rendering unnecessarily. Instead, it would be better to have a prop showDeleteOutput
which is set to true if the prompt.outputs.length > 0 in the parent component
const deleteOutput = useCallback( | ||
async () => await onDeleteOutput(promptId), | ||
[promptId, onDeleteOutput] | ||
); | ||
|
||
const deletePrompt = useCallback( | ||
async () => await onDeletePrompt(promptId), | ||
[promptId, onDeletePrompt] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh just noticed as well but we should just construct the onDeletePrompt
and onDeleteOutput
callbacks in the parent, since the promptId is known there. Then promptId
wouldn't even need to be passed, just the callbacks. Similarly that would make PromptMenuButton memoization actually work as intended since the inline function currently prevents memoization from working (new function prop each time). So in PromptsContainer
we could have
const promptId = prompt._ui.id;
const onDeletePrompt = props.onDeletePrompt;
const onDeleteOutput = props.onDeleteOutput;
const deletePrompt = useCallback(() => onDeletePrompt(promptId), [onDeletePrompt, promptId]);
const deleteOutput = useCallback(() => onDeleteOutput(promptId), [onDeleteOutput, promptId]);
And they can be passed through the props to PromptMenuButton
<Menu.Item | ||
icon={<IconEraser size={16} />} | ||
color="white" | ||
onClick={deleteOutput} | ||
> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I think we can remove color here and it will have the correct color we want. To test this, you can set mode="gradio"
and themeMode="dark"
and themeMode="light"
props for AIConfigEditor in the LocalEditor
component
@@ -34,6 +35,7 @@ type Props = { | |||
) => Promise<void>; | |||
prompts: ClientPrompt[]; | |||
runningPromptId?: string; | |||
readOnly?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed as unused, since it's already obtained from context.
@@ -66,8 +68,10 @@ export default memo(function PromptsContainer(props: Props) { | |||
<Flex mt="md"> | |||
{!readOnly && ( | |||
<PromptMenuButton | |||
prompt={prompt} | |||
promptId={prompt._ui.id} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a comment above -- we shouldn't pass prompt and we can actually just pass callbacks (and a new showDeleteOutput
or canDeleteOutput
prop)
if (prompt._ui.id === action.id) { | ||
return { | ||
...prompt, | ||
outputs: undefined, | ||
}; | ||
} else { | ||
return prompt; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, it will handle selecting the correct prompt by id
…r cleanliness and reusability
<> | ||
{!readOnly && ( | ||
<PromptMenuButton | ||
showDeleteOutput={!!prompt.outputs?.length} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: feel prompt.outputs?.length > 0
is easier to read and I don't really like JS implicitly "falsey-ness" but don't really care
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it actually complains if you use this syntax unelss you do a (prompt.outputs?.length || 0) > 0
it seems, so thats why i kept it with this way
{!readOnly && ( | ||
<PromptMenuButton | ||
promptId={prompt._ui.id} | ||
onDeletePrompt={() => props.onDeletePrompt(prompt._ui.id)} | ||
/> | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
omg this had me confused so hard wondering wtf was going on, but this is the PromptsContainer
(with an s
), not PromptContainer
:S
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this change, I feel it makes sense to put with the individual prompt component anyways
onDeletePrompt={props.onDeletePrompt} | ||
onDeleteOutput={props.onDeleteOutput} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
@@ -66,6 +66,7 @@ export function aiConfigToClientConfig(aiconfig: AIConfig): ClientAIConfig { | |||
export type LogEvent = | |||
| "ADD_PROMPT" | |||
| "CLEAR_OUTPUTS" | |||
| "DELETE_OUTPUT" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Would like to use wording "CLEAR_OUTPUT" to match with our UI, but again not a blocker or a big deal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think maybe as a bigger refactor of the api? i made it clear output at first but then found that all the apis reference delete output so wanted to stay consistent there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I think this is great! Will let @rholinshead do a final pass before merging!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice - thanks!
Ah, looks like the formatting lint is still failing. I'll override to merge to unblock and then I can do a quick PR with the formatting. I'd recommend setting up black formatter extension in vs code (if you use it) so that it formats automatically on save instead of needing the command |
#1474) # Remove Unused os Import + Run Black Format + Add Missing Dep & Rebuild Following up with minor changes from #1465 (comment) Mainly, removed unused `os` import and save `server.py` so that black formatter runs to pass the lint. While testing, noticed lint in `LocalEditor` for missing dep so added that as well. Just for good measure, rebuilt the client. Test Plan: - Clear output works - Lint passes on PR
#1451

Adds the ability to delete a single output in the config editor