Remove Create with Text-to-CAD from toolbar, make commands in command palette more distinct (#7048)

* Remove Create with Text-to-CAD from the toolbar

* Remove "prompt-to-edit" wording, call commands "create" and "edit"

* Use sparkles for the ML feature, not chat

* lints

* Start fixing up tests, there are probably more though

* Fix up a few more tests

* Fix up prompt-to-edit tests (yay using fixtures!)

* Fix native file menu tests

* Update snapshots

* Fix menu test

* Fix snaps

---------

Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
This commit is contained in:
Frank Noirot
2025-05-18 06:24:48 -04:00
committed by GitHub
parent 3168c22de7
commit 5734cc7fc3
23 changed files with 177 additions and 92 deletions

View File

@ -146,9 +146,7 @@ export class CmdBarFixture {
await this.cmdBarOpenBtn.click()
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
if (selectCmd === 'promptToEdit') {
const promptEditCommand = this.page.getByText(
'Use Zoo AI to edit your parts and code.'
)
const promptEditCommand = this.selectOption({ name: 'Text-to-CAD Edit' })
await expect(promptEditCommand.first()).toBeVisible()
await promptEditCommand.first().scrollIntoViewIfNeeded()
await promptEditCommand.first().click()

View File

@ -121,11 +121,13 @@ export class HomePageFixture {
await projectCard.click()
}
goToModelingScene = async (name: string = 'testDefault') => {
/** Returns the project name in case caller has used the default and needs it */
goToModelingScene = async (name = 'testDefault') => {
// On web this is a no-op. There is no project view.
if (process.env.PLATFORM === 'web') return
if (process.env.PLATFORM === 'web') return ''
await this.createAndGoToProject(name)
return name
}
isNativeFileMenuCreated = async () => {

View File

@ -252,7 +252,7 @@ test.describe(
tronApp,
'Edit.Modify with Zoo Text-To-CAD'
)
await cmdBar.expectCommandName('Prompt-to-edit')
await cmdBar.expectCommandName('Text-to-CAD Edit')
})
await test.step('Modeling.Edit.Edit parameter', async () => {
await page.waitForTimeout(250)
@ -518,7 +518,7 @@ test.describe(
'Design.Create with Zoo Text-To-CAD'
)
await cmdBar.toBeOpened()
await cmdBar.expectCommandName('Text to CAD')
await cmdBar.expectCommandName('Text-to-CAD Create')
})
await test.step('Modeling.Design.Modify with Zoo Text-To-CAD', async () => {
@ -528,7 +528,7 @@ test.describe(
'Design.Modify with Zoo Text-To-CAD'
)
await cmdBar.toBeOpened()
await cmdBar.expectCommandName('Prompt-to-edit')
await cmdBar.expectCommandName('Text-to-CAD Edit')
})
await test.step('Modeling.Help.KCL code samples', async () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -4,9 +4,10 @@ import type { Page } from '@playwright/test'
import { createProject, getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
test.describe('Text-to-CAD tests', () => {
test('basic lego happy case', async ({ page, homePage }) => {
test('basic lego happy case', async ({ page, homePage, cmdBar }) => {
const u = await getUtils(page)
await test.step('Set up', async () => {
@ -15,7 +16,11 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad()
})
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
// Find the toast.
// Look out for the toast message
@ -56,6 +61,7 @@ test.describe('Text-to-CAD tests', () => {
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -64,7 +70,11 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x6 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x6 lego',
cmdBar
)
// Find the toast.
// Look out for the toast message
@ -82,7 +92,11 @@ test.describe('Text-to-CAD tests', () => {
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
// Can send a new prompt from the command bar.
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
// Find the toast.
// Look out for the toast message
@ -100,6 +114,7 @@ test.describe('Text-to-CAD tests', () => {
test('you can reject text-to-cad output and it does nothing', async ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -108,7 +123,11 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
// Find the toast.
// Look out for the toast message
@ -141,6 +160,7 @@ test.describe('Text-to-CAD tests', () => {
test('sending a bad prompt fails, can dismiss', async ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -150,7 +170,11 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad()
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
await sendPromptFromCommandBarTriggeredByButton(page, randomPrompt)
await sendPromptFromCommandBarAndSetExistingProject(
page,
randomPrompt,
cmdBar
)
// Find the toast.
// Look out for the toast message
@ -188,6 +212,7 @@ test.describe('Text-to-CAD tests', () => {
test('sending a bad prompt fails, can start over from toast', async ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -197,7 +222,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad()
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
// Find the toast.
// Look out for the toast message
@ -256,6 +281,7 @@ test.describe('Text-to-CAD tests', () => {
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -265,7 +291,7 @@ test.describe('Text-to-CAD tests', () => {
await u.waitForPageLoad()
const badPrompt = 'akjsndladflajbhflauweyf15;'
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
// Find the toast.
// Look out for the toast message
@ -292,7 +318,11 @@ test.describe('Text-to-CAD tests', () => {
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
// They should be able to try again from the command bar.
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
// Find the toast.
// Look out for the toast message
@ -310,17 +340,40 @@ test.describe('Text-to-CAD tests', () => {
test('ensure you can shift+enter in the prompt box', async ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
const projectName = await homePage.goToModelingScene()
await u.waitForPageLoad()
const promptWithNewline = `a 2x4\nlego`
await page.getByTestId('text-to-cad').click()
await test.step('Get to the prompt step to test', async () => {
await cmdBar.openCmdBar()
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
await cmdBar.currentArgumentInput.fill('existing')
await cmdBar.progressCmdBar()
await cmdBar.currentArgumentInput.fill(projectName)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Text-to-CAD Create',
stage: 'arguments',
currentArgKey: 'prompt',
currentArgValue: '',
highlightedHeaderArg: 'prompt',
headerArguments: {
Method: 'Existing project',
ProjectName: projectName,
Prompt: '',
},
})
})
// Type the prompt.
await page.keyboard.type('a 2x4')
@ -354,6 +407,7 @@ test.describe('Text-to-CAD tests', () => {
test('can do many at once and get many prompts back, and interact with many', async ({
page,
homePage,
cmdBar,
}) => {
// Let this test run longer since we've seen it timeout.
test.setTimeout(180_000)
@ -365,11 +419,23 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x8 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x8 lego',
cmdBar
)
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x10 lego')
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x10 lego',
cmdBar
)
// Find the toast.
// Look out for the toast message
@ -440,6 +506,7 @@ test.describe('Text-to-CAD tests', () => {
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
page,
homePage,
cmdBar,
}) => {
const u = await getUtils(page)
@ -448,11 +515,16 @@ test.describe('Text-to-CAD tests', () => {
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
await sendPromptFromCommandBarTriggeredByButton(
await sendPromptFromCommandBarAndSetExistingProject(
page,
'alkjsdnlajshdbfjlhsbdf a;askjdnf'
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarAndSetExistingProject(
page,
'alkjsdnlajshdbfjlhsbdf a;askjdnf',
cmdBar
)
// Find the toast.
@ -526,7 +598,9 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
const cmdSearchBar = page.getByPlaceholder('Search commands')
await expect(cmdSearchBar).toBeVisible()
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
const textToCadCommand = page.getByRole('option', {
name: 'Text-to-CAD Create',
})
await expect(textToCadCommand.first()).toBeVisible()
// Click the Text-to-CAD command
await textToCadCommand.first().scrollIntoViewIfNeeded()
@ -544,29 +618,67 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
})
}
async function sendPromptFromCommandBarTriggeredByButton(
async function sendPromptFromCommandBarAndSetExistingProject(
page: Page,
promptStr: string
promptStr: string,
cmdBar: CmdBarFixture,
projectName = 'testDefault'
) {
await page.waitForTimeout(1000)
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
await page.getByTestId('text-to-cad').click()
await cmdBar.openCmdBar()
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
// Enter the prompt.
const prompt = page.getByRole('textbox', { name: 'Prompt' })
await expect(prompt.first()).toBeVisible()
await cmdBar.expectState({
commandName: 'Text-to-CAD Create',
stage: 'arguments',
currentArgKey: 'method',
currentArgValue: '',
highlightedHeaderArg: 'method',
headerArguments: {
Method: '',
Prompt: '',
},
})
await cmdBar.currentArgumentInput.fill('existing')
await cmdBar.progressCmdBar()
// Type the prompt.
await page.keyboard.type(promptStr)
await page.waitForTimeout(200)
await page.keyboard.press('Enter')
await cmdBar.expectState({
commandName: 'Text-to-CAD Create',
stage: 'arguments',
currentArgKey: 'projectName',
currentArgValue: '',
highlightedHeaderArg: 'projectName',
headerArguments: {
Method: 'Existing project',
ProjectName: '',
Prompt: '',
},
})
await cmdBar.currentArgumentInput.fill(projectName)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Text-to-CAD Create',
stage: 'arguments',
currentArgKey: 'prompt',
currentArgValue: '',
highlightedHeaderArg: 'prompt',
headerArguments: {
Method: 'Existing project',
ProjectName: projectName,
Prompt: '',
},
})
await cmdBar.currentArgumentInput.fill(promptStr)
await cmdBar.progressCmdBar()
})
}
test(
'Text-to-CAD functionality',
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
async ({ context, page, cmdBar }, testInfo) => {
const projectName = 'project-000'
const prompt = 'lego 2x4'
const textToCadFileName = 'lego-2x4.kcl'
@ -603,7 +715,12 @@ test(
await openKclCodePanel()
await test.step(`Test file creation`, async () => {
await sendPromptFromCommandBarTriggeredByButton(page, prompt)
await sendPromptFromCommandBarAndSetExistingProject(
page,
prompt,
cmdBar,
projectName
)
// File is considered created if it shows up in the Project Files pane
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
expect(fileExists()).toBeTruthy()

View File

@ -325,7 +325,7 @@ export function Toolbar({
// A single button
return (
<div
className="relative"
className={`relative ${itemConfig.alwaysDark ? ' dark bg-chalkboard-90 ' : ''}`}
key={itemConfig.id}
// Mouse events do not fire on disabled buttons
onMouseEnter={handleMouseEnter}

View File

@ -110,7 +110,7 @@ export function createApplicationCommands({
const textToCADCommand: Command = {
name: 'Text-to-CAD',
description: 'Generate parts from text prompts.',
displayName: 'Text to CAD',
displayName: 'Text-to-CAD Create',
groupId: 'application',
needsReview: false,
status: IS_ML_EXPERIMENTAL ? 'experimental' : 'active',

View File

@ -939,8 +939,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
},
'Prompt-to-edit': {
description: 'Use Zoo AI to edit your parts and code.',
icon: 'chat',
displayName: 'Text-to-CAD Edit',
description:
'Use machine learning to edit your parts and code from a text prompt.',
icon: 'sparkles',
status: IS_ML_EXPERIMENTAL ? 'experimental' : 'active',
args: {
selection: {

View File

@ -1,5 +1,4 @@
import type { EventFrom, StateFrom } from 'xstate'
import { settingsActor } from '@src/lib/singletons'
import type { CustomIconName } from '@src/components/CustomIcon'
import { createLiteral } from '@src/lang/create'
@ -35,6 +34,7 @@ export type ToolbarItem = {
id: string
onClick: (props: ToolbarItemCallbackProps) => void
icon?: CustomIconName
className?: string
iconColor?: string
alwaysDark?: true
status: 'available' | 'unavailable' | 'kcl-only' | 'experimental'
@ -424,39 +424,6 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
],
},
'break',
{
id: 'ai',
array: [
{
id: 'text-to-cad',
onClick: () => {
const currentProject =
settingsActor.getSnapshot().context.currentProject
commandBarActor.send({
type: 'Find and select command',
data: {
name: 'Text-to-CAD',
groupId: 'application',
argDefaultValues: {
method: 'existingProject',
projectName: currentProject?.name,
},
},
})
},
icon: 'sparkles',
iconColor: '#29FFA4',
alwaysDark: true,
status: IS_ML_EXPERIMENTAL ? 'experimental' : 'available',
title: 'Create with Zoo Text-to-CAD',
description: 'Create geometry with AI / ML.',
links: [
{
label: 'API docs',
url: 'https://zoo.dev/docs/api/ml/generate-a-cad-model-from-text',
},
],
},
{
id: 'prompt-to-edit',
onClick: () =>
@ -468,14 +435,13 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
iconColor: '#29FFA4',
alwaysDark: true,
status: IS_ML_EXPERIMENTAL ? 'experimental' : 'available',
title: 'Modify with Zoo Text-to-CAD',
description: 'Edit geometry with AI / ML.',
title: 'Text-to-CAD',
description:
'Edit or create geometry from a text prompt and selection.',
links: [],
},
],
},
],
},
sketching: {
check: (state) =>
state.matches('Sketch') ||