[Feature]: Enable Text-to-CAD at the application level (#6501)
* chore: saving off skeleton * fix: saving skeleton * chore: skeleton for loading projects from project directory path * chore: cleaning up useless state transition to be an on event direct to action state * fix: new structure for web vs desktop vs react machine provider code * chore: saving off skeleton * fix: skeleton logic for react? going to move it from a string to obj.string * fix: trying to prevent error element unmount on global react components. This is bricking JS state * fix: we are so back * chore: implemented navigating to specfic KCL file * chore: implementing renaming project * chore: deleting project * fix: auto fixes * fix: old debug/testing file oops * chore: generic create new file * chore: skeleton for web create file provide * chore: basic machine vitest... need to figure out how to get window.electron implemented in vitest? * chore: save off progress before deleting other project implementation, a few missing features still * chore: trying a different init skeleton? most likely will migrate * chore: first attempt of purging projects context provider * chore: enabling toast for some machine state * chore: enabling more toast success and error * chore: writing read write state to the system io based on the project path * fix: tsc fixes * fix: use file system watcher, navigate to project after creation via the requestProjectName * chore: open project command, hooks vs snapshot context helpers * chore: implemented open and create project for e2e testing. They are hard coded in poor spot for now. * fix: codespell fixes * chore: implementing more project commands * chore: PR improvements for root.tsx * chore: leaving comment about new Router.tsx layout * fix: removing debugging code * fix: rewriting component for readability * fix: improving web initialization * chore: implementing import file from url which is not actually that? * fix: clearing search params on import file from url * fix: fixed two e2e tests, forgot needsReview when making new command * fix: fixing some import from url business logic to pass e2e tests * chore: script for diffing circular deps +/- * fix: formatting * fix: massive fix for circular depsga! * fix: trying to fix some errors and auto fmt * fix: updating deps * fix: removing debugging code * fix: big clean up * fix: more deletion * fix: tsc cleanup * fix: TSC TSC TSC TSC! * fix: typo fix * fix: clear query params on web only, desktop not required * fix: removing unused code * fmt * Bring back `trap` removed in merge * Use explicit types instead of `any`s on arg configs * Add project commands directly to command palette * fix: deleting debugging code, from PR review * fix: this got added back(?) * fix: using referred type * fix: more PR clean up * fix: big block comment for xstate architecture decision * fix: more pr comment fixes * fix: saving off logic, need a big cleanup because I hacked it together to get a POC * fix: extra business? * fix: merge conflict just added them back why dude * fix: more PR comments * fix: big ciruclar deps fix, commandBarActor in appActor * chore: writing e2e test, still need to fix 3 bugs * chore: adding more scenarios * fix: formatting * fix: fixing tsc errors * chore: deleting the old text to cad and using the new application level one, almost there * fix: prompt to edit works * fix: large push to get 1 text to cad command... the usage is a little buggy with delete and navigate within /file * fix: settings for highlight edges now works * chore: adding another e2e test * fix: cleaning up e2e tests and writing more of them * fix: tsc type * chore: more e2e improvements, unique project name in text to cad * chore: e2e tests should be good to go * fix: gotcha comment * fix: enabled web t2c, codespell fixes * fix: fixing merge conflcits?? * fix: t2c is back * Remove spaces in command bar test * fmt --------- Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com> Co-authored-by: lee-at-zoo-corp <lee@zoo.dev>
This commit is contained in:
@ -83,7 +83,7 @@ test.describe('Command bar tests', () => {
|
|||||||
await page.keyboard.press('Enter') // submit
|
await page.keyboard.press('Enter') // submit
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-activeLine')).toContainText(
|
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||||
`fillet( radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] )`
|
`fillet(radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01])`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -411,6 +411,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
closeFilePanel: () => closeFilePanel(page),
|
closeFilePanel: () => closeFilePanel(page),
|
||||||
openVariablesPane: () => openVariablesPane(page),
|
openVariablesPane: () => openVariablesPane(page),
|
||||||
openLogsPane: () => openLogsPane(page),
|
openLogsPane: () => openLogsPane(page),
|
||||||
|
goToHomePageFromModeling: () => goToHomePageFromModeling(page),
|
||||||
openAndClearDebugPanel: () => openAndClearDebugPanel(page),
|
openAndClearDebugPanel: () => openAndClearDebugPanel(page),
|
||||||
clearAndCloseDebugPanel: async () => {
|
clearAndCloseDebugPanel: async () => {
|
||||||
await clearCommandLogs(page)
|
await clearCommandLogs(page)
|
||||||
@ -1011,6 +1012,11 @@ export async function createProject({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function goToHomePageFromModeling(page: Page) {
|
||||||
|
await page.getByTestId('app-logo').click()
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
export function executorInputPath(fileName: string): string {
|
export function executorInputPath(fileName: string): string {
|
||||||
return path.join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName)
|
return path.join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName)
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -64,7 +64,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x6 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x6 lego')
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -82,7 +82,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
// Can send a new prompt from the command bar.
|
// Can send a new prompt from the command bar.
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -108,7 +108,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -149,29 +149,8 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
|
||||||
await expect(commandBarButton).toBeVisible()
|
|
||||||
// Click the command bar button
|
|
||||||
await commandBarButton.click()
|
|
||||||
|
|
||||||
// Wait for the command bar to appear
|
|
||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
|
||||||
|
|
||||||
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
|
||||||
// Click the Text-to-CAD command
|
|
||||||
await textToCadCommand.first().click()
|
|
||||||
|
|
||||||
// Enter the prompt.
|
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
|
||||||
await expect(prompt.first()).toBeVisible()
|
|
||||||
|
|
||||||
// Type the prompt.
|
|
||||||
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
||||||
await page.keyboard.type(randomPrompt)
|
await sendPromptFromCommandBarTriggeredByButton(page, randomPrompt)
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -217,30 +196,8 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
|
||||||
await expect(commandBarButton).toBeVisible()
|
|
||||||
// Click the command bar button
|
|
||||||
await commandBarButton.click()
|
|
||||||
|
|
||||||
// Wait for the command bar to appear
|
|
||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
|
||||||
|
|
||||||
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
|
||||||
// Click the Text-to-CAD command
|
|
||||||
await textToCadCommand.first().click()
|
|
||||||
|
|
||||||
// Enter the prompt.
|
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
|
||||||
await expect(prompt.first()).toBeVisible()
|
|
||||||
|
|
||||||
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
||||||
|
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
||||||
// Type the prompt.
|
|
||||||
await page.keyboard.type(badPrompt)
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -307,30 +264,8 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
|
||||||
await expect(commandBarButton).toBeVisible()
|
|
||||||
// Click the command bar button
|
|
||||||
await commandBarButton.click()
|
|
||||||
|
|
||||||
// Wait for the command bar to appear
|
|
||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
|
||||||
|
|
||||||
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
|
||||||
// Click the Text-to-CAD command
|
|
||||||
await textToCadCommand.first().click()
|
|
||||||
|
|
||||||
// Enter the prompt.
|
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
|
||||||
await expect(prompt.first()).toBeVisible()
|
|
||||||
|
|
||||||
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
||||||
|
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
||||||
// Type the prompt.
|
|
||||||
await page.keyboard.type(badPrompt)
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -357,7 +292,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||||
|
|
||||||
// They should be able to try again from the command bar.
|
// They should be able to try again from the command bar.
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -385,23 +320,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
|
|
||||||
const promptWithNewline = `a 2x4\nlego`
|
const promptWithNewline = `a 2x4\nlego`
|
||||||
|
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
await page.getByTestId('text-to-cad').click()
|
||||||
await expect(commandBarButton).toBeVisible()
|
|
||||||
// Click the command bar button
|
|
||||||
await commandBarButton.click()
|
|
||||||
|
|
||||||
// Wait for the command bar to appear
|
|
||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
|
||||||
|
|
||||||
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
|
||||||
// Click the Text-to-CAD command
|
|
||||||
await textToCadCommand.first().click()
|
|
||||||
|
|
||||||
// Enter the prompt.
|
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
|
||||||
await expect(prompt.first()).toBeVisible()
|
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
await page.keyboard.type('a 2x4')
|
await page.keyboard.type('a 2x4')
|
||||||
@ -446,11 +365,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x8 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x8 lego')
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x10 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x10 lego')
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -529,9 +448,12 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||||
|
|
||||||
await sendPromptFromCommandBar(page, 'alkjsdnlajshdbfjlhsbdf a;askjdnf')
|
await sendPromptFromCommandBarTriggeredByButton(
|
||||||
|
page,
|
||||||
|
'alkjsdnlajshdbfjlhsbdf a;askjdnf'
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -543,7 +465,9 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const generatingToastMessage = page.getByText(
|
const generatingToastMessage = page.getByText(
|
||||||
`Generating parametric model...`
|
`Generating parametric model...`
|
||||||
)
|
)
|
||||||
await expect(generatingToastMessage.first()).toBeVisible({ timeout: 10000 })
|
await expect(generatingToastMessage.first()).toBeVisible({
|
||||||
|
timeout: 10000,
|
||||||
|
})
|
||||||
|
|
||||||
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
const successToastMessage = page.getByText(`Text-to-CAD successful`)
|
||||||
// We should have three success toasts.
|
// We should have three success toasts.
|
||||||
@ -587,7 +511,8 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
// Added underscore if we need this for later.
|
||||||
|
async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
||||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
@ -619,6 +544,25 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendPromptFromCommandBarTriggeredByButton(
|
||||||
|
page: Page,
|
||||||
|
promptStr: string
|
||||||
|
) {
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
||||||
|
await page.getByTestId('text-to-cad').click()
|
||||||
|
|
||||||
|
// Enter the prompt.
|
||||||
|
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||||
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
|
// Type the prompt.
|
||||||
|
await page.keyboard.type(promptStr)
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Text-to-CAD functionality',
|
'Text-to-CAD functionality',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
@ -659,7 +603,7 @@ test(
|
|||||||
await openKclCodePanel()
|
await openKclCodePanel()
|
||||||
|
|
||||||
await test.step(`Test file creation`, async () => {
|
await test.step(`Test file creation`, async () => {
|
||||||
await sendPromptFromCommandBar(page, prompt)
|
await sendPromptFromCommandBarTriggeredByButton(page, prompt)
|
||||||
// File is considered created if it shows up in the Project Files pane
|
// File is considered created if it shows up in the Project Files pane
|
||||||
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
||||||
expect(fileExists()).toBeTruthy()
|
expect(fileExists()).toBeTruthy()
|
||||||
@ -689,3 +633,749 @@ test(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Below there are twelve (12) tests for testing the navigation and file creation
|
||||||
|
* logic around text to cad. The Text to CAD command is now globally available
|
||||||
|
* within the application and is the same command for all parts of the application.
|
||||||
|
* There are many new user scenarios to test because we can navigate to any project
|
||||||
|
* you can accept and reject the creation and everything needs to be updated properly.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Gotcha: The API requests for text to CAD are mocked! The return values are
|
||||||
|
* from real API requests which are copied and pasted below
|
||||||
|
*
|
||||||
|
* Gotcha: The exports OBJ etc... are not in the output they are massive.
|
||||||
|
*
|
||||||
|
* Gotcha: Yes, the 3D render preview will be broken because the exported models
|
||||||
|
* are not included. These tests do not care about this.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
||||||
|
async function mockPageTextToCAD(page: Page) {
|
||||||
|
await page.route(
|
||||||
|
'https://api.dev.zoo.dev/ai/text-to-cad/glb?kcl=true',
|
||||||
|
async (route) => {
|
||||||
|
const json = {
|
||||||
|
id: 'bfc0ffc0-46c6-48bc-841e-1dc08f3428ce',
|
||||||
|
created_at: '2025-04-24T16:50:48.857892376Z',
|
||||||
|
user_id: '85de7740-3e38-4e86-abb5-e5afbb8a2183',
|
||||||
|
status: 'queued',
|
||||||
|
updated_at: '2025-04-24T16:50:48.857892376Z',
|
||||||
|
prompt: '1x1x1 cube',
|
||||||
|
output_format: 'glb',
|
||||||
|
model_version: '',
|
||||||
|
kcl_version: '0.2.63',
|
||||||
|
model: 'kcl',
|
||||||
|
feedback: null,
|
||||||
|
mocked: true,
|
||||||
|
}
|
||||||
|
await route.fulfill({ json })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
await page.route(
|
||||||
|
'https://api.dev.zoo.dev/user/text-to-cad/*',
|
||||||
|
async (route) => {
|
||||||
|
const json = {
|
||||||
|
mocked: true,
|
||||||
|
id: '6ecbb863-2766-47f0-95cb-7122ad7560ce',
|
||||||
|
created_at: '2025-04-24T16:52:00.360Z',
|
||||||
|
started_at: '2025-04-24T16:52:00.360Z',
|
||||||
|
completed_at: '2025-04-24T16:52:00.363Z',
|
||||||
|
user_id: '85de7740-3e38-4e86-abb5-e5afbb8a2183',
|
||||||
|
status: 'completed',
|
||||||
|
updated_at: '2025-04-24T16:52:00.360Z',
|
||||||
|
prompt: '2x2x2 cube',
|
||||||
|
outputs: {},
|
||||||
|
output_format: 'step',
|
||||||
|
model_version: '',
|
||||||
|
kcl_version: '0.2.63',
|
||||||
|
model: 'kcl',
|
||||||
|
feedback: null,
|
||||||
|
code: '/*\nGenerated by Text-to-CAD:\n2x2x2 cube\n*/\n@settings(defaultLengthUnit = mm)\n\n// Define the dimensions of the cube\ncubeSide = 2\n\n// Start a sketch on the XY plane\ncubeSketch = startSketchOn(XY)\n |> startProfileAt([0, 0], %)\n |> line(end = [cubeSide, 0])\n |> line(end = [0, cubeSide])\n |> line(end = [-cubeSide, 0])\n |> close()\n\n// Extrude the sketch to create a 3D cube\ncube = extrude(cubeSketch, length = cubeSide)',
|
||||||
|
}
|
||||||
|
await route.fulfill({ json })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> New Project -> Stay in home page -> Reject -> Project should be deleted',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('New project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Reject' }).click()
|
||||||
|
|
||||||
|
// Expect the entire project to be deleted
|
||||||
|
await expect(
|
||||||
|
page.getByText('Successfully deleted "my-project-name"')
|
||||||
|
).toBeVisible()
|
||||||
|
// Project DOM card has this test id
|
||||||
|
await expect(page.getByTestId(projectName)).not.toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> New Project -> Stay in home page -> Accept -> should navigate to file',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('New project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Accept' }).click()
|
||||||
|
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'2x2x2-cube.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||||
|
).toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> Existing Project -> Stay in home page -> Reject -> should delete single file',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// Create and navigate to the project then come home
|
||||||
|
await createProject({ name: projectName, page, returnHome: true })
|
||||||
|
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
await expect(page.getByText('1 file')).toBeVisible()
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('Existing project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await expect(page.getByRole('button', { name: 'Reject' })).toBeVisible()
|
||||||
|
await expect(page.getByText('2 file')).toBeVisible()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Reject' }).click()
|
||||||
|
|
||||||
|
await expect(page.getByText('1 file')).toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> Existing Project -> Stay in home page -> Accept -> should navigate to file',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// Create and navigate to the project then come home
|
||||||
|
await createProject({ name: projectName, page, returnHome: true })
|
||||||
|
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('Existing project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Accept' }).click()
|
||||||
|
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'2x2x2-cube.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||||
|
).toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> New Project -> Navigate to the project -> Reject -> should go to home page',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('New project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// Go into the project that was created from Text to CAD
|
||||||
|
await page.getByText(projectName).click()
|
||||||
|
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'2x2x2-cube.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Reject' }).click()
|
||||||
|
|
||||||
|
// Make sure we went back home
|
||||||
|
await expect(
|
||||||
|
page.getByText('No Projects found, ready to make your first one?')
|
||||||
|
).toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> New Project -> Navigate to the project -> Accept -> should stay in same file',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('New project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// Go into the project that was created from Text to CAD
|
||||||
|
await page.getByText(projectName).click()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Accept' }).click()
|
||||||
|
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'2x2x2-cube.kcl'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> Existing Project -> Navigate to the project -> Reject -> should load main.kcl',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// Create and navigate to the project then come home
|
||||||
|
await createProject({ name: projectName, page, returnHome: true })
|
||||||
|
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('Existing project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// Go into the project that was created from Text to CAD
|
||||||
|
// This only works because there is only 1 project. Each project has the same value of `data-test-id='project-title'`
|
||||||
|
await page.getByTestId('project-title').click()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Reject' }).click()
|
||||||
|
|
||||||
|
// Check header is populated with the project and file name
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'main.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check file is deleted
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(page.getByText('2x2x2-cube.kcl')).not.toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> Existing Project -> Navigate to the project -> Accept -> should load 2x2x2-cube.kcl',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page }, testInfo) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// Create and navigate to the project then come home
|
||||||
|
await createProject({ name: projectName, page, returnHome: true })
|
||||||
|
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('Existing project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// Go into the project that was created from Text to CAD
|
||||||
|
// This only works because there is only 1 project. Each project has the same value of `data-test-id='project-title'`
|
||||||
|
await page.getByTestId('project-title').click()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Accept' }).click()
|
||||||
|
|
||||||
|
// Check header is populated with the project and file name
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'2x2x2-cube.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check file is created
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||||
|
).toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> New Project -> Navigate to different project -> Reject -> should stay in project',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page, homePage }, testInfo) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const unrelatedProjectName = 'unrelated-project'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// Create and navigate to the project then come home
|
||||||
|
await createProject({
|
||||||
|
name: unrelatedProjectName,
|
||||||
|
page,
|
||||||
|
returnHome: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('New project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await homePage.openProject(unrelatedProjectName)
|
||||||
|
// Check that we opened the unrelated project
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
unrelatedProjectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'main.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Reject' }).click()
|
||||||
|
|
||||||
|
// Check header is populated with the project and file name
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
unrelatedProjectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'main.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check file is created
|
||||||
|
await u.openFilePanel()
|
||||||
|
// File should be deleted
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||||
|
).not.toBeVisible()
|
||||||
|
|
||||||
|
await u.goToHomePageFromModeling()
|
||||||
|
|
||||||
|
// Project should be deleted
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('home-section').getByText(projectName)
|
||||||
|
).not.toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> New Project -> Navigate to different project -> Accept -> should go to new project',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page, homePage }, testInfo) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const unrelatedProjectName = 'unrelated-project'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// Create and navigate to the project then come home
|
||||||
|
await createProject({
|
||||||
|
name: unrelatedProjectName,
|
||||||
|
page,
|
||||||
|
returnHome: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('New project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await homePage.openProject(unrelatedProjectName)
|
||||||
|
// Check that we opened the unrelated project
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
unrelatedProjectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'main.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Accept' }).click()
|
||||||
|
|
||||||
|
// Check header is populated with the project and file name
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'2x2x2-cube.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check file is created
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||||
|
).not.toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> Existing Project -> Navigate to different project -> Reject -> should stay in same project',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page, homePage }, testInfo) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const unrelatedProjectName = 'unrelated-project'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// Create and navigate to the project then come home
|
||||||
|
await createProject({
|
||||||
|
name: unrelatedProjectName,
|
||||||
|
page,
|
||||||
|
returnHome: true,
|
||||||
|
})
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
await createProject({ name: projectName, page, returnHome: true })
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('Existing project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await homePage.openProject(unrelatedProjectName)
|
||||||
|
// Check that we opened the unrelated project
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
unrelatedProjectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'main.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Reject' }).click()
|
||||||
|
|
||||||
|
// Check header is populated with the project and file name
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
unrelatedProjectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'main.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
await u.goToHomePageFromModeling()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('home-section').getByText(projectName)
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('home-section').getByText(unrelatedProjectName)
|
||||||
|
).toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Home Page -> Text To CAD -> Existing Project -> Navigate to different project -> Accept -> should navigate to new project',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, page, homePage }, testInfo) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
const projectName = 'my-project-name'
|
||||||
|
const unrelatedProjectName = 'unrelated-project'
|
||||||
|
const prompt = '2x2x2 cube'
|
||||||
|
await mockPageTextToCAD(page)
|
||||||
|
|
||||||
|
// Create and navigate to the project then come home
|
||||||
|
await createProject({
|
||||||
|
name: unrelatedProjectName,
|
||||||
|
page,
|
||||||
|
returnHome: true,
|
||||||
|
})
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
await createProject({ name: projectName, page, returnHome: true })
|
||||||
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
|
// open commands
|
||||||
|
await page.getByTestId('command-bar-open-button').click()
|
||||||
|
|
||||||
|
// search Text To CAD
|
||||||
|
await page.keyboard.type('Text To CAD')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// new project
|
||||||
|
await page.keyboard.type('Existing project')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// write name
|
||||||
|
await page.keyboard.type(projectName)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// prompt
|
||||||
|
await page.keyboard.type(prompt)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await homePage.openProject(unrelatedProjectName)
|
||||||
|
// Check that we opened the unrelated project
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
unrelatedProjectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'main.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Accept' }).click()
|
||||||
|
|
||||||
|
// Check header is populated with the project and file name
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-project-name')).toContainText(
|
||||||
|
projectName
|
||||||
|
)
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
|
'2x2x2-cube.kcl'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check file is created
|
||||||
|
await u.openFilePanel()
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||||
|
).toBeVisible()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -100,42 +100,6 @@ export const FileMachineProvider = ({
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Due to the route provider, i've moved this to the FileMachineProvider instead of CommandBarProvider
|
|
||||||
// This will register the commands to route to Telemetry, Home, and Settings.
|
|
||||||
useEffect(() => {
|
|
||||||
const filePath =
|
|
||||||
PATHS.FILE + '/' + encodeURIComponent(file?.path || BROWSER_PATH)
|
|
||||||
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
|
|
||||||
createRouteCommands(navigate, location, filePath)
|
|
||||||
commandBarActor.send({
|
|
||||||
type: 'Remove commands',
|
|
||||||
data: {
|
|
||||||
commands: [
|
|
||||||
RouteTelemetryCommand,
|
|
||||||
RouteHomeCommand,
|
|
||||||
RouteSettingsCommand,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (location.pathname === PATHS.HOME) {
|
|
||||||
commandBarActor.send({
|
|
||||||
type: 'Add commands',
|
|
||||||
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
|
|
||||||
})
|
|
||||||
} else if (location.pathname.includes(PATHS.FILE)) {
|
|
||||||
commandBarActor.send({
|
|
||||||
type: 'Add commands',
|
|
||||||
data: {
|
|
||||||
commands: [
|
|
||||||
RouteTelemetryCommand,
|
|
||||||
RouteSettingsCommand,
|
|
||||||
RouteHomeCommand,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [location])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
markOnce('code/didLoadFile')
|
markOnce('code/didLoadFile')
|
||||||
async function fetchKclSamples() {
|
async function fetchKclSamples() {
|
||||||
@ -440,6 +404,51 @@ export const FileMachineProvider = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Due to the route provider, i've moved this to the FileMachineProvider instead of CommandBarProvider
|
||||||
|
// This will register the commands to route to Telemetry, Home, and Settings.
|
||||||
|
useEffect(() => {
|
||||||
|
const filePath =
|
||||||
|
PATHS.FILE + '/' + encodeURIComponent(file?.path || BROWSER_PATH)
|
||||||
|
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
|
||||||
|
createRouteCommands(navigate, location, filePath)
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Remove commands',
|
||||||
|
data: {
|
||||||
|
commands: [
|
||||||
|
RouteTelemetryCommand,
|
||||||
|
RouteHomeCommand,
|
||||||
|
RouteSettingsCommand,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (location.pathname === PATHS.HOME) {
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Add commands',
|
||||||
|
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
|
||||||
|
})
|
||||||
|
} else if (location.pathname.includes(PATHS.FILE)) {
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Add commands',
|
||||||
|
data: {
|
||||||
|
commands: [
|
||||||
|
RouteTelemetryCommand,
|
||||||
|
RouteSettingsCommand,
|
||||||
|
RouteHomeCommand,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOTCHA: If we call navigate() while in the /file route the fileMachineProvider
|
||||||
|
// has a context.project of the original one that was loaded. It does not update
|
||||||
|
// Watch when the navigation changes, if it changes set a new Project within the fileMachine
|
||||||
|
// to load the latest state of the project you are in.
|
||||||
|
if (project) {
|
||||||
|
// TODO: Clean this up with global application state when fileMachine gets merged into SystemIOMachine
|
||||||
|
send({ type: 'Refresh with new project', data: { project } })
|
||||||
|
}
|
||||||
|
}, [location])
|
||||||
|
|
||||||
const cb = modelingMenuCallbackMostActions(
|
const cb = modelingMenuCallbackMostActions(
|
||||||
settings,
|
settings,
|
||||||
navigate,
|
navigate,
|
||||||
|
@ -8,7 +8,7 @@ import React, {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
import { useLoaderData } from 'react-router-dom'
|
||||||
import type { Actor, ContextFrom, Prop, SnapshotFrom, StateFrom } from 'xstate'
|
import type { Actor, ContextFrom, Prop, SnapshotFrom, StateFrom } from 'xstate'
|
||||||
import { assign, fromPromise } from 'xstate'
|
import { assign, fromPromise } from 'xstate'
|
||||||
|
|
||||||
@ -107,7 +107,6 @@ import {
|
|||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
} from '@src/lib/singletons'
|
} from '@src/lib/singletons'
|
||||||
import { submitAndAwaitTextToKcl } from '@src/lib/textToCad'
|
|
||||||
import { err, reject, reportRejection, trap } from '@src/lib/trap'
|
import { err, reject, reportRejection, trap } from '@src/lib/trap'
|
||||||
import type { IndexLoaderData } from '@src/lib/types'
|
import type { IndexLoaderData } from '@src/lib/types'
|
||||||
import { platform, uuidv4 } from '@src/lib/utils'
|
import { platform, uuidv4 } from '@src/lib/utils'
|
||||||
@ -139,11 +138,10 @@ export const ModelingMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
app: { theme, allowOrbitInSketchMode },
|
app: { allowOrbitInSketchMode },
|
||||||
modeling: { defaultUnit, cameraProjection, highlightEdges, cameraOrbit },
|
modeling: { defaultUnit, cameraProjection, cameraOrbit },
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
const navigate = useNavigate()
|
const { context } = useFileContext()
|
||||||
const { context, send: fileMachineSend } = useFileContext()
|
|
||||||
const { file } = useLoaderData() as IndexLoaderData
|
const { file } = useLoaderData() as IndexLoaderData
|
||||||
const token = useToken()
|
const token = useToken()
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
@ -533,23 +531,6 @@ export const ModelingMachineProvider = ({
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
'Submit to Text-to-CAD API': ({ event }) => {
|
|
||||||
if (event.type !== 'Text-to-CAD') return
|
|
||||||
const trimmedPrompt = event.data.prompt.trim()
|
|
||||||
if (!trimmedPrompt) return
|
|
||||||
|
|
||||||
submitAndAwaitTextToKcl({
|
|
||||||
trimmedPrompt,
|
|
||||||
fileMachineSend,
|
|
||||||
navigate,
|
|
||||||
context,
|
|
||||||
token,
|
|
||||||
settings: {
|
|
||||||
theme: theme.current,
|
|
||||||
highlightEdges: highlightEdges.current,
|
|
||||||
},
|
|
||||||
}).catch(reportRejection)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
guards: {
|
guards: {
|
||||||
'has valid selection for deletion': ({
|
'has valid selection for deletion': ({
|
||||||
|
@ -263,7 +263,10 @@ function ProjectMenuPopover({
|
|||||||
data-testid="project-sidebar-toggle"
|
data-testid="project-sidebar-toggle"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-start py-0.5">
|
<div className="flex flex-col items-start py-0.5">
|
||||||
<span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block">
|
<span
|
||||||
|
className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
|
||||||
|
data-testid="app-header-file-name"
|
||||||
|
>
|
||||||
{isDesktop() && file?.name
|
{isDesktop() && file?.name
|
||||||
? file.name.slice(
|
? file.name.slice(
|
||||||
file.name.lastIndexOf(window.electron.path.sep) + 1
|
file.name.lastIndexOf(window.electron.path.sep) + 1
|
||||||
@ -271,7 +274,10 @@ function ProjectMenuPopover({
|
|||||||
: APP_NAME}
|
: APP_NAME}
|
||||||
</span>
|
</span>
|
||||||
{isDesktop() && project?.name && (
|
{isDesktop() && project?.name && (
|
||||||
<span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block">
|
<span
|
||||||
|
className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block"
|
||||||
|
data-testid="app-header-project-name"
|
||||||
|
>
|
||||||
{project.name}
|
{project.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import { useFileSystemWatcher } from '@src/hooks/useFileSystemWatcher'
|
import { useFileSystemWatcher } from '@src/hooks/useFileSystemWatcher'
|
||||||
import { PATHS } from '@src/lib/paths'
|
import { PATHS } from '@src/lib/paths'
|
||||||
import { systemIOActor, useSettings } from '@src/lib/singletons'
|
import { systemIOActor, useSettings, useToken } from '@src/lib/singletons'
|
||||||
import {
|
import {
|
||||||
useHasListedProjects,
|
useHasListedProjects,
|
||||||
useProjectDirectoryPath,
|
useProjectDirectoryPath,
|
||||||
useRequestedFileName,
|
useRequestedFileName,
|
||||||
useRequestedProjectName,
|
useRequestedProjectName,
|
||||||
|
useRequestedTextToCadGeneration,
|
||||||
|
useFolders,
|
||||||
} from '@src/machines/systemIO/hooks'
|
} from '@src/machines/systemIO/hooks'
|
||||||
import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
|
import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import { submitAndAwaitTextToKclSystemIO } from '@src/lib/textToCad'
|
||||||
|
import { reportRejection } from '@src/lib/trap'
|
||||||
|
import { getUniqueProjectName } from '@src/lib/desktopFS'
|
||||||
|
|
||||||
export function SystemIOMachineLogicListenerDesktop() {
|
export function SystemIOMachineLogicListenerDesktop() {
|
||||||
const requestedProjectName = useRequestedProjectName()
|
const requestedProjectName = useRequestedProjectName()
|
||||||
@ -18,6 +23,9 @@ export function SystemIOMachineLogicListenerDesktop() {
|
|||||||
const hasListedProjects = useHasListedProjects()
|
const hasListedProjects = useHasListedProjects()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
|
const requestedTextToCadGeneration = useRequestedTextToCadGeneration()
|
||||||
|
const token = useToken()
|
||||||
|
const folders = useFolders()
|
||||||
|
|
||||||
const useGlobalProjectNavigation = () => {
|
const useGlobalProjectNavigation = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -95,6 +103,27 @@ export function SystemIOMachineLogicListenerDesktop() {
|
|||||||
? [settings.app.projectDirectory.current]
|
? [settings.app.projectDirectory.current]
|
||||||
: []
|
: []
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Move this generateTextToCAD to another machine in the future and make a whole machine out of it.
|
||||||
|
useEffect(() => {
|
||||||
|
const requestedPromptTrimmed =
|
||||||
|
requestedTextToCadGeneration.requestedPrompt.trim()
|
||||||
|
const requestedProjectName =
|
||||||
|
requestedTextToCadGeneration.requestedProjectName
|
||||||
|
const isProjectNew = requestedTextToCadGeneration.isProjectNew
|
||||||
|
if (!requestedPromptTrimmed || !requestedProjectName) return
|
||||||
|
const uniqueNameIfNeeded = isProjectNew
|
||||||
|
? getUniqueProjectName(requestedProjectName, folders)
|
||||||
|
: requestedProjectName
|
||||||
|
submitAndAwaitTextToKclSystemIO({
|
||||||
|
trimmedPrompt: requestedPromptTrimmed,
|
||||||
|
projectName: uniqueNameIfNeeded,
|
||||||
|
navigate,
|
||||||
|
token,
|
||||||
|
isProjectNew,
|
||||||
|
settings: { highlightEdges: settings.modeling.highlightEdges.current },
|
||||||
|
}).catch(reportRejection)
|
||||||
|
}, [requestedTextToCadGeneration])
|
||||||
}
|
}
|
||||||
|
|
||||||
useGlobalProjectNavigation()
|
useGlobalProjectNavigation()
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
import { useEffect, useCallback } from 'react'
|
import { useEffect, useCallback } from 'react'
|
||||||
import { useClearURLParams } from '@src/machines/systemIO/hooks'
|
import {
|
||||||
|
useClearURLParams,
|
||||||
|
useRequestedTextToCadGeneration,
|
||||||
|
} from '@src/machines/systemIO/hooks'
|
||||||
import { useSearchParams } from 'react-router-dom'
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import { CREATE_FILE_URL_PARAM } from '@src/lib/constants'
|
import { CREATE_FILE_URL_PARAM } from '@src/lib/constants'
|
||||||
|
import { submitAndAwaitTextToKclSystemIO } from '@src/lib/textToCad'
|
||||||
|
import { reportRejection } from '@src/lib/trap'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { useSettings, useToken } from '@src/lib/singletons'
|
||||||
|
|
||||||
export function SystemIOMachineLogicListenerWeb() {
|
export function SystemIOMachineLogicListenerWeb() {
|
||||||
const clearURLParams = useClearURLParams()
|
const clearURLParams = useClearURLParams()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const settings = useSettings()
|
||||||
|
const requestedTextToCadGeneration = useRequestedTextToCadGeneration()
|
||||||
|
const token = useToken()
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const clearImportSearchParams = useCallback(() => {
|
const clearImportSearchParams = useCallback(() => {
|
||||||
// Clear the search parameters related to the "Import file from URL" command
|
// Clear the search parameters related to the "Import file from URL" command
|
||||||
@ -24,6 +35,26 @@ export function SystemIOMachineLogicListenerWeb() {
|
|||||||
}, [clearURLParams])
|
}, [clearURLParams])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move this generateTextToCAD to another machine in the future and make a whole machine out of it.
|
||||||
|
useEffect(() => {
|
||||||
|
const requestedPromptTrimmed =
|
||||||
|
requestedTextToCadGeneration.requestedPrompt.trim()
|
||||||
|
const requestedProjectName =
|
||||||
|
requestedTextToCadGeneration.requestedProjectName
|
||||||
|
const isProjectNew = requestedTextToCadGeneration.isProjectNew
|
||||||
|
if (!requestedPromptTrimmed || !requestedProjectName) return
|
||||||
|
// Gotcha: web has no project name.
|
||||||
|
const uniqueNameIfNeeded = requestedProjectName
|
||||||
|
submitAndAwaitTextToKclSystemIO({
|
||||||
|
trimmedPrompt: requestedPromptTrimmed,
|
||||||
|
projectName: uniqueNameIfNeeded,
|
||||||
|
navigate,
|
||||||
|
token,
|
||||||
|
isProjectNew,
|
||||||
|
settings: { highlightEdges: settings.modeling.highlightEdges.current },
|
||||||
|
}).catch(reportRejection)
|
||||||
|
}, [requestedTextToCadGeneration])
|
||||||
|
|
||||||
useClearQueryParams()
|
useClearQueryParams()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -21,20 +21,17 @@ import {
|
|||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
||||||
import type { EventFrom } from 'xstate'
|
|
||||||
|
|
||||||
import { ActionButton } from '@src/components/ActionButton'
|
import { ActionButton } from '@src/components/ActionButton'
|
||||||
import type { useFileContext } from '@src/hooks/useFileContext'
|
|
||||||
import { base64Decode } from '@src/lang/wasm'
|
import { base64Decode } from '@src/lang/wasm'
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
|
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
|
||||||
import { PATHS } from '@src/lib/paths'
|
import { PATHS } from '@src/lib/paths'
|
||||||
import { codeManager, kclManager } from '@src/lib/singletons'
|
import { codeManager, kclManager, systemIOActor } from '@src/lib/singletons'
|
||||||
import { sendTelemetry } from '@src/lib/textToCadTelemetry'
|
import { sendTelemetry } from '@src/lib/textToCadTelemetry'
|
||||||
import type { Themes } from '@src/lib/theme'
|
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { commandBarActor } from '@src/lib/singletons'
|
import { commandBarActor } from '@src/lib/singletons'
|
||||||
import type { fileMachine } from '@src/machines/fileMachine'
|
import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
|
||||||
|
import { useProjectDirectoryPath } from '@src/machines/systemIO/hooks'
|
||||||
|
|
||||||
const CANVAS_SIZE = 128
|
const CANVAS_SIZE = 128
|
||||||
const PROMPT_TRUNCATE_LENGTH = 128
|
const PROMPT_TRUNCATE_LENGTH = 128
|
||||||
@ -82,10 +79,16 @@ export function ToastTextToCadError({
|
|||||||
toastId,
|
toastId,
|
||||||
message,
|
message,
|
||||||
prompt,
|
prompt,
|
||||||
|
method,
|
||||||
|
projectName,
|
||||||
|
newProjectName,
|
||||||
}: {
|
}: {
|
||||||
toastId: string
|
toastId: string
|
||||||
message: string
|
message: string
|
||||||
prompt: string
|
prompt: string
|
||||||
|
method: string
|
||||||
|
projectName: string
|
||||||
|
newProjectName: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-between gap-6">
|
<div className="flex flex-col justify-between gap-6">
|
||||||
@ -118,10 +121,13 @@ export function ToastTextToCadError({
|
|||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
groupId: 'modeling',
|
groupId: 'application',
|
||||||
name: 'Text-to-CAD',
|
name: 'Text-to-CAD',
|
||||||
argDefaultValues: {
|
argDefaultValues: {
|
||||||
prompt,
|
prompt,
|
||||||
|
method,
|
||||||
|
projectName,
|
||||||
|
newProjectName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -139,24 +145,22 @@ export function ToastTextToCadSuccess({
|
|||||||
toastId,
|
toastId,
|
||||||
data,
|
data,
|
||||||
navigate,
|
navigate,
|
||||||
context,
|
|
||||||
token,
|
token,
|
||||||
fileMachineSend,
|
|
||||||
settings,
|
settings,
|
||||||
|
projectName,
|
||||||
|
fileName,
|
||||||
|
isProjectNew,
|
||||||
}: {
|
}: {
|
||||||
toastId: string
|
toastId: string
|
||||||
data: TextToCad_type & { fileName: string }
|
data: TextToCad_type & { fileName: string }
|
||||||
navigate: (to: string) => void
|
navigate: (to: string) => void
|
||||||
context: ReturnType<typeof useFileContext>['context']
|
|
||||||
token?: string
|
token?: string
|
||||||
fileMachineSend: (
|
settings?: {
|
||||||
event: EventFrom<typeof fileMachine>,
|
|
||||||
data?: unknown
|
|
||||||
) => void
|
|
||||||
settings: {
|
|
||||||
theme: Themes
|
|
||||||
highlightEdges: boolean
|
highlightEdges: boolean
|
||||||
}
|
}
|
||||||
|
projectName: string
|
||||||
|
fileName: string
|
||||||
|
isProjectNew: boolean
|
||||||
}) {
|
}) {
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||||
@ -164,6 +168,7 @@ export function ToastTextToCadSuccess({
|
|||||||
const [hasCopied, setHasCopied] = useState(false)
|
const [hasCopied, setHasCopied] = useState(false)
|
||||||
const [showCopiedUi, setShowCopiedUi] = useState(false)
|
const [showCopiedUi, setShowCopiedUi] = useState(false)
|
||||||
const modelId = data.id
|
const modelId = data.id
|
||||||
|
const projectDirectoryPath = useProjectDirectoryPath()
|
||||||
|
|
||||||
const animate = useCallback(
|
const animate = useCallback(
|
||||||
({
|
({
|
||||||
@ -198,7 +203,11 @@ export function ToastTextToCadSuccess({
|
|||||||
if (!canvasRef.current) return
|
if (!canvasRef.current) return
|
||||||
|
|
||||||
const canvas = canvasRef.current
|
const canvas = canvasRef.current
|
||||||
const renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true })
|
const renderer = new WebGLRenderer({
|
||||||
|
canvas,
|
||||||
|
antialias: true,
|
||||||
|
alpha: true,
|
||||||
|
})
|
||||||
renderer.setSize(CANVAS_SIZE, CANVAS_SIZE)
|
renderer.setSize(CANVAS_SIZE, CANVAS_SIZE)
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
|
||||||
|
|
||||||
@ -344,15 +353,30 @@ export function ToastTextToCadSuccess({
|
|||||||
}
|
}
|
||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
// Delete the file from the project
|
// Delete the file from the project
|
||||||
fileMachineSend({
|
|
||||||
type: 'Delete file',
|
if (projectName && fileName) {
|
||||||
data: {
|
// You are in the new workflow for text to cad at the global application level
|
||||||
name: data.fileName,
|
if (isProjectNew) {
|
||||||
path: `${context.project.path}${window.electron.sep}${data.fileName}`,
|
// Delete the entire project if it was newly created from text to CAD
|
||||||
children: null,
|
systemIOActor.send({
|
||||||
},
|
type: SystemIOMachineEvents.deleteProject,
|
||||||
})
|
data: {
|
||||||
|
requestedProjectName: projectName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Only delete the file if the project was preexisting
|
||||||
|
systemIOActor.send({
|
||||||
|
type: SystemIOMachineEvents.deleteKCLFile,
|
||||||
|
data: {
|
||||||
|
requestedProjectName: projectName,
|
||||||
|
requestedFileName: fileName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.dismiss(toastId)
|
toast.dismiss(toastId)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -368,11 +392,9 @@ export function ToastTextToCadSuccess({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
sendTelemetry(modelId, 'accepted', token)
|
sendTelemetry(modelId, 'accepted', token)
|
||||||
navigate(
|
const path = `${projectDirectoryPath}${window.electron.path.sep}${projectName}${window.electron.sep}${fileName}`
|
||||||
`${PATHS.FILE}/${encodeURIComponent(
|
navigate(`${PATHS.FILE}/${encodeURIComponent(path)}`)
|
||||||
`${context.project.path}${window.electron.sep}${data.fileName}`
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
toast.dismiss(toastId)
|
toast.dismiss(toastId)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -426,7 +448,6 @@ function traverseSceneToStyleObjects({
|
|||||||
}: {
|
}: {
|
||||||
scene: Scene
|
scene: Scene
|
||||||
color?: number
|
color?: number
|
||||||
theme: Themes
|
|
||||||
highlightEdges?: boolean
|
highlightEdges?: boolean
|
||||||
}) {
|
}) {
|
||||||
scene.traverse((child) => {
|
scene.traverse((child) => {
|
||||||
|
83
src/lib/commandBarConfigs/applicationCommandConfig.ts
Normal file
83
src/lib/commandBarConfigs/applicationCommandConfig.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import type { systemIOMachine } from '@src/machines/systemIO/systemIOMachine'
|
||||||
|
import type { ActorRefFrom } from 'xstate'
|
||||||
|
import type { Command, CommandArgumentOption } from '@src/lib/commandTypes'
|
||||||
|
import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
|
||||||
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
|
|
||||||
|
export function createApplicationCommands({
|
||||||
|
systemIOActor,
|
||||||
|
}: {
|
||||||
|
systemIOActor: ActorRefFrom<typeof systemIOMachine>
|
||||||
|
}) {
|
||||||
|
const textToCADCommand: Command = {
|
||||||
|
name: 'Text-to-CAD',
|
||||||
|
description: 'Use the Zoo Text-to-CAD API to generate part starters.',
|
||||||
|
displayName: `Text to CAD`,
|
||||||
|
groupId: 'application',
|
||||||
|
needsReview: false,
|
||||||
|
icon: 'sparkles',
|
||||||
|
onSubmit: (record) => {
|
||||||
|
if (record) {
|
||||||
|
const requestedProjectName = record.newProjectName || record.projectName
|
||||||
|
const requestedPrompt = record.prompt
|
||||||
|
const isProjectNew = !!record.newProjectName
|
||||||
|
systemIOActor.send({
|
||||||
|
type: SystemIOMachineEvents.generateTextToCAD,
|
||||||
|
data: { requestedPrompt, requestedProjectName, isProjectNew },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
method: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: true,
|
||||||
|
skip: true,
|
||||||
|
options: isDesktop()
|
||||||
|
? [
|
||||||
|
{ name: 'New project', value: 'newProject' },
|
||||||
|
{ name: 'Existing project', value: 'existingProject' },
|
||||||
|
]
|
||||||
|
: [{ name: 'Overwrite', value: 'existingProject' }],
|
||||||
|
valueSummary(value) {
|
||||||
|
return isDesktop()
|
||||||
|
? value === 'newProject'
|
||||||
|
? 'New project'
|
||||||
|
: 'Existing project'
|
||||||
|
: 'Overwrite'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
projectName: {
|
||||||
|
inputType: 'options',
|
||||||
|
required: (commandsContext) =>
|
||||||
|
isDesktop() &&
|
||||||
|
commandsContext.argumentsToSubmit.method === 'existingProject',
|
||||||
|
skip: true,
|
||||||
|
options: (_, context) => {
|
||||||
|
const { folders } = systemIOActor.getSnapshot().context
|
||||||
|
const options: CommandArgumentOption<string>[] = []
|
||||||
|
folders.forEach((folder) => {
|
||||||
|
options.push({
|
||||||
|
name: folder.name,
|
||||||
|
value: folder.name,
|
||||||
|
isCurrent: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
},
|
||||||
|
},
|
||||||
|
newProjectName: {
|
||||||
|
inputType: 'text',
|
||||||
|
required: (commandsContext) =>
|
||||||
|
isDesktop() &&
|
||||||
|
commandsContext.argumentsToSubmit.method === 'newProject',
|
||||||
|
skip: true,
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
inputType: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDesktop() ? [textToCADCommand] : [textToCADCommand]
|
||||||
|
}
|
@ -151,9 +151,6 @@ export type ModelingCommandSchema = {
|
|||||||
}
|
}
|
||||||
namedValue: KclCommandValue
|
namedValue: KclCommandValue
|
||||||
}
|
}
|
||||||
'Text-to-CAD': {
|
|
||||||
prompt: string
|
|
||||||
}
|
|
||||||
'Prompt-to-edit': {
|
'Prompt-to-edit': {
|
||||||
prompt: string
|
prompt: string
|
||||||
selection: Selections
|
selection: Selections
|
||||||
@ -959,16 +956,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'Text-to-CAD': {
|
|
||||||
description: 'Use the Zoo Text-to-CAD API to generate part starters.',
|
|
||||||
icon: 'chat',
|
|
||||||
args: {
|
|
||||||
prompt: {
|
|
||||||
inputType: 'text',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'Prompt-to-edit': {
|
'Prompt-to-edit': {
|
||||||
description: 'Use Zoo AI to edit your kcl',
|
description: 'Use Zoo AI to edit your kcl',
|
||||||
icon: 'chat',
|
icon: 'chat',
|
||||||
|
@ -28,6 +28,7 @@ import type { AppMachineContext } from '@src/lib/types'
|
|||||||
import { createAuthCommands } from '@src/lib/commandBarConfigs/authCommandConfig'
|
import { createAuthCommands } from '@src/lib/commandBarConfigs/authCommandConfig'
|
||||||
import { commandBarMachine } from '@src/machines/commandBarMachine'
|
import { commandBarMachine } from '@src/machines/commandBarMachine'
|
||||||
import { createProjectCommands } from '@src/lib/commandBarConfigs/projectsCommandConfig'
|
import { createProjectCommands } from '@src/lib/commandBarConfigs/projectsCommandConfig'
|
||||||
|
import { createApplicationCommands } from '@src/lib/commandBarConfigs/applicationCommandConfig'
|
||||||
|
|
||||||
export const codeManager = new CodeManager()
|
export const codeManager = new CodeManager()
|
||||||
export const engineCommandManager = new EngineCommandManager()
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
@ -226,6 +227,7 @@ commandBarActor.send({
|
|||||||
commands: [
|
commands: [
|
||||||
...createAuthCommands({ authActor }),
|
...createAuthCommands({ authActor }),
|
||||||
...createProjectCommands({ systemIOActor }),
|
...createProjectCommands({ systemIOActor }),
|
||||||
|
...createApplicationCommands({ systemIOActor }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -2,8 +2,6 @@ import type { Models } from '@kittycad/lib'
|
|||||||
import { VITE_KC_API_BASE_URL } from '@src/env'
|
import { VITE_KC_API_BASE_URL } from '@src/env'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import type { NavigateFunction } from 'react-router-dom'
|
import type { NavigateFunction } from 'react-router-dom'
|
||||||
import type { ContextFrom, EventFrom } from 'xstate'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ToastTextToCadError,
|
ToastTextToCadError,
|
||||||
ToastTextToCadSuccess,
|
ToastTextToCadSuccess,
|
||||||
@ -12,13 +10,12 @@ import { FILE_EXT } from '@src/lib/constants'
|
|||||||
import crossPlatformFetch from '@src/lib/crossPlatformFetch'
|
import crossPlatformFetch from '@src/lib/crossPlatformFetch'
|
||||||
import { getNextFileName } from '@src/lib/desktopFS'
|
import { getNextFileName } from '@src/lib/desktopFS'
|
||||||
import { isDesktop } from '@src/lib/isDesktop'
|
import { isDesktop } from '@src/lib/isDesktop'
|
||||||
import { kclManager } from '@src/lib/singletons'
|
import { kclManager, systemIOActor } from '@src/lib/singletons'
|
||||||
import type { Themes } from '@src/lib/theme'
|
import { SystemIOMachineEvents } from '@src/machines/systemIO/utils'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import { toSync } from '@src/lib/utils'
|
import { toSync } from '@src/lib/utils'
|
||||||
import type { fileMachine } from '@src/machines/fileMachine'
|
|
||||||
|
|
||||||
async function submitTextToCadPrompt(
|
export async function submitTextToCadPrompt(
|
||||||
prompt: string,
|
prompt: string,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
token?: string
|
token?: string
|
||||||
@ -52,7 +49,7 @@ async function submitTextToCadPrompt(
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getTextToCadResult(
|
export async function getTextToCadResult(
|
||||||
id: string,
|
id: string,
|
||||||
token?: string
|
token?: string
|
||||||
): Promise<Models['TextToCad_type'] | Error> {
|
): Promise<Models['TextToCad_type'] | Error> {
|
||||||
@ -68,29 +65,25 @@ async function getTextToCadResult(
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TextToKclProps {
|
interface TextToKclPropsApplicationLevel {
|
||||||
trimmedPrompt: string
|
trimmedPrompt: string
|
||||||
fileMachineSend: (
|
|
||||||
type: EventFrom<typeof fileMachine>,
|
|
||||||
data?: unknown
|
|
||||||
) => unknown
|
|
||||||
navigate: NavigateFunction
|
navigate: NavigateFunction
|
||||||
context: ContextFrom<typeof fileMachine>
|
|
||||||
token?: string
|
token?: string
|
||||||
settings: {
|
projectName: string
|
||||||
theme: Themes
|
isProjectNew: boolean
|
||||||
|
settings?: {
|
||||||
highlightEdges: boolean
|
highlightEdges: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function submitAndAwaitTextToKcl({
|
export async function submitAndAwaitTextToKclSystemIO({
|
||||||
trimmedPrompt,
|
trimmedPrompt,
|
||||||
fileMachineSend,
|
|
||||||
navigate,
|
|
||||||
context,
|
|
||||||
token,
|
token,
|
||||||
|
projectName,
|
||||||
|
navigate,
|
||||||
|
isProjectNew,
|
||||||
settings,
|
settings,
|
||||||
}: TextToKclProps) {
|
}: TextToKclPropsApplicationLevel) {
|
||||||
const toastId = toast.loading('Submitting to Text-to-CAD API...')
|
const toastId = toast.loading('Submitting to Text-to-CAD API...')
|
||||||
const showFailureToast = (message: string) => {
|
const showFailureToast = (message: string) => {
|
||||||
toast.error(
|
toast.error(
|
||||||
@ -99,6 +92,9 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
toastId,
|
toastId,
|
||||||
message,
|
message,
|
||||||
prompt: trimmedPrompt,
|
prompt: trimmedPrompt,
|
||||||
|
method: isProjectNew ? 'newProject' : 'existingProject',
|
||||||
|
projectName: isProjectNew ? '' : projectName,
|
||||||
|
newProjectName: isProjectNew ? projectName : '',
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
id: toastId,
|
id: toastId,
|
||||||
@ -109,7 +105,7 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
|
|
||||||
const textToCadQueued = await submitTextToCadPrompt(
|
const textToCadQueued = await submitTextToCadPrompt(
|
||||||
trimmedPrompt,
|
trimmedPrompt,
|
||||||
context.project.name,
|
projectName,
|
||||||
token
|
token
|
||||||
)
|
)
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
@ -174,12 +170,16 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let newFileName = ''
|
||||||
|
|
||||||
const textToCadOutputCreated = await textToCadComplete
|
const textToCadOutputCreated = await textToCadComplete
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
showFailureToast('Failed to generate parametric model')
|
showFailureToast('Failed to generate parametric model')
|
||||||
return e
|
return e
|
||||||
})
|
})
|
||||||
.then(async (value) => {
|
.then(async (value) => {
|
||||||
|
console.log('completed')
|
||||||
|
console.log(value)
|
||||||
if (value.code === undefined || !value.code || value.code.length === 0) {
|
if (value.code === undefined || !value.code || value.code.length === 0) {
|
||||||
// We want to show the real error message to the user.
|
// We want to show the real error message to the user.
|
||||||
if (value.error && value.error.length > 0) {
|
if (value.error && value.error.length > 0) {
|
||||||
@ -193,7 +193,7 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TRUNCATED_PROMPT_LENGTH = 24
|
const TRUNCATED_PROMPT_LENGTH = 24
|
||||||
let newFileName = `${value.prompt
|
newFileName = `${value.prompt
|
||||||
.slice(0, TRUNCATED_PROMPT_LENGTH)
|
.slice(0, TRUNCATED_PROMPT_LENGTH)
|
||||||
.replace(/\s/gi, '-')
|
.replace(/\s/gi, '-')
|
||||||
.replace(/\W/gi, '-')
|
.replace(/\W/gi, '-')
|
||||||
@ -205,16 +205,15 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
// and by extension the file-deletion-on-reject logic.
|
// and by extension the file-deletion-on-reject logic.
|
||||||
newFileName = getNextFileName({
|
newFileName = getNextFileName({
|
||||||
entryName: newFileName,
|
entryName: newFileName,
|
||||||
baseDir: context.selectedDirectory.path,
|
baseDir: projectName,
|
||||||
}).name
|
}).name
|
||||||
|
|
||||||
fileMachineSend({
|
systemIOActor.send({
|
||||||
type: 'Create file',
|
type: SystemIOMachineEvents.createKCLFile,
|
||||||
data: {
|
data: {
|
||||||
name: newFileName,
|
requestedProjectName: projectName,
|
||||||
makeDir: false,
|
requestedCode: value.code,
|
||||||
content: value.code,
|
requestedFileName: newFileName,
|
||||||
silent: true,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -234,13 +233,16 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
// and options to reject or accept the model
|
// and options to reject or accept the model
|
||||||
toast.success(
|
toast.success(
|
||||||
() =>
|
() =>
|
||||||
|
// EXPECTED: This will throw a error in dev mode, do not worry about it.
|
||||||
|
// Warning: Internal React error: Expected static flag was missing. Please notify the React team.
|
||||||
ToastTextToCadSuccess({
|
ToastTextToCadSuccess({
|
||||||
toastId,
|
toastId,
|
||||||
data: textToCadOutputCreated,
|
data: textToCadOutputCreated,
|
||||||
token,
|
token,
|
||||||
|
projectName: projectName,
|
||||||
|
fileName: newFileName,
|
||||||
navigate,
|
navigate,
|
||||||
context,
|
isProjectNew,
|
||||||
fileMachineSend,
|
|
||||||
settings,
|
settings,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { DEV } from '@src/env'
|
import { DEV } from '@src/env'
|
||||||
import type { EventFrom, StateFrom } from 'xstate'
|
import type { EventFrom, StateFrom } from 'xstate'
|
||||||
|
import { settingsActor } from '@src/lib/singletons'
|
||||||
|
|
||||||
import type { CustomIconName } from '@src/components/CustomIcon'
|
import type { CustomIconName } from '@src/components/CustomIcon'
|
||||||
import { createLiteral } from '@src/lang/create'
|
import { createLiteral } from '@src/lang/create'
|
||||||
@ -418,11 +419,21 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
array: [
|
array: [
|
||||||
{
|
{
|
||||||
id: 'text-to-cad',
|
id: 'text-to-cad',
|
||||||
onClick: () =>
|
onClick: () => {
|
||||||
|
const currentProject =
|
||||||
|
settingsActor.getSnapshot().context.currentProject
|
||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'Text-to-CAD', groupId: 'modeling' },
|
data: {
|
||||||
}),
|
name: 'Text-to-CAD',
|
||||||
|
groupId: 'application',
|
||||||
|
argDefaultValues: {
|
||||||
|
method: 'existingProject',
|
||||||
|
projectName: currentProject?.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
icon: 'sparkles',
|
icon: 'sparkles',
|
||||||
iconColor: '#29FFA4',
|
iconColor: '#29FFA4',
|
||||||
alwaysDark: true,
|
alwaysDark: true,
|
||||||
|
@ -63,6 +63,7 @@ type FileMachineEvents =
|
|||||||
}
|
}
|
||||||
| { type: 'assign'; data: { [key: string]: any } }
|
| { type: 'assign'; data: { [key: string]: any } }
|
||||||
| { type: 'Refresh' }
|
| { type: 'Refresh' }
|
||||||
|
| { type: 'Refresh with new project'; data: { project: Project } }
|
||||||
|
|
||||||
export const fileMachine = setup({
|
export const fileMachine = setup({
|
||||||
types: {} as {
|
types: {} as {
|
||||||
@ -95,6 +96,10 @@ export const fileMachine = setup({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
setProject: assign(({ event }) => {
|
||||||
|
if (event.type !== 'Refresh with new project') return {}
|
||||||
|
return { project: event.data.project }
|
||||||
|
}),
|
||||||
navigateToFile: () => {},
|
navigateToFile: () => {},
|
||||||
openFileInNewWindow: () => {},
|
openFileInNewWindow: () => {},
|
||||||
renameToastSuccess: () => {},
|
renameToastSuccess: () => {},
|
||||||
@ -183,6 +188,10 @@ export const fileMachine = setup({
|
|||||||
},
|
},
|
||||||
|
|
||||||
Refresh: '.Reading files',
|
Refresh: '.Reading files',
|
||||||
|
'Refresh with new project': {
|
||||||
|
actions: ['setProject'],
|
||||||
|
target: '.Reading files',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
states: {
|
states: {
|
||||||
'Has no files': {
|
'Has no files': {
|
||||||
|
@ -371,7 +371,6 @@ export type ModelingMachineEvent =
|
|||||||
| { type: 'Chamfer'; data?: ModelingCommandSchema['Chamfer'] }
|
| { type: 'Chamfer'; data?: ModelingCommandSchema['Chamfer'] }
|
||||||
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
|
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
|
||||||
| { type: 'Helix'; data: ModelingCommandSchema['Helix'] }
|
| { type: 'Helix'; data: ModelingCommandSchema['Helix'] }
|
||||||
| { type: 'Text-to-CAD'; data: ModelingCommandSchema['Text-to-CAD'] }
|
|
||||||
| { type: 'Prompt-to-edit'; data: ModelingCommandSchema['Prompt-to-edit'] }
|
| { type: 'Prompt-to-edit'; data: ModelingCommandSchema['Prompt-to-edit'] }
|
||||||
| {
|
| {
|
||||||
type: 'Delete selection'
|
type: 'Delete selection'
|
||||||
|
@ -19,3 +19,9 @@ export const useHasListedProjects = () =>
|
|||||||
|
|
||||||
export const useClearURLParams = () =>
|
export const useClearURLParams = () =>
|
||||||
useSelector(systemIOActor, (state) => state.context.clearURLParams)
|
useSelector(systemIOActor, (state) => state.context.clearURLParams)
|
||||||
|
|
||||||
|
export const useRequestedTextToCadGeneration = () =>
|
||||||
|
useSelector(
|
||||||
|
systemIOActor,
|
||||||
|
(state) => state.context.requestedTextToCadGeneration
|
||||||
|
)
|
||||||
|
@ -76,6 +76,22 @@ export const systemIOMachine = setup({
|
|||||||
| {
|
| {
|
||||||
type: SystemIOMachineEvents.setDefaultProjectFolderName
|
type: SystemIOMachineEvents.setDefaultProjectFolderName
|
||||||
data: { requestedDefaultProjectFolderName: string }
|
data: { requestedDefaultProjectFolderName: string }
|
||||||
|
}
|
||||||
|
// TODO: Move this generateTextToCAD to another machine in the future and make a whole machine out of it.
|
||||||
|
| {
|
||||||
|
type: SystemIOMachineEvents.generateTextToCAD
|
||||||
|
data: {
|
||||||
|
requestedPrompt: string
|
||||||
|
requestedProjectName: string
|
||||||
|
isProjectNew: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: SystemIOMachineEvents.deleteKCLFile
|
||||||
|
data: {
|
||||||
|
requestedProjectName: string
|
||||||
|
requestedFileName: string
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -143,6 +159,12 @@ export const systemIOMachine = setup({
|
|||||||
return event.output
|
return event.output
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
[SystemIOMachineActions.setRequestedTextToCadGeneration]: assign({
|
||||||
|
requestedTextToCadGeneration: ({ event }) => {
|
||||||
|
assertEvent(event, SystemIOMachineEvents.generateTextToCAD)
|
||||||
|
return event.data
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
actors: {
|
actors: {
|
||||||
[SystemIOMachineActors.readFoldersFromProjectDirectory]: fromPromise(
|
[SystemIOMachineActors.readFoldersFromProjectDirectory]: fromPromise(
|
||||||
@ -213,6 +235,23 @@ export const systemIOMachine = setup({
|
|||||||
return { value: true, error: undefined }
|
return { value: true, error: undefined }
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
[SystemIOMachineActors.deleteKCLFile]: fromPromise(
|
||||||
|
async ({
|
||||||
|
input,
|
||||||
|
}: {
|
||||||
|
input: {
|
||||||
|
context: SystemIOContext
|
||||||
|
requestedProjectName: string
|
||||||
|
requestedFileName: string
|
||||||
|
}
|
||||||
|
}): Promise<{
|
||||||
|
message: string
|
||||||
|
fileName: string
|
||||||
|
projectName: string
|
||||||
|
}> => {
|
||||||
|
return { message: '', fileName: '', projectName: '' }
|
||||||
|
}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
}).createMachine({
|
}).createMachine({
|
||||||
initial: SystemIOMachineStates.idle,
|
initial: SystemIOMachineStates.idle,
|
||||||
@ -231,6 +270,11 @@ export const systemIOMachine = setup({
|
|||||||
},
|
},
|
||||||
canReadWriteProjectDirectory: { value: true, error: undefined },
|
canReadWriteProjectDirectory: { value: true, error: undefined },
|
||||||
clearURLParams: { value: false },
|
clearURLParams: { value: false },
|
||||||
|
requestedTextToCadGeneration: {
|
||||||
|
requestedPrompt: '',
|
||||||
|
requestedProjectName: NO_PROJECT_DIRECTORY,
|
||||||
|
isProjectNew: true,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
states: {
|
states: {
|
||||||
[SystemIOMachineStates.idle]: {
|
[SystemIOMachineStates.idle]: {
|
||||||
@ -267,6 +311,12 @@ export const systemIOMachine = setup({
|
|||||||
[SystemIOMachineEvents.importFileFromURL]: {
|
[SystemIOMachineEvents.importFileFromURL]: {
|
||||||
target: SystemIOMachineStates.importFileFromURL,
|
target: SystemIOMachineStates.importFileFromURL,
|
||||||
},
|
},
|
||||||
|
[SystemIOMachineEvents.generateTextToCAD]: {
|
||||||
|
actions: [SystemIOMachineActions.setRequestedTextToCadGeneration],
|
||||||
|
},
|
||||||
|
[SystemIOMachineEvents.deleteKCLFile]: {
|
||||||
|
target: SystemIOMachineStates.deletingKCLFile,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[SystemIOMachineStates.readingFolders]: {
|
[SystemIOMachineStates.readingFolders]: {
|
||||||
@ -374,7 +424,7 @@ export const systemIOMachine = setup({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDone: {
|
onDone: {
|
||||||
target: SystemIOMachineStates.idle,
|
target: SystemIOMachineStates.readingFolders,
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
target: SystemIOMachineStates.idle,
|
target: SystemIOMachineStates.idle,
|
||||||
@ -440,5 +490,26 @@ export const systemIOMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[SystemIOMachineStates.deletingKCLFile]: {
|
||||||
|
invoke: {
|
||||||
|
id: SystemIOMachineActors.deleteKCLFile,
|
||||||
|
src: SystemIOMachineActors.deleteKCLFile,
|
||||||
|
input: ({ context, event }) => {
|
||||||
|
assertEvent(event, SystemIOMachineEvents.deleteKCLFile)
|
||||||
|
return {
|
||||||
|
context,
|
||||||
|
requestedProjectName: event.data.requestedProjectName,
|
||||||
|
requestedFileName: event.data.requestedFileName,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDone: {
|
||||||
|
target: SystemIOMachineStates.readingFolders,
|
||||||
|
},
|
||||||
|
onError: {
|
||||||
|
target: SystemIOMachineStates.readingFolders,
|
||||||
|
actions: [SystemIOMachineActions.toastError],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -228,5 +228,28 @@ export const systemIOMachineDesktop = systemIOMachine.provide({
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
[SystemIOMachineActors.deleteKCLFile]: fromPromise(
|
||||||
|
async ({
|
||||||
|
input,
|
||||||
|
}: {
|
||||||
|
input: {
|
||||||
|
context: SystemIOContext
|
||||||
|
requestedProjectName: string
|
||||||
|
requestedFileName: string
|
||||||
|
}
|
||||||
|
}) => {
|
||||||
|
const path = window.electron.path.join(
|
||||||
|
input.context.projectDirectoryPath,
|
||||||
|
input.requestedProjectName,
|
||||||
|
input.requestedFileName
|
||||||
|
)
|
||||||
|
await window.electron.rm(path)
|
||||||
|
return {
|
||||||
|
message: 'File deleted successfully',
|
||||||
|
projectName: input.requestedProjectName,
|
||||||
|
fileName: input.requestedFileName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -9,6 +9,7 @@ export enum SystemIOMachineActors {
|
|||||||
createKCLFile = 'create kcl file',
|
createKCLFile = 'create kcl file',
|
||||||
checkReadWrite = 'check read write',
|
checkReadWrite = 'check read write',
|
||||||
importFileFromURL = 'import file from URL',
|
importFileFromURL = 'import file from URL',
|
||||||
|
deleteKCLFile = 'delete kcl delete',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SystemIOMachineStates {
|
export enum SystemIOMachineStates {
|
||||||
@ -21,6 +22,7 @@ export enum SystemIOMachineStates {
|
|||||||
creatingKCLFile = 'creatingKCLFile',
|
creatingKCLFile = 'creatingKCLFile',
|
||||||
checkingReadWrite = 'checkingReadWrite',
|
checkingReadWrite = 'checkingReadWrite',
|
||||||
importFileFromURL = 'importFileFromURL',
|
importFileFromURL = 'importFileFromURL',
|
||||||
|
deletingKCLFile = 'deletingKCLFile',
|
||||||
}
|
}
|
||||||
|
|
||||||
const donePrefix = 'xstate.done.actor.'
|
const donePrefix = 'xstate.done.actor.'
|
||||||
@ -40,6 +42,8 @@ export enum SystemIOMachineEvents {
|
|||||||
done_checkReadWrite = donePrefix + 'check read write',
|
done_checkReadWrite = donePrefix + 'check read write',
|
||||||
importFileFromURL = 'import file from URL',
|
importFileFromURL = 'import file from URL',
|
||||||
done_importFileFromURL = donePrefix + 'import file from URL',
|
done_importFileFromURL = donePrefix + 'import file from URL',
|
||||||
|
generateTextToCAD = 'generate text to CAD',
|
||||||
|
deleteKCLFile = 'delete kcl file',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SystemIOMachineActions {
|
export enum SystemIOMachineActions {
|
||||||
@ -51,6 +55,7 @@ export enum SystemIOMachineActions {
|
|||||||
toastSuccess = 'toastSuccess',
|
toastSuccess = 'toastSuccess',
|
||||||
toastError = 'toastError',
|
toastError = 'toastError',
|
||||||
setReadWriteProjectDirectory = 'set read write project directory',
|
setReadWriteProjectDirectory = 'set read write project directory',
|
||||||
|
setRequestedTextToCadGeneration = 'set requested text to cad generation',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NO_PROJECT_DIRECTORY = ''
|
export const NO_PROJECT_DIRECTORY = ''
|
||||||
@ -70,4 +75,9 @@ export type SystemIOContext = {
|
|||||||
requestedFileName: { project: string; file: string }
|
requestedFileName: { project: string; file: string }
|
||||||
canReadWriteProjectDirectory: { value: boolean; error: unknown }
|
canReadWriteProjectDirectory: { value: boolean; error: unknown }
|
||||||
clearURLParams: { value: boolean }
|
clearURLParams: { value: boolean }
|
||||||
|
requestedTextToCadGeneration: {
|
||||||
|
requestedPrompt: string
|
||||||
|
requestedProjectName: string
|
||||||
|
isProjectNew: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user