From af9bc87330b56c3613c511b0410a142bd72f1725 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Thu, 6 Feb 2025 15:47:15 +0100 Subject: [PATCH 001/124] fix(writer): bump default model for workflow completion block - WF-175 --- src/writer/blocks/writercompletion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/writer/blocks/writercompletion.py b/src/writer/blocks/writercompletion.py index d1980397d..cc42b0be3 100644 --- a/src/writer/blocks/writercompletion.py +++ b/src/writer/blocks/writercompletion.py @@ -2,7 +2,7 @@ from writer.blocks.base_block import WorkflowBlock from writer.ss_types import AbstractTemplate -DEFAULT_MODEL = "palmyra-x-003-instruct" +DEFAULT_MODEL = "palmyra-x-004" class WriterCompletion(WorkflowBlock): From cf123279134f9ff167b121b0c0f89818f310cb25 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 27 Dec 2024 11:19:18 +0100 Subject: [PATCH 002/124] chore(ui): bump Vue.js to 3.3 --- package-lock.json | 2 +- src/ui/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e81e7f0d0..e6ebfccf2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24169,7 +24169,7 @@ "vega": "^5.22.1", "vega-embed": "^6.22.1", "vega-lite": "^5.7.1", - "vue": "^3.2.47", + "vue": "^3.3.0", "vue-dompurify-html": "^5.0.1" }, "devDependencies": { diff --git a/src/ui/package.json b/src/ui/package.json index a4071b3cb..f1a0b969d 100644 --- a/src/ui/package.json +++ b/src/ui/package.json @@ -38,7 +38,7 @@ "vega": "^5.22.1", "vega-embed": "^6.22.1", "vega-lite": "^5.7.1", - "vue": "^3.2.47", + "vue": "^3.3.0", "vue-dompurify-html": "^5.0.1" }, "devDependencies": { From dc20a285bfdc5697e239da7ac942e9e8d1dcf946 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 27 Dec 2024 11:22:51 +0100 Subject: [PATCH 003/124] chore(ui): bump Vue.js to 3.4 - WF-163 --- package-lock.json | 2 +- src/ui/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6ebfccf2..b3f098130 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24169,7 +24169,7 @@ "vega": "^5.22.1", "vega-embed": "^6.22.1", "vega-lite": "^5.7.1", - "vue": "^3.3.0", + "vue": "^3.4.0", "vue-dompurify-html": "^5.0.1" }, "devDependencies": { diff --git a/src/ui/package.json b/src/ui/package.json index f1a0b969d..4a7645f64 100644 --- a/src/ui/package.json +++ b/src/ui/package.json @@ -38,7 +38,7 @@ "vega": "^5.22.1", "vega-embed": "^6.22.1", "vega-lite": "^5.7.1", - "vue": "^3.3.0", + "vue": "^3.4.0", "vue-dompurify-html": "^5.0.1" }, "devDependencies": { From 5d07f120f268b3e5163dddaa3f05cc7cd910fb4d Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 27 Dec 2024 11:50:53 +0100 Subject: [PATCH 004/124] chore(ui): bump Vue.js to 3.5 - WF-163 --- package-lock.json | 134 ++++++++++-------- src/ui/package.json | 2 +- .../src/builder/BuilderEmbeddedCodeEditor.vue | 6 +- src/ui/src/builder/BuilderSelect.vue | 5 +- .../src/components/core/base/BaseDropdown.vue | 14 +- .../components/core/base/BaseInputColor.vue | 4 +- .../components/core/base/BaseInputSlider.vue | 6 +- .../core/base/BaseInputSliderRange.vue | 6 +- .../core/base/BaseInputSliderThumb.vue | 4 +- .../components/core/content/CoreAvatar.vue | 15 +- .../components/core/content/CoreDataframe.vue | 8 +- .../CoreDataframe/CoreDataframeCellNumber.vue | 6 +- .../CoreDataframe/CoreDataframeCellText.vue | 6 +- .../core/content/CoreProgressBar.vue | 4 +- .../workflows/base/WorkflowsNodeNamer.vue | 4 +- src/ui/src/wds/WdsDropdownMenu.vue | 4 +- src/ui/src/wds/WdsTextInput.vue | 4 +- 17 files changed, 132 insertions(+), 100 deletions(-) diff --git a/package-lock.json b/package-lock.json index b3f098130..c0f9aeff9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -549,16 +549,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -612,8 +614,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.0", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.3" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -2050,13 +2057,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "dev": true, + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -7878,45 +7885,53 @@ "license": "MIT" }, "node_modules/@vue/compiler-core": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.23.9", - "@vue/compiler-core": "3.4.21", - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21", + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", - "magic-string": "^0.30.7", - "postcss": "^8.4.35", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/eslint-config-prettier": { @@ -7956,42 +7971,53 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "license": "MIT", "dependencies": { - "@vue/shared": "3.4.21" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "license": "MIT", "dependencies": { - "@vue/runtime-core": "3.4.21", - "@vue/shared": "3.4.21", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.4.21" + "vue": "3.5.13" } }, "node_modules/@vue/shared": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "license": "MIT" }, "node_modules/@vue/test-utils": { @@ -21762,14 +21788,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "license": "MIT", @@ -23534,14 +23552,16 @@ } }, "node_modules/vue": { - "version": "3.4.21", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.21", - "@vue/compiler-sfc": "3.4.21", - "@vue/runtime-dom": "3.4.21", - "@vue/server-renderer": "3.4.21", - "@vue/shared": "3.4.21" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" @@ -24169,7 +24189,7 @@ "vega": "^5.22.1", "vega-embed": "^6.22.1", "vega-lite": "^5.7.1", - "vue": "^3.4.0", + "vue": "^3.5.0", "vue-dompurify-html": "^5.0.1" }, "devDependencies": { diff --git a/src/ui/package.json b/src/ui/package.json index 4a7645f64..319173d78 100644 --- a/src/ui/package.json +++ b/src/ui/package.json @@ -38,7 +38,7 @@ "vega": "^5.22.1", "vega-embed": "^6.22.1", "vega-lite": "^5.7.1", - "vue": "^3.4.0", + "vue": "^3.5.0", "vue-dompurify-html": "^5.0.1" }, "devDependencies": { diff --git a/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue b/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue index b734d088e..de5d53ca8 100644 --- a/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue +++ b/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue @@ -7,10 +7,10 @@ diff --git a/src/ui/src/builder/BuilderTree.vue b/src/ui/src/builder/BuilderTree.vue index a1fe46263..211b774b0 100644 --- a/src/ui/src/builder/BuilderTree.vue +++ b/src/ui/src/builder/BuilderTree.vue @@ -37,11 +37,12 @@
-
@@ -57,8 +58,9 @@ diff --git a/src/ui/src/wds/WdsButton.vue b/src/ui/src/wds/WdsButton.vue index c2246c44d..81ac35271 100644 --- a/src/ui/src/wds/WdsButton.vue +++ b/src/ui/src/wds/WdsButton.vue @@ -1,12 +1,16 @@ From 3aceb1d3f66cd04ed25a48401bf569db138e703e Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:24:36 +0000 Subject: [PATCH 015/124] feat: UI workflows link --- .../builder/settings/BuilderFieldsText.vue | 47 +++++++++++++++++-- src/ui/src/writerTypes.ts | 2 + src/writer/abstract.py | 2 +- src/writer/blocks/__init__.py | 2 + src/writer/blocks/base_trigger.py | 6 +++ src/writer/blocks/uieventtrigger.py | 41 ++++++++++++++++ 6 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 src/writer/blocks/base_trigger.py create mode 100644 src/writer/blocks/uieventtrigger.py diff --git a/src/ui/src/builder/settings/BuilderFieldsText.vue b/src/ui/src/builder/settings/BuilderFieldsText.vue index 310854cde..aef85eb8c 100644 --- a/src/ui/src/builder/settings/BuilderFieldsText.vue +++ b/src/ui/src/builder/settings/BuilderFieldsText.vue @@ -57,14 +57,51 @@ const templateField = computed(() => { const inputId = computed(() => `${props.componentId}-${props.fieldKey}`); +const predefinedOptionFns = { + uiComponents: () => { + const uiComponents = wf + .getComponents(undefined, { sortedByPosition: true }) + .filter((c) => wf.isChildOf("root", c.id)); + const options = {}; + uiComponents.forEach((component) => { + options[component.id] = component.id; + }); + return options; + }, + uiComponentsWithEvents: () => { + const uiComponents = wf + .getComponents(undefined, { sortedByPosition: true }) + .filter((c) => wf.isChildOf("root", c.id)) + .filter((c) => Boolean(wf.getComponentDefinition(c.type).events)); + const options = {}; + uiComponents.forEach((component) => { + options[component.id] = component.id; + }); + return options; + }, + eventTypes: (core: typeof wf, componentId: Component["id"]) => { + const refComponentId = + core.getComponentById(componentId).content?.["refComponentId"]; + const refComponent = wf.getComponentById(refComponentId); + if (!refComponent) return {}; + const refDef = core.getComponentDefinition(refComponent.type); + const refEvents = Object.fromEntries( + Object.keys(refDef.events ?? {}).map((k) => [k, k]), + ); + return refEvents; + }, +}; + const options = computed(() => { const field = templateField.value; - if (field.options) { - return typeof field.options === "function" - ? field.options(wf, componentId.value) - : field.options; + if (!field.options) return {}; + if (typeof field.options === "function") { + return field.options(wf, componentId.value); + } + if (typeof field.options === "string") { + return predefinedOptionFns?.[field.options](wf, componentId.value); } - return {}; + return field.options; }); const handleInput = (ev: Event) => { diff --git a/src/ui/src/writerTypes.ts b/src/ui/src/writerTypes.ts index 6d7d88feb..befd78005 100644 --- a/src/ui/src/writerTypes.ts +++ b/src/ui/src/writerTypes.ts @@ -69,6 +69,7 @@ export type WriterComponentDefinitionField = { control?: FieldControl; options?: | Record + | string // For predefined functions | ((wf?: Core, componentId?: ComponentId) => Record); // List of values to be provided as autocomplete options /** Data type for the field */ type: FieldType; @@ -133,6 +134,7 @@ export const enum FieldType { VAlign = "Align (V)", Padding = "Padding", Tools = "Tools", + ComponentPicker = "Component", } export const enum FieldCategory { diff --git a/src/writer/abstract.py b/src/writer/abstract.py index b0ce57f98..80b6c19e9 100644 --- a/src/writer/abstract.py +++ b/src/writer/abstract.py @@ -13,4 +13,4 @@ templates:Dict[str, AbstractTemplate] = {} def register_abstract_template(type: str, abstract_template: AbstractTemplate): - templates[type] = abstract_template \ No newline at end of file + templates[type] = abstract_template diff --git a/src/writer/blocks/__init__.py b/src/writer/blocks/__init__.py index 66bfdece4..9827db035 100644 --- a/src/writer/blocks/__init__.py +++ b/src/writer/blocks/__init__.py @@ -14,6 +14,7 @@ from writer.blocks.writercompletion import WriterCompletion from writer.blocks.writerinitchat import WriterInitChat from writer.blocks.writernocodeapp import WriterNoCodeApp +from writer.blocks.uieventtrigger import UIEventTrigger SetState.register("workflows_setstate") WriterClassification.register("workflows_writerclassification") @@ -31,3 +32,4 @@ ReturnValue.register("workflows_returnvalue") WriterInitChat.register("workflows_writerinitchat") WriterAddToKG.register("workflows_writeraddtokg") +UIEventTrigger.register("workflows_uieventtrigger") diff --git a/src/writer/blocks/base_trigger.py b/src/writer/blocks/base_trigger.py new file mode 100644 index 000000000..d58e16359 --- /dev/null +++ b/src/writer/blocks/base_trigger.py @@ -0,0 +1,6 @@ +from writer.blocks.base_block import WorkflowBlock + +class WorkflowTrigger(WorkflowBlock): + + def run(self): + pass \ No newline at end of file diff --git a/src/writer/blocks/uieventtrigger.py b/src/writer/blocks/uieventtrigger.py new file mode 100644 index 000000000..8947b7070 --- /dev/null +++ b/src/writer/blocks/uieventtrigger.py @@ -0,0 +1,41 @@ +from writer.abstract import register_abstract_template +from writer.blocks.base_trigger import WorkflowTrigger +from writer.ss_types import AbstractTemplate + + +class UIEventTrigger(WorkflowTrigger): + + @classmethod + def register(cls, type: str): + super(UIEventTrigger, cls).register(type) + register_abstract_template(type, AbstractTemplate( + baseType="workflows_node", + writer={ + "name": "UI Trigger", + "description": "Trigger the workflow when an UI event takes place.", + "category": "Writer", + "fields": { + "refComponentId": { + "name": "Component Id", + "type": "Text", + "options": "uiComponentsWithEvents", + "desc": "The id of the component that will trigger this branch.", + }, + "refEventType": { + "name": "Event type", + "type": "Text", + "desc": "The type of the event that will trigger this branch. For example, wf-click.", + "options": "eventTypes", + }, + }, + "outs": { + "trigger": { + "name": "Trigger", + "style": "success", + }, + }, + } + )) + + def run(self): + pass \ No newline at end of file From b6b14f3dc0f9535c5fd1dcf59a848ce71076d98f Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:10:43 +0100 Subject: [PATCH 016/124] feat: UI workflows link --- .../settings/BuilderSettingsHandlers.vue | 49 ++++++++++++++++--- src/ui/src/builder/useComponentActions.ts | 6 +-- src/ui/src/renderer/ComponentProxy.vue | 40 +++------------ src/ui/src/renderer/useFieldsErrors.ts | 2 + src/writer/core.py | 3 +- src/writer/workflows.py | 13 ++++- 6 files changed, 70 insertions(+), 43 deletions(-) diff --git a/src/ui/src/builder/settings/BuilderSettingsHandlers.vue b/src/ui/src/builder/settings/BuilderSettingsHandlers.vue index 13ab6facc..e0c4e33b4 100644 --- a/src/ui/src/builder/settings/BuilderSettingsHandlers.vue +++ b/src/ui/src/builder/settings/BuilderSettingsHandlers.vue @@ -1,5 +1,5 @@ diff --git a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellText.vue b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellText.vue index fc501810f..24fab58a9 100644 --- a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellText.vue +++ b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellText.vue @@ -7,6 +7,7 @@ const props = defineProps({ value: { validator: () => true, required: true }, useMarkdown: { type: Boolean, required: false }, editable: { type: Boolean, required: false }, + wrapText: { type: Boolean, required: false }, }); const emits = defineEmits({ @@ -33,7 +34,9 @@ function stopEditing() { />
-

{{ value }}

+

+ {{ value }} +

@@ -50,7 +53,7 @@ function stopEditing() { width: 100%; } -.CoreDataframeCellText__content { +.CoreDataframeCellText__content--noWrap { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue index d43baa13e..6c7e9d448 100644 --- a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue +++ b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue @@ -4,6 +4,7 @@ import { computed } from "vue"; const props = defineProps({ value: { validator: () => true, required: true }, + wrapText: { type: Boolean, required: false }, }); const text = computed(() => JSON.stringify(props.value, bigIntReplacer)); @@ -11,7 +12,9 @@ const text = computed(() => JSON.stringify(props.value, bigIntReplacer)); @@ -25,7 +28,7 @@ const text = computed(() => JSON.stringify(props.value, bigIntReplacer)); pointer-events: none; } -.CoreDataframeCellUnknown__content { +.CoreDataframeCellUnknown__content--noWrap { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; diff --git a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue index daa9fcbeb..36dbaa059 100644 --- a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue +++ b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue @@ -23,6 +23,7 @@ :value="row[columnName]" :use-markdown="useMarkdown" :editable="editable && (isRowHovered || hasFocusWithin)" + :wrap-text="wrapText" class="CoreDataframeRow__cell__content" @change=" $emit( @@ -62,6 +63,7 @@ import { useFocusWithin } from "@/composables/useFocusWithin"; const props = defineProps({ showIndex: { type: Boolean, required: false }, + wrapText: { type: Boolean, required: false }, indexText: { type: [Number, String], required: true }, columns: { type: Array as PropType, required: true }, row: { type: Object, required: true }, From 324a5e5defc83af16843bd67c801faecc825ab0a Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:28:32 +0000 Subject: [PATCH 019/124] feat: UI workflows link --- src/ui/src/builder/BuilderSwitcher.vue | 6 ------ src/ui/src/components/core/root/CorePage.vue | 2 ++ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ui/src/builder/BuilderSwitcher.vue b/src/ui/src/builder/BuilderSwitcher.vue index 1d3aa15e5..6e75fe16e 100644 --- a/src/ui/src/builder/BuilderSwitcher.vue +++ b/src/ui/src/builder/BuilderSwitcher.vue @@ -9,7 +9,6 @@ UI
- wf.featureFlags.value.includes("workflows"), -); - let selectedId: Ref = ref(null); const selectOption = (optionId: "ui" | "preview" | "workflows") => { diff --git a/src/ui/src/components/core/root/CorePage.vue b/src/ui/src/components/core/root/CorePage.vue index e094c2c4a..9b939ee67 100644 --- a/src/ui/src/components/core/root/CorePage.vue +++ b/src/ui/src/components/core/root/CorePage.vue @@ -127,6 +127,8 @@ const fields = inject(injectionKeys.evaluatedFields); function handleKeydown(ev: KeyboardEvent) { const ssEv = getKeydown(ev); + const targetEl = ev.target as HTMLElement; + if (!rootEl.value.contains(targetEl)) return; rootEl.value.dispatchEvent(ssEv); } From 39e58c8bb57395443098514f90d86f5a0cc04335 Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:40:04 +0000 Subject: [PATCH 020/124] feat: UI workflow link --- src/writer/blocks/__init__.py | 2 +- src/writer/blocks/base_trigger.py | 1 + src/writer/blocks/calleventhandler.py | 2 +- src/writer/workflows.py | 5 ++--- tests/backend/blocks/test_calleventhandler.py | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/writer/blocks/__init__.py b/src/writer/blocks/__init__.py index 9827db035..3c2fd258f 100644 --- a/src/writer/blocks/__init__.py +++ b/src/writer/blocks/__init__.py @@ -7,6 +7,7 @@ from writer.blocks.returnvalue import ReturnValue from writer.blocks.runworkflow import RunWorkflow from writer.blocks.setstate import SetState +from writer.blocks.uieventtrigger import UIEventTrigger from writer.blocks.writeraddchatmessage import WriterAddChatMessage from writer.blocks.writeraddtokg import WriterAddToKG from writer.blocks.writerchat import WriterChat @@ -14,7 +15,6 @@ from writer.blocks.writercompletion import WriterCompletion from writer.blocks.writerinitchat import WriterInitChat from writer.blocks.writernocodeapp import WriterNoCodeApp -from writer.blocks.uieventtrigger import UIEventTrigger SetState.register("workflows_setstate") WriterClassification.register("workflows_writerclassification") diff --git a/src/writer/blocks/base_trigger.py b/src/writer/blocks/base_trigger.py index d58e16359..7983a0af0 100644 --- a/src/writer/blocks/base_trigger.py +++ b/src/writer/blocks/base_trigger.py @@ -1,5 +1,6 @@ from writer.blocks.base_block import WorkflowBlock + class WorkflowTrigger(WorkflowBlock): def run(self): diff --git a/src/writer/blocks/calleventhandler.py b/src/writer/blocks/calleventhandler.py index 7a349e4ee..fd6125213 100644 --- a/src/writer/blocks/calleventhandler.py +++ b/src/writer/blocks/calleventhandler.py @@ -59,7 +59,7 @@ def run(self): "state": self.runner.session.session_state, "context": self.execution_environment, "session": writer.core._event_handler_session_info(), - "ui": writer.core_event_handler_ui_manager() + "ui": writer.core._event_handler_ui_manager() } | additional_args handler_args = inspect.getfullargspec(callable_handler).args diff --git a/src/writer/workflows.py b/src/writer/workflows.py index 154f518ec..cd08f161e 100644 --- a/src/writer/workflows.py +++ b/src/writer/workflows.py @@ -1,14 +1,13 @@ import json import time -from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple, Type +from typing import Any, Dict, List, Literal, Optional, Tuple, Type import writer.blocks import writer.blocks.base_block -from writer.blocks.base_trigger import WorkflowTrigger import writer.core import writer.core_ui from writer.ss_types import WorkflowExecutionLog, WriterConfigurationError -import logging + class WorkflowRunner(): diff --git a/tests/backend/blocks/test_calleventhandler.py b/tests/backend/blocks/test_calleventhandler.py index 726418fb8..12a427826 100644 --- a/tests/backend/blocks/test_calleventhandler.py +++ b/tests/backend/blocks/test_calleventhandler.py @@ -25,8 +25,8 @@ class MockAppProcess(): def __init__(self): self.handler_registry = MockHandlerRegistry() -def test_call_event_handler(session, runner): - writer.core.get_app_process = lambda: MockAppProcess() +def test_call_event_handler(session, runner, monkeypatch): + monkeypatch.setattr("writer.core.get_app_process", lambda: MockAppProcess()) session.add_fake_component({ "name": "valid_handler" }) @@ -36,8 +36,8 @@ def test_call_event_handler(session, runner): assert block.result == 1 assert session.session_state["animal"] == "duck" -def test_invalid_json(session, runner): - writer.core.get_app_process = lambda: MockAppProcess() +def test_invalid_json(session, runner, monkeypatch): + monkeypatch.setattr("writer.core.get_app_process", lambda: MockAppProcess()) session.add_fake_component({ "name": "invalid_handler" }) From 0ff7de8736c3cb372cd05f11d462460d02e78bc0 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Thu, 20 Feb 2025 12:45:26 +0100 Subject: [PATCH 021/124] feat(ui): remove legacy dataframe - AI-23 --- .../core/content/CoreDataframeLegacy.vue | 661 ------------------ src/ui/src/core/index.ts | 11 - src/ui/src/core/templateMap.ts | 2 +- 3 files changed, 1 insertion(+), 673 deletions(-) delete mode 100644 src/ui/src/components/core/content/CoreDataframeLegacy.vue diff --git a/src/ui/src/components/core/content/CoreDataframeLegacy.vue b/src/ui/src/components/core/content/CoreDataframeLegacy.vue deleted file mode 100644 index e5cd3c620..000000000 --- a/src/ui/src/components/core/content/CoreDataframeLegacy.vue +++ /dev/null @@ -1,661 +0,0 @@ - - - - - - diff --git a/src/ui/src/core/index.ts b/src/ui/src/core/index.ts index dd9ea56e0..6d77503af 100644 --- a/src/ui/src/core/index.ts +++ b/src/ui/src/core/index.ts @@ -14,7 +14,6 @@ import { getSupportedComponentTypes, getComponentDefinition, registerAbstractComponentTemplate, - registerComponentTemplate, } from "./templateMap"; import * as typeHierarchy from "./typeHierarchy"; import { auditAndFixComponents } from "./auditAndFix"; @@ -111,16 +110,6 @@ export function generateCore() { featureFlags.value = initData.featureFlags; loadAbstractTemplates(initData.abstractTemplates); - // put some components behind feature flag - - if (featureFlags.value.includes("dataframeEditor")) { - const component = await import( - "@/components/core/content/CoreDataframe.vue" - ).then((m) => m.default); - - registerComponentTemplate("dataframe", component); - } - // Only returned for edit (Builder) mode userFunctions.value = initData.userFunctions; diff --git a/src/ui/src/core/templateMap.ts b/src/ui/src/core/templateMap.ts index ec4b344f2..dde1d6c0e 100644 --- a/src/ui/src/core/templateMap.ts +++ b/src/ui/src/core/templateMap.ts @@ -1,7 +1,7 @@ import type { Component as VueComponent } from "vue"; // Maps Writer Framework component types to renderable Vue components // content -import CoreDataframe from "../components/core/content/CoreDataframeLegacy.vue"; +import CoreDataframe from "../components/core/content/CoreDataframe.vue"; import CoreHeading from "../components/core/content/CoreHeading.vue"; import CoreIcon from "../components/core/content/CoreIcon.vue"; import CoreImage from "../components/core/content/CoreImage.vue"; From da37068a462608604deb9e0c81e6e5d64fc35425 Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Thu, 20 Feb 2025 12:28:49 +0000 Subject: [PATCH 022/124] fix: Remove unused variable --- src/ui/src/builder/settings/BuilderSettingsHandlers.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/src/builder/settings/BuilderSettingsHandlers.vue b/src/ui/src/builder/settings/BuilderSettingsHandlers.vue index e0c4e33b4..497c99f86 100644 --- a/src/ui/src/builder/settings/BuilderSettingsHandlers.vue +++ b/src/ui/src/builder/settings/BuilderSettingsHandlers.vue @@ -339,7 +339,7 @@ async function createLinkedWorkflow(refEventType: string) { "workflows_workflow", "workflows_root", ); - const triggerId = createAndInsertComponent( + createAndInsertComponent( "workflows_uieventtrigger", workflowId, undefined, From dc4eecbfbd233ec3dcdb6c86d3e17999838ba393 Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Thu, 20 Feb 2025 14:26:31 +0100 Subject: [PATCH 023/124] Update src/ui/src/builder/settings/BuilderSettingsHandlers.vue Co-authored-by: Alexandre Rousseau --- src/ui/src/builder/settings/BuilderSettingsHandlers.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/src/builder/settings/BuilderSettingsHandlers.vue b/src/ui/src/builder/settings/BuilderSettingsHandlers.vue index 497c99f86..2e4ead441 100644 --- a/src/ui/src/builder/settings/BuilderSettingsHandlers.vue +++ b/src/ui/src/builder/settings/BuilderSettingsHandlers.vue @@ -32,7 +32,7 @@
addCreate linked workflow From 9ccfc65b28e7a58a055afd3849418700e9a62cbe Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Thu, 20 Feb 2025 14:27:10 +0100 Subject: [PATCH 024/124] Update src/ui/src/builder/settings/BuilderFieldsText.vue Co-authored-by: Alexandre Rousseau --- src/ui/src/builder/settings/BuilderFieldsText.vue | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ui/src/builder/settings/BuilderFieldsText.vue b/src/ui/src/builder/settings/BuilderFieldsText.vue index aef85eb8c..6912722e8 100644 --- a/src/ui/src/builder/settings/BuilderFieldsText.vue +++ b/src/ui/src/builder/settings/BuilderFieldsText.vue @@ -69,15 +69,14 @@ const predefinedOptionFns = { return options; }, uiComponentsWithEvents: () => { - const uiComponents = wf + return wf .getComponents(undefined, { sortedByPosition: true }) .filter((c) => wf.isChildOf("root", c.id)) - .filter((c) => Boolean(wf.getComponentDefinition(c.type).events)); - const options = {}; - uiComponents.forEach((component) => { - options[component.id] = component.id; - }); - return options; + .filter((c) => Boolean(wf.getComponentDefinition(c.type).events)) + .reduce((acc, component) => { + acc[component.id] = [component.id] + return acc; + }, {}); }, eventTypes: (core: typeof wf, componentId: Component["id"]) => { const refComponentId = From 7fdb201312743f3e9cf31ea1291cdef8c0c46f19 Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Thu, 20 Feb 2025 13:36:49 +0000 Subject: [PATCH 025/124] feat: Code block --- src/writer/blocks/code.py | 73 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/writer/blocks/code.py diff --git a/src/writer/blocks/code.py b/src/writer/blocks/code.py new file mode 100644 index 000000000..31d08d9dc --- /dev/null +++ b/src/writer/blocks/code.py @@ -0,0 +1,73 @@ +import inspect + +import writer.core +from writer.abstract import register_abstract_template +from writer.blocks.base_block import WorkflowBlock +from writer.ss_types import AbstractTemplate + + +class CallEventHandler(WorkflowBlock): + + @classmethod + def register(cls, type: str): + super(CallEventHandler, cls).register(type) + register_abstract_template(type, AbstractTemplate( + baseType="workflows_node", + writer={ + "name": "Call event handler", + "description": "Executes an event handler.", + "category": "Logic", + "fields": { + "name": { + "name": "Name", + "type": "Text", + "desc": "The name of the event handling function." + }, + "additionalArgs": { + "name": "Additional arguments", + "init": '{ "my_arg": 2 }', + "type": "Object", + "control": "Textarea", + "default": "{}" + }, + }, + "outs": { + "success": { + "name": "Success", + "description": "The event handler execution was successful.", + "style": "success", + }, + "error": { + "name": "Error", + "description": "The event handler execution wasn't successful.", + "style": "error", + }, + }, + } + )) + + def run(self): + try: + handler_name = self._get_field("name") + additional_args = self._get_field("additionalArgs", as_json=True) + + current_app_process = writer.core.get_app_process() + handler_registry = current_app_process.handler_registry + callable_handler = handler_registry.find_handler_callable(handler_name) + + args = { + "state": self.runner.session.session_state, + "context": self.execution_environment, + "session": writer.core._event_handler_session_info(), + } | additional_args + + handler_args = inspect.getfullargspec(callable_handler).args + func_args = [] + for arg in handler_args: + if arg in args: + func_args.append(args[arg]) + self.result = callable_handler(*func_args) + self.outcome = "success" + except BaseException as e: + self.outcome = "error" + raise e \ No newline at end of file From 71aeac9284f318a3d3d2ee9a89fa424f31a2abdc Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Thu, 20 Feb 2025 14:13:12 +0000 Subject: [PATCH 026/124] feat: UI workflow link --- .../builder/settings/BuilderFieldsText.vue | 2 +- .../settings/BuilderSettingsHandlers.vue | 30 +++++++++++-------- src/ui/src/builder/useComponentActions.ts | 15 ++++------ src/ui/src/renderer/useFieldsErrors.ts | 5 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/ui/src/builder/settings/BuilderFieldsText.vue b/src/ui/src/builder/settings/BuilderFieldsText.vue index 6912722e8..00375104a 100644 --- a/src/ui/src/builder/settings/BuilderFieldsText.vue +++ b/src/ui/src/builder/settings/BuilderFieldsText.vue @@ -74,7 +74,7 @@ const predefinedOptionFns = { .filter((c) => wf.isChildOf("root", c.id)) .filter((c) => Boolean(wf.getComponentDefinition(c.type).events)) .reduce((acc, component) => { - acc[component.id] = [component.id] + acc[component.id] = [component.id]; return acc; }, {}); }, diff --git a/src/ui/src/builder/settings/BuilderSettingsHandlers.vue b/src/ui/src/builder/settings/BuilderSettingsHandlers.vue index 2e4ead441..ecbb849f8 100644 --- a/src/ui/src/builder/settings/BuilderSettingsHandlers.vue +++ b/src/ui/src/builder/settings/BuilderSettingsHandlers.vue @@ -338,23 +338,27 @@ async function createLinkedWorkflow(refEventType: string) { const workflowId = createAndInsertComponent( "workflows_workflow", "workflows_root", - ); - createAndInsertComponent( - "workflows_uieventtrigger", - workflowId, undefined, - { - content: { - alias: `${name} - ${refEventType}`, - refComponentId: component.value.id, - refEventType, - }, - x: 48, - y: 200, + undefined, + (parentId) => { + createAndInsertComponent( + "workflows_uieventtrigger", + parentId, + undefined, + { + content: { + alias: `${name} - ${refEventType}`, + refComponentId: component.value.id, + refEventType, + }, + x: 96, + y: 96, + }, + ); }, ); + wf.setActivePageId(workflowId); - await nextTick(); wfbm.setMode("workflows"); wfbm.setSelection(workflowId); } diff --git a/src/ui/src/builder/useComponentActions.ts b/src/ui/src/builder/useComponentActions.ts index 37ef0ddf5..6f9bd4451 100644 --- a/src/ui/src/builder/useComponentActions.ts +++ b/src/ui/src/builder/useComponentActions.ts @@ -128,10 +128,7 @@ export function useComponentActions(wf: Core, ssbm: BuilderManager) { parentId: Component["id"], position?: number, initProperties?: Partial< - Omit< - Component, - "id" | "type" | "parent" | "handlers" | "position" - > + Omit >, ) { const newId = generateNewComponentId(); @@ -139,7 +136,8 @@ export function useComponentActions(wf: Core, ssbm: BuilderManager) { const { fields } = definition; const initContent = {}; Object.entries(fields ?? {}).map(([fieldKey, field]) => { - initContent[fieldKey] = initProperties?.["content"]?.[fieldKey] ?? field.init; + initContent[fieldKey] = + initProperties?.["content"]?.[fieldKey] ?? field.init; }); const component = { @@ -168,11 +166,9 @@ export function useComponentActions(wf: Core, ssbm: BuilderManager) { parentId: Component["id"], position?: number, initProperties?: Partial< - Omit< - Component, - "id" | "type" | "parent" | "handlers" | "position" - > + Omit >, + initializer?: (parentId: Component["id"]) => void, ): Component["id"] { const component = createComponent( type, @@ -184,6 +180,7 @@ export function useComponentActions(wf: Core, ssbm: BuilderManager) { ssbm.openMutationTransaction(transactionId, `Create`); wf.addComponent(component); repositionHigherSiblings(component.id, 1); + initializer?.(component.id); ssbm.registerPostMutation(component); ssbm.closeMutationTransaction(transactionId); wf.sendComponentUpdate(); diff --git a/src/ui/src/renderer/useFieldsErrors.ts b/src/ui/src/renderer/useFieldsErrors.ts index 265d54e37..142c86a4a 100644 --- a/src/ui/src/renderer/useFieldsErrors.ts +++ b/src/ui/src/renderer/useFieldsErrors.ts @@ -17,12 +17,11 @@ export function useFieldsErrors( wf: Core, instancePath: ComputedRef, ) { - if (!instancePath.value.at(-1)) return computed(() => ({})); - const { getEvaluatedFields } = useEvaluator(wf); const componentFields = computed(() => { - const { componentId } = instancePath.value.at(-1); + const componentId = instancePath.value.at(-1)?.componentId; + if (componentId === undefined) return {}; const component = wf.getComponentById(componentId); if (!component) return {}; From fb4b01d7cf49aa8ba08ca42ab7457ed947ce6cc1 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 14 Feb 2025 16:59:15 +0100 Subject: [PATCH 027/124] feat(ui): improve component tree search. WF-183 When searching a component in the tree, we start to display only component matching the query, or those containing a children matching the query. --- .../BuilderSidebarComponentTreeBranch.vue | 26 ++++++++++++++++--- src/ui/src/core/index.ts | 22 ++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue b/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue index 6d4fea6f2..24b5fe0cb 100644 --- a/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue +++ b/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue @@ -1,5 +1,6 @@ @@ -86,7 +89,7 @@ const emit = defineEmits({ dropdownSelect: (key: string) => typeof key === "string", }); -defineExpose({ expand }); +defineExpose({ expand, toggleCollapse }); const collapsed = ref(false); const isMainHovered = ref(false); @@ -100,8 +103,9 @@ function expand() { emit("expandBranch"); } -function toggleCollapse() { - collapsed.value = !collapsed.value; +function toggleCollapse(newCollapse?: boolean) { + newCollapse ??= !collapsed.value; + if (newCollapse !== collapsed.value) collapsed.value = newCollapse; } @@ -166,4 +170,26 @@ function toggleCollapse() { color: var(--builderPrimaryTextColor); --containerShadow: var(--wdsShadowMenu); } + +.slide-fade-enter-active { + transition: all 0.1s ease-out; +} + +.slide-fade-leave-active { + transition: all 0.1s ease-in; +} + +.slide-fade-enter-from, +.slide-fade-leave-to { + transform: translateY(-18px); + opacity: 0; +} +@media (prefers-reduced-motion) { + .slide-fade-enter-from, + .slide-fade-leave-to, + .slide-fade-enter-active, + .slide-fade-leave-active { + transition: unset; + } +} diff --git a/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue b/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue index 24b5fe0cb..f10a9ded7 100644 --- a/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue +++ b/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue @@ -1,6 +1,5 @@ @@ -50,7 +53,7 @@ function stopEditing() { width: 100%; } -.CoreDataframeCellText__content { +.CoreDataframeCellText__content--noWrap { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue index d43baa13e..6c7e9d448 100644 --- a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue +++ b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeCellUnknown.vue @@ -4,6 +4,7 @@ import { computed } from "vue"; const props = defineProps({ value: { validator: () => true, required: true }, + wrapText: { type: Boolean, required: false }, }); const text = computed(() => JSON.stringify(props.value, bigIntReplacer)); @@ -11,7 +12,9 @@ const text = computed(() => JSON.stringify(props.value, bigIntReplacer)); @@ -25,7 +28,7 @@ const text = computed(() => JSON.stringify(props.value, bigIntReplacer)); pointer-events: none; } -.CoreDataframeCellUnknown__content { +.CoreDataframeCellUnknown__content--noWrap { text-overflow: ellipsis; overflow: hidden; white-space: nowrap; diff --git a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue index daa9fcbeb..36dbaa059 100644 --- a/src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue +++ b/src/ui/src/components/core/content/CoreDataframe/CoreDataframeRow.vue @@ -23,6 +23,7 @@ :value="row[columnName]" :use-markdown="useMarkdown" :editable="editable && (isRowHovered || hasFocusWithin)" + :wrap-text="wrapText" class="CoreDataframeRow__cell__content" @change=" $emit( @@ -62,6 +63,7 @@ import { useFocusWithin } from "@/composables/useFocusWithin"; const props = defineProps({ showIndex: { type: Boolean, required: false }, + wrapText: { type: Boolean, required: false }, indexText: { type: [Number, String], required: true }, columns: { type: Array as PropType, required: true }, row: { type: Object, required: true }, From 9efba1f747b86e469bb1982f7e17cbd6a75ce285 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Thu, 20 Feb 2025 12:45:26 +0100 Subject: [PATCH 047/124] feat(ui): remove legacy dataframe - AI-23 --- .../core/content/CoreDataframeLegacy.vue | 661 ------------------ src/ui/src/core/index.ts | 11 - src/ui/src/core/templateMap.ts | 2 +- 3 files changed, 1 insertion(+), 673 deletions(-) delete mode 100644 src/ui/src/components/core/content/CoreDataframeLegacy.vue diff --git a/src/ui/src/components/core/content/CoreDataframeLegacy.vue b/src/ui/src/components/core/content/CoreDataframeLegacy.vue deleted file mode 100644 index e5cd3c620..000000000 --- a/src/ui/src/components/core/content/CoreDataframeLegacy.vue +++ /dev/null @@ -1,661 +0,0 @@ - - - - - - diff --git a/src/ui/src/core/index.ts b/src/ui/src/core/index.ts index dd9ea56e0..6d77503af 100644 --- a/src/ui/src/core/index.ts +++ b/src/ui/src/core/index.ts @@ -14,7 +14,6 @@ import { getSupportedComponentTypes, getComponentDefinition, registerAbstractComponentTemplate, - registerComponentTemplate, } from "./templateMap"; import * as typeHierarchy from "./typeHierarchy"; import { auditAndFixComponents } from "./auditAndFix"; @@ -111,16 +110,6 @@ export function generateCore() { featureFlags.value = initData.featureFlags; loadAbstractTemplates(initData.abstractTemplates); - // put some components behind feature flag - - if (featureFlags.value.includes("dataframeEditor")) { - const component = await import( - "@/components/core/content/CoreDataframe.vue" - ).then((m) => m.default); - - registerComponentTemplate("dataframe", component); - } - // Only returned for edit (Builder) mode userFunctions.value = initData.userFunctions; diff --git a/src/ui/src/core/templateMap.ts b/src/ui/src/core/templateMap.ts index ec4b344f2..dde1d6c0e 100644 --- a/src/ui/src/core/templateMap.ts +++ b/src/ui/src/core/templateMap.ts @@ -1,7 +1,7 @@ import type { Component as VueComponent } from "vue"; // Maps Writer Framework component types to renderable Vue components // content -import CoreDataframe from "../components/core/content/CoreDataframeLegacy.vue"; +import CoreDataframe from "../components/core/content/CoreDataframe.vue"; import CoreHeading from "../components/core/content/CoreHeading.vue"; import CoreIcon from "../components/core/content/CoreIcon.vue"; import CoreImage from "../components/core/content/CoreImage.vue"; From bb43636482ddfd5b676aac44ad57cea725dba909 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 14 Feb 2025 10:39:25 +0100 Subject: [PATCH 048/124] feat(writer): include description in `GraphTool` Add a `description` field in `GraphTool` and use the tool's name in the Writer's chat workflow block as graph description. --- src/writer/ai.py | 4 +++- src/writer/blocks/writerchat.py | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/writer/ai.py b/src/writer/ai.py index fc3083aca..d43e1952c 100644 --- a/src/writer/ai.py +++ b/src/writer/ai.py @@ -88,6 +88,7 @@ class Tool(TypedDict, total=False): class GraphTool(Tool): graph_ids: List[str] subqueries: bool + description: Optional[str] class FunctionToolParameterMeta(TypedDict): @@ -1419,7 +1420,8 @@ def validate_graph_ids(graph_ids: List[str]) -> bool: "graph_ids": tool_instance["graph_ids"], "subqueries": tool_instance.get( "subqueries", False - ) + ), + "description": tool_instance.get("description", None) } } ) diff --git a/src/writer/blocks/writerchat.py b/src/writer/blocks/writerchat.py index 4a3f73f7a..2cf8ead73 100644 --- a/src/writer/blocks/writerchat.py +++ b/src/writer/blocks/writerchat.py @@ -90,10 +90,12 @@ def run(self): parameters=tool_raw.get("parameters") ) elif tool_type == "graph": - tool = { - "type": "graph", - "graph_ids": tool_raw.get("graph_ids") - } + tool = writer.ai.GraphTool( + type="graph", + graph_ids=tool_raw.get("graph_ids"), + subqueries=False, + description=tool_name, + ) else: continue tools.append(tool) From 6e03274b6dc86588f2f1338a3783d1e1cb067dfe Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 14 Feb 2025 15:40:35 +0100 Subject: [PATCH 049/124] fix(ui): improve text input ghost variant - WF-184 --- src/ui/src/wds/WdsTextInput.vue | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/ui/src/wds/WdsTextInput.vue b/src/ui/src/wds/WdsTextInput.vue index bf55b80f6..942a658ac 100644 --- a/src/ui/src/wds/WdsTextInput.vue +++ b/src/ui/src/wds/WdsTextInput.vue @@ -77,6 +77,7 @@ function focus() { outline: none; color: var(--primaryTextColor); background: transparent; + transition: box-shadow ease-in-out 0.2s; } .WdsTextInput:focus, @@ -87,15 +88,22 @@ function focus() { .WdsTextInput--ghost { border-color: transparent; -} -.WdsTextInput--ghost:hover { background-color: var(--wdsColorGray1); + transition: + box-shadow, + background-color, + border-color ease-in-out 0.2s; } -.WdsTextInput--ghost:focus, -.WdsTextInput--ghost:focus-within { - background-color: var(--wdsColorGray1); - border-color: transparent; - box-shadow: unset; +.WdsTextInput--ghost:disabled { + cursor: not-allowed; +} +.WdsTextInput--ghost:not(:disabled):hover { + background-color: transparent; + border-color: var(--wdsColorBlue3); +} +.WdsTextInput--ghost:not(:disabled):focus, +.WdsTextInput--ghost:not(:disabled):focus-within { + background-color: transparent; } .WdsTextInput--leftIcon { From b33962c486eda923a421496caf589b4ffe36e8ae Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 14 Feb 2025 15:41:24 +0100 Subject: [PATCH 050/124] fix(ui): fix "add file" alignement - WF-184 --- src/ui/src/builder/panels/BuilderCodePanel.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/src/builder/panels/BuilderCodePanel.vue b/src/ui/src/builder/panels/BuilderCodePanel.vue index e38bce8db..bacfadab2 100644 --- a/src/ui/src/builder/panels/BuilderCodePanel.vue +++ b/src/ui/src/builder/panels/BuilderCodePanel.vue @@ -249,8 +249,8 @@ async function handleSave() { border: none; display: flex; - justify-content: center; - text-align: left; + justify-content: flex-start; + align-items: center; gap: 4px; } From 316b277c48b799e2bb5d7ed87b4a0970363deca0 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 14 Feb 2025 15:44:08 +0100 Subject: [PATCH 051/124] fix(ui): harmonize dropdown in code editor - WF-184 --- src/ui/src/builder/BuilderMoreDropdown.vue | 14 ++++++++++---- src/ui/src/builder/BuilderTree.vue | 19 +++++++++---------- .../BuilderCodePanelSourceFilesTree.vue | 6 ++++-- src/ui/src/wds/WdsButton.vue | 18 ++++++++++++++---- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/ui/src/builder/BuilderMoreDropdown.vue b/src/ui/src/builder/BuilderMoreDropdown.vue index fa6419a0a..541bf3697 100644 --- a/src/ui/src/builder/BuilderMoreDropdown.vue +++ b/src/ui/src/builder/BuilderMoreDropdown.vue @@ -4,11 +4,12 @@ variant="neutral" size="smallIcon" :disabled="disabled" + :custom-size="triggerCustomSize" @click="isOpen = !isOpen" > more_horiz - import("@/wds/WdsDropdownMenu.vue")); +const WdsDropdownMenu = defineAsyncComponent( + () => import("@/wds/WdsDropdownMenu.vue"), +); defineProps({ options: { type: Array as PropType, default: () => [], }, + triggerCustomSize: { type: String, default: "smallIcon" }, disabled: { type: Boolean }, hideIcons: { type: Boolean, required: false }, }); @@ -58,7 +62,9 @@ const dropdown = useTemplateRef("dropdown"); const { floatingStyles } = useFloating(trigger, dropdown, { placement: "bottom-end", - middleware: [autoPlacement({ allowedPlacements: ["bottom", "top"] })], + middleware: [ + autoPlacement({ allowedPlacements: ["bottom-end", "top-end"] }), + ], }); // close the dropdown when clicking outside @@ -85,6 +91,6 @@ function onSelect(value: string) { position: relative; } .BuilderMoreDropdown__dropdown { - min-width: 200px; + min-width: 150px; } diff --git a/src/ui/src/builder/BuilderTree.vue b/src/ui/src/builder/BuilderTree.vue index a1fe46263..211b774b0 100644 --- a/src/ui/src/builder/BuilderTree.vue +++ b/src/ui/src/builder/BuilderTree.vue @@ -37,11 +37,12 @@
-
@@ -57,8 +58,9 @@ diff --git a/src/ui/src/wds/WdsButton.vue b/src/ui/src/wds/WdsButton.vue index c2246c44d..81ac35271 100644 --- a/src/ui/src/wds/WdsButton.vue +++ b/src/ui/src/wds/WdsButton.vue @@ -1,12 +1,16 @@ From 241835031bd16cc44c897e1cb84e804d75ee7884 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Fri, 14 Feb 2025 16:59:15 +0100 Subject: [PATCH 052/124] feat(ui): improve component tree search. WF-183 When searching a component in the tree, we start to display only component matching the query, or those containing a children matching the query. --- .../BuilderSidebarComponentTreeBranch.vue | 26 ++++++++++++++++--- src/ui/src/core/index.ts | 22 ++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue b/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue index 6d4fea6f2..24b5fe0cb 100644 --- a/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue +++ b/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue @@ -1,5 +1,6 @@ @@ -88,7 +91,7 @@ const emit = defineEmits({ dropdownSelect: (key: string) => typeof key === "string", }); -defineExpose({ expand }); +defineExpose({ expand, toggleCollapse }); const collapsed = ref(false); const isMainHovered = ref(false); @@ -102,8 +105,9 @@ function expand() { emit("expandBranch"); } -function toggleCollapse() { - collapsed.value = !collapsed.value; +function toggleCollapse(newCollapse?: boolean) { + newCollapse ??= !collapsed.value; + if (newCollapse !== collapsed.value) collapsed.value = newCollapse; } @@ -165,4 +169,26 @@ function toggleCollapse() { display: flex; justify-content: flex-end; } + +.slide-fade-enter-active { + transition: all 0.1s ease-out; +} + +.slide-fade-leave-active { + transition: all 0.1s ease-in; +} + +.slide-fade-enter-from, +.slide-fade-leave-to { + transform: translateY(-18px); + opacity: 0; +} +@media (prefers-reduced-motion) { + .slide-fade-enter-from, + .slide-fade-leave-to, + .slide-fade-enter-active, + .slide-fade-leave-active { + transition: unset; + } +} diff --git a/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue b/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue index 24b5fe0cb..f10a9ded7 100644 --- a/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue +++ b/src/ui/src/builder/sidebar/BuilderSidebarComponentTreeBranch.vue @@ -1,6 +1,5 @@ @@ -80,6 +80,7 @@ const selectedWorkflowComponentId = computed(() => { function jumpToWorkflow() { if (!selectedWorkflowComponentId.value) return; ssbm.setSelection(selectedWorkflowComponentId.value, null, "click"); + wf.setActivePageId(selectedWorkflowComponentId.value); } From 47c0f572f31b12a52e2e72dc23f79ab78657d034 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Tue, 4 Feb 2025 17:26:45 +0100 Subject: [PATCH 063/124] feat: implement `FieldType.Handler` - WF-136 Implement a new `FieldType.Handler`for fields that allow to display a custom selector in the UI displaying the list of function defined in Python script. --- .../settings/BuilderFieldsHandler.spec.ts | 108 ++++++++++++++++++ .../builder/settings/BuilderFieldsHandler.vue | 57 +++++++++ .../settings/BuilderFieldsWorkflowKey.spec.ts | 2 +- .../settings/BuilderFieldsWorkflowKey.vue | 10 +- .../settings/BuilderSettingsProperties.vue | 7 ++ src/ui/src/tests/mocks.ts | 7 +- src/ui/src/writerTypes.ts | 1 + src/writer/blocks/calleventhandler.py | 4 +- 8 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 src/ui/src/builder/settings/BuilderFieldsHandler.spec.ts create mode 100644 src/ui/src/builder/settings/BuilderFieldsHandler.vue diff --git a/src/ui/src/builder/settings/BuilderFieldsHandler.spec.ts b/src/ui/src/builder/settings/BuilderFieldsHandler.spec.ts new file mode 100644 index 000000000..4b64b0255 --- /dev/null +++ b/src/ui/src/builder/settings/BuilderFieldsHandler.spec.ts @@ -0,0 +1,108 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import BuilderFieldsHandler from "./BuilderFieldsHandler.vue"; +import { flushPromises, mount } from "@vue/test-utils"; +import { buildMockCore, buildMockComponent, mockProvides } from "@/tests/mocks"; +import injectionKeys from "@/injectionKeys"; +import { generateBuilderManager } from "../builderManager"; +import BuilderSelect from "../BuilderSelect.vue"; +import type { generateCore } from "@/core"; + +describe("BuilderFieldsHandler", () => { + let core: ReturnType; + let userFunctions: ReturnType["userFunctions"]; + const component1 = buildMockComponent({ + id: "wf1", + parentId: "workflows_root", + }); + const fieldKey = "mock-field-key"; + + function buildWrapper(componentId: string) { + const ssbm = generateBuilderManager(); + + return mount(BuilderFieldsHandler, { + props: { + componentId, + fieldKey, + }, + global: { + stubs: { + BaseSelect: true, + }, + provide: { + ...mockProvides, + [injectionKeys.builderManager as symbol]: ssbm, + [injectionKeys.core as symbol]: core, + }, + }, + }); + } + + beforeEach(() => { + const mockCore = buildMockCore(); + core = mockCore.core; + userFunctions = mockCore.userFunctions; + userFunctions.value = [ + { name: "func1", args: ["arg1", "arg2"] }, + { name: "func2", args: ["arg1", "arg2"] }, + ]; + + core.addComponent(component1); + }); + + it("should initialize option", async () => { + const wrapper = buildWrapper(component1.id); + await flushPromises(); + + expect(wrapper.attributes("data-automation-key")).toBe(fieldKey); + + const select = wrapper.getComponent(BuilderSelect); + + expect(select.props("modelValue")).toBe(""); + + const options = select.props("options"); + expect(options).toHaveLength(3); + expect(options.at(1)).toStrictEqual({ + value: "func1", + label: "func1", + icon: "function", + }); + }); + + it("should select an handler", async () => { + const wrapper = buildWrapper(component1.id); + await flushPromises(); + + const select = wrapper.getComponent(BuilderSelect); + + select.vm.$emit("update:modelValue", "func1"); + + await flushPromises(); + + expect(core.sendComponentUpdate).toHaveBeenCalled(); + expect(select.props("modelValue")).toBe("func1"); + }); + + it("should handle unexisting handler", async () => { + const unexstingHandler = "unexsting-handler"; + const component2 = buildMockComponent({ + id: "wf3", + parentId: "workflows_root", + content: { [fieldKey]: unexstingHandler }, + }); + core.addComponent(component2); + + const wrapper = buildWrapper(component2.id); + await flushPromises(); + + const select = wrapper.getComponent(BuilderSelect); + + expect(select.props("modelValue")).toBe(unexstingHandler); + + const options = select.props("options"); + expect(options).toHaveLength(4); + expect(options.at(-1)).toStrictEqual({ + value: unexstingHandler, + label: unexstingHandler, + }); + }); +}); diff --git a/src/ui/src/builder/settings/BuilderFieldsHandler.vue b/src/ui/src/builder/settings/BuilderFieldsHandler.vue new file mode 100644 index 000000000..8397ca30c --- /dev/null +++ b/src/ui/src/builder/settings/BuilderFieldsHandler.vue @@ -0,0 +1,57 @@ + + + diff --git a/src/ui/src/builder/settings/BuilderFieldsWorkflowKey.spec.ts b/src/ui/src/builder/settings/BuilderFieldsWorkflowKey.spec.ts index 423f3e5b1..54b514a47 100644 --- a/src/ui/src/builder/settings/BuilderFieldsWorkflowKey.spec.ts +++ b/src/ui/src/builder/settings/BuilderFieldsWorkflowKey.spec.ts @@ -106,7 +106,7 @@ describe("BuilderFieldsWorkflowKey", () => { const select = wrapper.getComponent(BuilderSelect); - expect(select.props("modelValue")).toBe("unexsting-workflow-key"); + expect(select.props("modelValue")).toBe(unexstingWorkflowKey); const options = select.props("options"); expect(options).toHaveLength(4); diff --git a/src/ui/src/builder/settings/BuilderFieldsWorkflowKey.vue b/src/ui/src/builder/settings/BuilderFieldsWorkflowKey.vue index 93439c83a..ac3c4a300 100644 --- a/src/ui/src/builder/settings/BuilderFieldsWorkflowKey.vue +++ b/src/ui/src/builder/settings/BuilderFieldsWorkflowKey.vue @@ -15,7 +15,6 @@ diff --git a/src/ui/src/wds/WdsModal.vue b/src/ui/src/wds/WdsModal.vue new file mode 100644 index 000000000..0b7755aaa --- /dev/null +++ b/src/ui/src/wds/WdsModal.vue @@ -0,0 +1,117 @@ + + + + + + From bbe44873fcde884411bb1fb6066f8a77a58b17da Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:56:33 +0000 Subject: [PATCH 066/124] fix: Set outcome --- src/writer/blocks/uieventtrigger.py | 56 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/writer/blocks/uieventtrigger.py b/src/writer/blocks/uieventtrigger.py index 8947b7070..cf54389b6 100644 --- a/src/writer/blocks/uieventtrigger.py +++ b/src/writer/blocks/uieventtrigger.py @@ -4,38 +4,40 @@ class UIEventTrigger(WorkflowTrigger): - @classmethod def register(cls, type: str): super(UIEventTrigger, cls).register(type) - register_abstract_template(type, AbstractTemplate( - baseType="workflows_node", - writer={ - "name": "UI Trigger", - "description": "Trigger the workflow when an UI event takes place.", - "category": "Writer", - "fields": { - "refComponentId": { - "name": "Component Id", - "type": "Text", - "options": "uiComponentsWithEvents", - "desc": "The id of the component that will trigger this branch.", - }, - "refEventType": { - "name": "Event type", - "type": "Text", - "desc": "The type of the event that will trigger this branch. For example, wf-click.", - "options": "eventTypes", + register_abstract_template( + type, + AbstractTemplate( + baseType="workflows_node", + writer={ + "name": "UI Trigger", + "description": "Trigger the workflow when an UI event takes place.", + "category": "Writer", + "fields": { + "refComponentId": { + "name": "Component Id", + "type": "Text", + "options": "uiComponentsWithEvents", + "desc": "The id of the component that will trigger this branch.", + }, + "refEventType": { + "name": "Event type", + "type": "Text", + "desc": "The type of the event that will trigger this branch. For example, wf-click.", + "options": "eventTypes", + }, }, - }, - "outs": { - "trigger": { - "name": "Trigger", - "style": "success", + "outs": { + "trigger": { + "name": "Trigger", + "style": "success", + }, }, }, - } - )) + ), + ) def run(self): - pass \ No newline at end of file + self.outcome = "trigger" From b0f32e40a7d957f327ad67f085ecef578a06ed03 Mon Sep 17 00:00:00 2001 From: Ramiro Medina <64783088+ramedina86@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:57:11 +0000 Subject: [PATCH 067/124] feat: Workflows code block --- .../src/builder/BuilderEmbeddedCodeEditor.vue | 2 +- src/ui/src/builder/BuilderModal.vue | 2 +- .../builder/settings/BuilderFieldsCode.vue | 51 ++ .../settings/BuilderSettingsProperties.vue | 24 +- src/ui/src/writerTypes.ts | 1 + src/writer/blocks/__init__.py | 2 + src/writer/blocks/code.py | 110 ++-- src/writer/core.py | 613 ++++++++++-------- src/writer/workflows.py | 126 ++-- tests/backend/blocks/test_code.py | 40 ++ 10 files changed, 609 insertions(+), 362 deletions(-) create mode 100644 src/ui/src/builder/settings/BuilderFieldsCode.vue create mode 100644 tests/backend/blocks/test_code.py diff --git a/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue b/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue index de5d53ca8..6708ca305 100644 --- a/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue +++ b/src/ui/src/builder/BuilderEmbeddedCodeEditor.vue @@ -93,7 +93,7 @@ onUnmounted(() => { } .editorContainer { - min-height: 100px; + min-height: inherit; width: 100%; height: 100%; overflow: hidden; diff --git a/src/ui/src/builder/BuilderModal.vue b/src/ui/src/builder/BuilderModal.vue index 12ab381a7..4b3d7fda7 100644 --- a/src/ui/src/builder/BuilderModal.vue +++ b/src/ui/src/builder/BuilderModal.vue @@ -16,7 +16,7 @@ close diff --git a/src/ui/src/builder/settings/BuilderFieldsCode.vue b/src/ui/src/builder/settings/BuilderFieldsCode.vue new file mode 100644 index 000000000..c35146a0d --- /dev/null +++ b/src/ui/src/builder/settings/BuilderFieldsCode.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/ui/src/builder/settings/BuilderSettingsProperties.vue b/src/ui/src/builder/settings/BuilderSettingsProperties.vue index f89d6ae19..ee23bfad8 100644 --- a/src/ui/src/builder/settings/BuilderSettingsProperties.vue +++ b/src/ui/src/builder/settings/BuilderSettingsProperties.vue @@ -30,6 +30,9 @@ :hint="fieldValue.desc" :unit="fieldValue.type" :error="errorsByFields[fieldKey]" + :is-expansible="fieldValue.type == FieldType.Code" + @expand="handleExpand(fieldKey)" + @shrink="handleShrink(fieldKey)" > + + + @@ -124,7 +135,7 @@ diff --git a/src/ui/src/builder/BuilderSelect.spec.ts b/src/ui/src/builder/BuilderSelect.spec.ts new file mode 100644 index 000000000..7f1dc9435 --- /dev/null +++ b/src/ui/src/builder/BuilderSelect.spec.ts @@ -0,0 +1,54 @@ +import { flushPromises, mount, shallowMount } from "@vue/test-utils"; +import { describe, expect, it } from "vitest"; + +import BuilderSelect from "./BuilderSelect.vue"; +import { Option } from "./BuilderSelect.vue"; +import WdsDropdownMenu from "@/wds/WdsDropdownMenu.vue"; + +describe("BuilderSelect", () => { + const options: Option[] = [ + { label: "Label A", value: "a" }, + { label: "Label B", value: "b" }, + { label: "Label C", value: "c" }, + ]; + + it("should display unknow value selected", () => { + const wrapper = shallowMount(BuilderSelect, { + props: { + modelValue: "x", + enableMultiSelection: false, + hideIcons: true, + options, + }, + }); + + expect(wrapper.get(".material-symbols-outlined").text()).toBe( + "help_center", + ); + }); + + it("should support single mode", async () => { + const wrapper = mount(BuilderSelect, { + props: { + modelValue: "a", + enableMultiSelection: false, + options, + }, + global: { + stubs: { + WdsDropdownMenu: true, + }, + }, + }); + await flushPromises(); + + await wrapper.get(".BuilderSelect__trigger").trigger("click"); + await flushPromises(); + + const dropdownMenu = wrapper.getComponent(WdsDropdownMenu); + dropdownMenu.vm.$emit("select", "b"); + await flushPromises(); + + expect(wrapper.emitted("update:modelValue").at(0)).toStrictEqual(["b"]); + }); +}); diff --git a/src/ui/src/builder/BuilderSelect.vue b/src/ui/src/builder/BuilderSelect.vue index 79be00e4e..7d5ba77fb 100644 --- a/src/ui/src/builder/BuilderSelect.vue +++ b/src/ui/src/builder/BuilderSelect.vue @@ -5,9 +5,11 @@ role="button" @click="isOpen = !isOpen" > - {{ - currentIcon - }} + {{ currentIcon }}
{{ expandIcon }}
- import("@/wds/WdsDropdownMenu.vue")); +const WdsDropdownMenu = defineAsyncComponent( + () => import("@/wds/WdsDropdownMenu.vue"), +); const props = defineProps({ options: { - type: Array as PropType, + type: Array as PropType< + WdsDropdownMenuOption[] | Readonly + >, default: () => [], }, defaultIcon: { type: String, required: false, default: undefined }, hideIcons: { type: Boolean, required: false }, enableSearch: { type: Boolean, required: false }, + enableMultiSelection: { type: Boolean, required: false }, }); -const currentValue = defineModel({ type: String, required: false }); +const currentValue = defineModel({ + type: [String, Array] as PropType, + required: true, + default: undefined, +}); const isOpen = ref(false); const trigger = useTemplateRef("trigger"); const dropdown = useTemplateRef("dropdown"); +const middleware = computed(() => + // avoid placement on the top when search mode is enabled + props.enableSearch + ? [] + : [autoPlacement({ allowedPlacements: ["bottom", "top"] })], +); + const { floatingStyles, update: updateFloatingStyle } = useFloating( trigger, dropdown, { placement: "bottom", - middleware: [autoPlacement({ allowedPlacements: ["bottom", "top"] })], + middleware, }, ); @@ -81,15 +100,29 @@ const expandIcon = computed(() => isOpen.value ? "keyboard_arrow_up" : "expand_more", ); -const selectedOption = computed(() => - props.options.find((o) => o.value === currentValue.value), +const selectedOptions = computed(() => + props.options.filter((o) => isSelected(o.value)), ); -const currentLabel = computed(() => selectedOption.value?.label ?? ""); +const hasUnknowOptionSelected = computed(() => { + return currentValue.value && selectedOptions.value.length === 0; +}); + +const currentLabel = computed(() => { + if (hasUnknowOptionSelected.value) return String(currentValue.value); + + return selectedOptions.value + .map((o) => o.label) + .sort() + .join(" / "); +}); const currentIcon = computed(() => { + if (hasUnknowOptionSelected.value) return "help_center"; if (props.hideIcons) return ""; - return selectedOption.value?.icon ?? props.defaultIcon ?? "help_center"; + return ( + selectedOptions.value.at(0)?.icon ?? props.defaultIcon ?? "help_center" + ); }); // close the dropdown when clicking outside @@ -105,10 +138,16 @@ watch( { immediate: true }, ); -function onSelect(value: string) { - isOpen.value = false; +function onSelect(value: string | string[]) { + if (!props.enableMultiSelection) isOpen.value = false; currentValue.value = value; } + +function isSelected(value: string) { + return Array.isArray(currentValue.value) + ? currentValue.value.includes(value) + : currentValue.value === value; +} diff --git a/src/ui/src/wds/WdsTag.vue b/src/ui/src/wds/WdsTag.vue new file mode 100644 index 000000000..f6ab231e1 --- /dev/null +++ b/src/ui/src/wds/WdsTag.vue @@ -0,0 +1,73 @@ + + + + + + From 7fa68ea4a819ca19acf8238a5e263519f9697284 Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Wed, 19 Feb 2025 14:51:05 +0100 Subject: [PATCH 074/124] feat(ui): create loading state for dropdown menu - WD-138 --- src/ui/src/builder/BuilderGraphSelect.vue | 5 +- src/ui/src/builder/BuilderSelect.vue | 7 +- src/ui/src/wds/WdsDropdownMenu.vue | 117 ++++++++++++++-------- src/ui/src/wds/WdsSkeletonLoader.vue | 31 ++++++ 4 files changed, 113 insertions(+), 47 deletions(-) create mode 100644 src/ui/src/wds/WdsSkeletonLoader.vue diff --git a/src/ui/src/builder/BuilderGraphSelect.vue b/src/ui/src/builder/BuilderGraphSelect.vue index c171fd747..6397ccd8b 100644 --- a/src/ui/src/builder/BuilderGraphSelect.vue +++ b/src/ui/src/builder/BuilderGraphSelect.vue @@ -12,7 +12,6 @@ import type { Option } from "./BuilderSelect.vue"; import BuilderAsyncLoader from "./BuilderAsyncLoader.vue"; import type { WriterGraph } from "@/writerTypes"; import WdsTextInput from "@/wds/WdsTextInput.vue"; -import LoadingSymbol from "@/renderer/LoadingSymbol.vue"; const BuilderSelect = defineAsyncComponent({ loader: () => import("./BuilderSelect.vue"), @@ -65,15 +64,15 @@ const currentValueStr = computed({ diff --git a/src/ui/src/builder/BuilderSelect.vue b/src/ui/src/builder/BuilderSelect.vue index 91991caef..0a9fc58a2 100644 --- a/src/ui/src/builder/BuilderSelect.vue +++ b/src/ui/src/builder/BuilderSelect.vue @@ -6,7 +6,10 @@ @click="isOpen = !isOpen" > {{ currentIcon }} @@ -40,6 +43,7 @@ :enable-search="enableSearch" :enable-multi-selection="enableMultiSelection" :hide-icons="hideIcons" + :loading="loading" :options="options" :selected="currentValue" :style="floatingStyles" @@ -83,6 +87,7 @@ const props = defineProps({ hideIcons: { type: Boolean, required: false }, enableSearch: { type: Boolean, required: false }, enableMultiSelection: { type: Boolean, required: false }, + loading: { type: Boolean, required: false }, }); const currentValue = defineModel({ diff --git a/src/ui/src/wds/WdsDropdownMenu.vue b/src/ui/src/wds/WdsDropdownMenu.vue index 49368c25c..98dca4607 100644 --- a/src/ui/src/wds/WdsDropdownMenu.vue +++ b/src/ui/src/wds/WdsDropdownMenu.vue @@ -1,6 +1,9 @@ + + @@ -69,7 +97,8 @@ export type WdsDropdownMenuOption = { + + + + From a3cf137703fa3dc7f2d729dffa41d1d0feb5980a Mon Sep 17 00:00:00 2001 From: Alexandre Rousseau Date: Wed, 19 Feb 2025 18:46:44 +0100 Subject: [PATCH 075/124] feat(ui): introduce `WdsCheckbox` - WF-138 --- src/ui/src/wds/WdsCheckbox.vue | 106 +++++++++++++++++++++++++ src/ui/src/wds/WdsDropdownMenu.spec.ts | 6 +- src/ui/src/wds/WdsDropdownMenu.vue | 59 +++++++------- 3 files changed, 139 insertions(+), 32 deletions(-) create mode 100644 src/ui/src/wds/WdsCheckbox.vue diff --git a/src/ui/src/wds/WdsCheckbox.vue b/src/ui/src/wds/WdsCheckbox.vue new file mode 100644 index 000000000..1d960cc5f --- /dev/null +++ b/src/ui/src/wds/WdsCheckbox.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/ui/src/wds/WdsDropdownMenu.spec.ts b/src/ui/src/wds/WdsDropdownMenu.spec.ts index b06745548..d9ce5b663 100644 --- a/src/ui/src/wds/WdsDropdownMenu.spec.ts +++ b/src/ui/src/wds/WdsDropdownMenu.spec.ts @@ -1,4 +1,4 @@ -import { shallowMount } from "@vue/test-utils"; +import { mount, shallowMount } from "@vue/test-utils"; import { describe, expect, it } from "vitest"; import WdsDropdownMenu from "./WdsDropdownMenu.vue"; @@ -31,7 +31,7 @@ describe("WdsDropdownMenu", () => { describe("multiple mode", () => { it("should support multiple mode", async () => { - const wrapper = shallowMount(WdsDropdownMenu, { + const wrapper = mount(WdsDropdownMenu, { props: { selected: ["???"], enableMultiSelection: true, @@ -40,7 +40,7 @@ describe("WdsDropdownMenu", () => { }); await wrapper - .get(`.WdsDropdownMenu__item[data-automation-key="b"]`) + .get(`.WdsDropdownMenu__checkbox[data-automation-key="b"]`) .trigger("click"); expect(wrapper.emitted("select").at(0)).toStrictEqual([["b"]]); diff --git a/src/ui/src/wds/WdsDropdownMenu.vue b/src/ui/src/wds/WdsDropdownMenu.vue index 98dca4607..59633999b 100644 --- a/src/ui/src/wds/WdsDropdownMenu.vue +++ b/src/ui/src/wds/WdsDropdownMenu.vue @@ -24,18 +24,30 @@ :key="index" class="WdsDropdownMenu__item" > -
- -
+ style="width: 20px" + />
+ +