Make Modify with Text-to-CAD selection arg optional by honoring skip: false
on non-required args (#6992)
* Make "skip = false" non-required args appear in header * Make non-required, unskippable selection args work * Make prompt-to-edit's selection arg optional but non-skippable * Update src/components/CommandBar/CommandBarSelectionInput.tsx Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> * Fix dumb logic bug Thanks for user testing @Irev-dev * Update mixed input to show selection Feel free to revert @Irev-Dev if this is the wrong move, but I found it odd that this component doesn't show the current selection in the text like the other selection input does, so I copied that over. * Merge branch 'main' into franknoirot/adhoc/optional-selection-args * Merge branch 'main' into franknoirot/adhoc/optional-selection-args * Merge remote-tracking branch 'origin' into franknoirot/adhoc/optional-selection-args * fix tests * change copy again * Update src/components/CommandBar/CommandBarSelectionMixedInput.tsx Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
This commit is contained in:
@ -99,6 +99,8 @@ test.describe('edit with AI example snapshots', () => {
|
||||
await test.step('fire off edit prompt', async () => {
|
||||
await cmdBar.captureTextToCadRequestSnapshot(test.info())
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
// being specific about the color with a hex means asserting pixel color is more stable
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
|
@ -88,6 +88,8 @@ test.describe('Prompt-to-edit tests', () => {
|
||||
|
||||
await test.step('fire off edit prompt', async () => {
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
// being specific about the color with a hex means asserting pixel color is more stable
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
@ -165,6 +167,8 @@ test.describe('Prompt-to-edit tests', () => {
|
||||
|
||||
await test.step('fire of bad prompt', async () => {
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.fill('ansheusha asnthuatshoeuhtaoetuhthaeu laughs in dvorak')
|
||||
|
@ -102,10 +102,12 @@ function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
||||
)}
|
||||
</p>
|
||||
{Object.entries(nonHiddenArgs || {})
|
||||
.filter(([_, argConfig]) =>
|
||||
typeof argConfig.required === 'function'
|
||||
? argConfig.required(commandBarState.context)
|
||||
: argConfig.required
|
||||
.filter(
|
||||
([_, argConfig]) =>
|
||||
argConfig.skip === false ||
|
||||
(typeof argConfig.required === 'function'
|
||||
? argConfig.required(commandBarState.context)
|
||||
: argConfig.required)
|
||||
)
|
||||
.map(([argName, arg], i) => {
|
||||
const argValue =
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
canSubmitSelectionArg,
|
||||
getSelectionCountByType,
|
||||
getSelectionTypeDisplayText,
|
||||
type Selections,
|
||||
} from '@src/lib/selections'
|
||||
import { engineCommandManager, kclManager } from '@src/lib/singletons'
|
||||
import { reportRejection } from '@src/lib/trap'
|
||||
@ -56,9 +57,13 @@ function CommandBarSelectionInput({
|
||||
const selectionsByType = useMemo(() => {
|
||||
return getSelectionCountByType(selection)
|
||||
}, [selection])
|
||||
const isArgRequired =
|
||||
arg.required instanceof Function
|
||||
? arg.required(commandBarState.context)
|
||||
: arg.required
|
||||
const canSubmitSelection = useMemo<boolean>(
|
||||
() => canSubmitSelectionArg(selectionsByType, arg),
|
||||
[selectionsByType]
|
||||
() => !isArgRequired || canSubmitSelectionArg(selectionsByType, arg),
|
||||
[selectionsByType, arg, isArgRequired]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@ -110,7 +115,18 @@ function CommandBarSelectionInput({
|
||||
return
|
||||
}
|
||||
|
||||
onSubmit(selection)
|
||||
/**
|
||||
* Now that arguments like this can be optional, we need to
|
||||
* construct an empty selection if it's not required to get it past our validation.
|
||||
*/
|
||||
const resolvedSelection: Selections | undefined = isArgRequired
|
||||
? selection
|
||||
: selection || {
|
||||
graphSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
|
||||
onSubmit(resolvedSelection)
|
||||
}
|
||||
|
||||
// Clear selection if needed
|
||||
|
@ -6,6 +6,7 @@ import type { Selections } from '@src/lib/selections'
|
||||
import {
|
||||
canSubmitSelectionArg,
|
||||
getSelectionCountByType,
|
||||
getSelectionTypeDisplayText,
|
||||
} from '@src/lib/selections'
|
||||
import { kclManager } from '@src/lib/singletons'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
@ -30,8 +31,14 @@ export default function CommandBarSelectionMixedInput({
|
||||
const selectionsByType = useMemo(() => {
|
||||
return getSelectionCountByType(selection)
|
||||
}, [selection])
|
||||
const isArgRequired =
|
||||
arg.required instanceof Function
|
||||
? arg.required(commandBarState.context)
|
||||
: arg.required
|
||||
|
||||
const canSubmitSelection = useMemo<boolean>(() => {
|
||||
// Don't do additional checks if this argument is not required
|
||||
if (!isArgRequired) return true
|
||||
if (!selection) return false
|
||||
const isNonZeroRange = selection.graphSelections.some((sel) => {
|
||||
const range = sel.codeRef.range
|
||||
@ -39,7 +46,7 @@ export default function CommandBarSelectionMixedInput({
|
||||
})
|
||||
if (isNonZeroRange) return true
|
||||
return canSubmitSelectionArg(selectionsByType, arg)
|
||||
}, [selectionsByType, selection])
|
||||
}, [selectionsByType, selection, arg, isArgRequired])
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus()
|
||||
@ -76,7 +83,18 @@ export default function CommandBarSelectionMixedInput({
|
||||
return
|
||||
}
|
||||
|
||||
onSubmit(selection)
|
||||
/**
|
||||
* Now that arguments like this can be optional, we need to
|
||||
* construct an empty selection if it's not required to get it past our validation.
|
||||
*/
|
||||
const resolvedSelection: Selections | undefined = isArgRequired
|
||||
? selection
|
||||
: selection || {
|
||||
graphSelections: [],
|
||||
otherSelections: [],
|
||||
}
|
||||
|
||||
onSubmit(resolvedSelection)
|
||||
}
|
||||
|
||||
const isMixedSelection = arg.inputType === 'selectionMixed'
|
||||
@ -92,9 +110,10 @@ export default function CommandBarSelectionMixedInput({
|
||||
(!hasSubmitted || canSubmitSelection || 'text-destroy-50')
|
||||
}
|
||||
>
|
||||
{canSubmitSelection
|
||||
? 'Select objects in the scene'
|
||||
: 'Select code or objects in the scene'}
|
||||
{canSubmitSelection &&
|
||||
(selection.graphSelections.length || selection.otherSelections.length)
|
||||
? getSelectionTypeDisplayText(selection) + ' selected'
|
||||
: 'Select code/objects, or skip'}
|
||||
|
||||
{showSceneSelection && (
|
||||
<div className="scene-selection mt-2">
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
KCL_DEFAULT_DEGREE,
|
||||
KCL_DEFAULT_LENGTH,
|
||||
KCL_DEFAULT_TRANSFORM,
|
||||
ML_EXPERIMENTAL_MESSAGE,
|
||||
} from '@src/lib/constants'
|
||||
import type { components } from '@src/lib/machine-api'
|
||||
import type { Selections } from '@src/lib/selections'
|
||||
@ -957,12 +958,13 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
'edgeCutEdge',
|
||||
],
|
||||
multiple: true,
|
||||
required: true,
|
||||
required: false,
|
||||
selectionSource: {
|
||||
allowSceneSelection: true,
|
||||
allowCodeSelection: true,
|
||||
},
|
||||
skip: true,
|
||||
skip: false,
|
||||
warningMessage: ML_EXPERIMENTAL_MESSAGE,
|
||||
},
|
||||
prompt: {
|
||||
inputType: 'text',
|
||||
|
@ -146,8 +146,15 @@ export const commandBarMachine = setup({
|
||||
typeof argConfig.required === 'function'
|
||||
? argConfig.required(context)
|
||||
: argConfig.required
|
||||
/**
|
||||
* TODO: we need to think harder about the relationship between
|
||||
* `required`, `skip`, and `hidden`.
|
||||
* This bit of logic essentially makes "skip false" arguments required.
|
||||
* We may need a bit of state to mark an argument as "visited" for "skip false" args
|
||||
* to truly not require any value to continue.
|
||||
*/
|
||||
const mustNotSkipArg =
|
||||
argIsRequired &&
|
||||
(argIsRequired || argConfig.skip === false) &&
|
||||
(!context.argumentsToSubmit.hasOwnProperty(argName) ||
|
||||
context.argumentsToSubmit[argName] === undefined ||
|
||||
(rejectedArg &&
|
||||
|
Reference in New Issue
Block a user