From 0dd996f28c6be4d90196166286bbb76e5e0beb4b Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Sun, 24 Nov 2024 13:26:28 -0800 Subject: [PATCH] refactors server sent event handling in workbench-app to fix excessive reconnects; removes old workflows and interact ux (#261) Elevates handling of SSE to route level component and adds hooks for adding/removing listeners from any child component without having to pass through props. Adds more logging as there was a swallowed error transforming events that was triggering retries even after new design - going forward, it'll be more obvious if there is an error causing this - or if there are 400/500 errors coming from the service. Instead of updating the old workflows & interact UX components, I went ahead and removed them since they are no longer needed. --- .../.vscode/settings.json | 4 +- .../skill-library/.vscode/settings.json | 2 + workbench-app/.vscode/settings.json | 4 +- workbench-app/package.json | 1 - workbench-app/pnpm-lock.yaml | 363 ------------- workbench-app/src/Constants.ts | 3 - .../components/Assistants/AssistantAdd.tsx | 4 +- .../components/Assistants/MyAssistants.tsx | 3 +- .../Conversations/Canvas/AssistantDrawer.tsx | 65 --- .../Canvas/AssistantInspector.tsx | 29 +- .../Conversations/Canvas/CanvasControls.tsx | 103 ---- .../Conversations/Canvas/CanvasDrawer.tsx | 69 --- .../Canvas/ConversationDrawer.tsx | 61 --- .../Conversations/Canvas/InteractCanvas.tsx | 149 ------ .../Conversations/InteractInput.tsx | 2 +- .../Conversations/MyConversations.tsx | 5 +- .../FrontDoor/Chat/ChatControls.tsx | 15 +- .../FrontDoor/Controls/ConversationList.tsx | 32 +- .../Controls/ConversationListOptions.tsx | 3 - .../src/components/Workflows/MyWorkflows.tsx | 72 --- .../components/Workflows/WorkflowControl.tsx | 146 ------ .../Workflows/WorkflowConversation.tsx | 325 ------------ .../components/Workflows/WorkflowCreate.tsx | 145 ------ .../AssistantDefinitionAdd.tsx | 154 ------ .../AssistantDefinitionCreate.tsx | 213 -------- .../ConversationDefinitionCreate.tsx | 101 ---- .../DefinitionPropertiesEditor.tsx | 102 ---- .../StatePropertiesEditor.tsx | 76 --- .../WorkflowDesigner/WorkflowCanvas.tsx | 480 ------------------ .../WorkflowDefinitionEditor.tsx | 119 ----- .../WorkflowDesigner/WorkflowDesigner.tsx | 130 ----- .../WorkflowDesigner/WorkflowExport.tsx | 39 -- .../WorkflowDesigner/WorkflowHelp.tsx | 239 --------- .../WorkflowDesigner/WorkflowImport.tsx | 56 -- .../WorkflowStateAssistantEditor.tsx | 139 ----- .../WorkflowStateAssistants.tsx | 225 -------- .../WorkflowStateConversation.tsx | 112 ---- .../WorkflowDesigner/WorkflowStateEditor.tsx | 220 -------- .../WorkflowDesigner/WorkflowStateHandle.tsx | 38 -- .../WorkflowDesigner/WorkflowStateNode.tsx | 119 ----- .../WorkflowDesigner/WorkflowStateOutlets.tsx | 440 ---------------- .../src/components/Workflows/WorkflowEdit.tsx | 30 -- .../components/Workflows/WorkflowRemove.tsx | 51 -- .../Workflows/WorkflowRunCreate.tsx | 112 ---- .../Workflows/WorkflowRunRemove.tsx | 47 -- .../src/components/Workflows/WorkflowRuns.tsx | 69 --- .../src/libs/EventSubscriptionManager.ts | 33 ++ .../src/libs/WorkbenchEventSource.ts | 226 --------- .../src/libs/useConversationEvents.ts | 31 +- workbench-app/src/libs/useHistoryUtility.ts | 10 +- .../src/libs/useWorkbenchEventSource.ts | 162 ++++++ workbench-app/src/libs/useWorkbenchService.ts | 40 +- workbench-app/src/main.tsx | 20 - .../src/models/WorkflowDefinition.ts | 62 --- workbench-app/src/models/WorkflowRun.ts | 11 - .../src/redux/features/app/AppState.ts | 1 - .../src/redux/features/app/appSlice.ts | 11 +- workbench-app/src/routes/Dashboard.tsx | 16 +- workbench-app/src/routes/FrontDoor.tsx | 11 + workbench-app/src/routes/Interact.tsx | 199 -------- workbench-app/src/routes/WorkflowEditor.tsx | 96 ---- workbench-app/src/routes/WorkflowInteract.tsx | 298 ----------- .../src/routes/WorkflowRunEditor.tsx | 112 ---- workbench-app/src/services/workbench/index.ts | 1 - .../src/services/workbench/workbench.ts | 2 - .../src/services/workbench/workflow.ts | 274 ---------- 66 files changed, 262 insertions(+), 6270 deletions(-) delete mode 100644 workbench-app/src/components/Conversations/Canvas/AssistantDrawer.tsx delete mode 100644 workbench-app/src/components/Conversations/Canvas/CanvasControls.tsx delete mode 100644 workbench-app/src/components/Conversations/Canvas/CanvasDrawer.tsx delete mode 100644 workbench-app/src/components/Conversations/Canvas/ConversationDrawer.tsx delete mode 100644 workbench-app/src/components/Conversations/Canvas/InteractCanvas.tsx delete mode 100644 workbench-app/src/components/Workflows/MyWorkflows.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowControl.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowConversation.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowCreate.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/AssistantDefinitionAdd.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/AssistantDefinitionCreate.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/ConversationDefinitionCreate.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/DefinitionPropertiesEditor.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/StatePropertiesEditor.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowCanvas.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowDefinitionEditor.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowDesigner.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowExport.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowHelp.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowImport.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowStateAssistantEditor.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowStateAssistants.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowStateConversation.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowStateEditor.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowStateHandle.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowStateNode.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowStateOutlets.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowEdit.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowRemove.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowRunCreate.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowRunRemove.tsx delete mode 100644 workbench-app/src/components/Workflows/WorkflowRuns.tsx create mode 100644 workbench-app/src/libs/EventSubscriptionManager.ts delete mode 100644 workbench-app/src/libs/WorkbenchEventSource.ts create mode 100644 workbench-app/src/libs/useWorkbenchEventSource.ts delete mode 100644 workbench-app/src/models/WorkflowDefinition.ts delete mode 100644 workbench-app/src/models/WorkflowRun.ts delete mode 100644 workbench-app/src/routes/Interact.tsx delete mode 100644 workbench-app/src/routes/WorkflowEditor.tsx delete mode 100644 workbench-app/src/routes/WorkflowInteract.tsx delete mode 100644 workbench-app/src/routes/WorkflowRunEditor.tsx delete mode 100644 workbench-app/src/services/workbench/workflow.ts diff --git a/libraries/python/assistant-extensions/.vscode/settings.json b/libraries/python/assistant-extensions/.vscode/settings.json index 1eccec71..d9591ac7 100644 --- a/libraries/python/assistant-extensions/.vscode/settings.json +++ b/libraries/python/assistant-extensions/.vscode/settings.json @@ -37,12 +37,14 @@ "**/__pycache__": true }, "cSpell.words": [ + "asyncio", "deepmerge", "DMAIC", "endregion", "Excalidraw", "openai", "pdfplumber", - "pydantic" + "pydantic", + "pytest" ] } diff --git a/libraries/python/skills/skill-library/.vscode/settings.json b/libraries/python/skills/skill-library/.vscode/settings.json index 1d907497..f31934d1 100644 --- a/libraries/python/skills/skill-library/.vscode/settings.json +++ b/libraries/python/skills/skill-library/.vscode/settings.json @@ -47,10 +47,12 @@ "cSpell.words": [ "dotenv", "httpx", + "metadrive", "openai", "pydantic", "pypdf", "runtimes", + "subdrive", "tiktoken" ], "python.testing.pytestArgs": ["skill_library"], diff --git a/workbench-app/.vscode/settings.json b/workbench-app/.vscode/settings.json index 656b97a9..14c149a4 100644 --- a/workbench-app/.vscode/settings.json +++ b/workbench-app/.vscode/settings.json @@ -199,6 +199,7 @@ "reactflow", "reduxjs", "rehype", + "retriable", "rjsf", "rootpath", "selectin", @@ -226,9 +227,6 @@ "westus", "winget", "workbenchservice", - "workflowdefinition", - "workflowrun", - "workflowuserparticipant", "YYYYMMDDH" ] } diff --git a/workbench-app/package.json b/workbench-app/package.json index 5bad88f0..6399b6fa 100644 --- a/workbench-app/package.json +++ b/workbench-app/package.json @@ -63,7 +63,6 @@ "react-syntax-highlighter": "^15.5.0", "react-virtualized-auto-sizer": "^1.0.24", "react-virtuoso": "^4.10.4", - "reactflow": "^11.11.4", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", "streamsaver": "^2.0.6" diff --git a/workbench-app/pnpm-lock.yaml b/workbench-app/pnpm-lock.yaml index a3e75c64..c3c9ed8b 100644 --- a/workbench-app/pnpm-lock.yaml +++ b/workbench-app/pnpm-lock.yaml @@ -143,9 +143,6 @@ importers: react-virtuoso: specifier: ^4.10.4 version: 4.10.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - reactflow: - specifier: ^11.11.4 - version: 11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rehype-raw: specifier: ^7.0.0 version: 7.0.0 @@ -2337,42 +2334,6 @@ packages: '@popperjs/core@2.4.4': resolution: {integrity: sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==} - '@reactflow/background@11.3.14': - resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - - '@reactflow/controls@11.2.14': - resolution: {integrity: sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - - '@reactflow/core@11.11.4': - resolution: {integrity: sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - - '@reactflow/minimap@11.7.14': - resolution: {integrity: sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - - '@reactflow/node-resizer@2.2.14': - resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - - '@reactflow/node-toolbar@1.3.14': - resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - '@reduxjs/toolkit@1.9.7': resolution: {integrity: sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==} peerDependencies: @@ -2456,99 +2417,15 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/d3-array@3.2.1': - resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} - - '@types/d3-axis@3.0.6': - resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} - - '@types/d3-brush@3.0.6': - resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} - - '@types/d3-chord@3.0.6': - resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} - - '@types/d3-color@3.1.3': - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - - '@types/d3-contour@3.0.6': - resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} - - '@types/d3-delaunay@6.0.4': - resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} - - '@types/d3-dispatch@3.0.6': - resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==} - - '@types/d3-drag@3.0.7': - resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} - - '@types/d3-dsv@3.0.7': - resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} - - '@types/d3-ease@3.0.2': - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - - '@types/d3-fetch@3.0.7': - resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} - - '@types/d3-force@3.0.10': - resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} - - '@types/d3-format@3.0.4': - resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} - - '@types/d3-geo@3.1.0': - resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} - - '@types/d3-hierarchy@3.1.7': - resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} - - '@types/d3-interpolate@3.0.4': - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - - '@types/d3-path@3.1.0': - resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==} - - '@types/d3-polygon@3.0.2': - resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} - - '@types/d3-quadtree@3.0.6': - resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} - - '@types/d3-random@3.0.3': - resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} - '@types/d3-scale-chromatic@3.0.3': resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==} '@types/d3-scale@4.0.8': resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} - '@types/d3-selection@3.0.10': - resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==} - - '@types/d3-shape@3.1.6': - resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} - - '@types/d3-time-format@4.0.3': - resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} - '@types/d3-time@3.0.3': resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} - '@types/d3-timer@3.0.2': - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - - '@types/d3-transition@3.0.8': - resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==} - - '@types/d3-zoom@3.0.8': - resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} - - '@types/d3@7.4.3': - resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -2561,9 +2438,6 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/geojson@7946.0.14': - resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} - '@types/hast@2.3.10': resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} @@ -2966,9 +2840,6 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - classcat@5.0.5: - resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} - classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -4748,12 +4619,6 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} - reactflow@11.11.4: - resolution: {integrity: sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - reactivity-store@0.3.5: resolution: {integrity: sha512-LYcmC0Qukuosg9x+nrYEKUMFRuqbLVSn0Bd4dY5XXKx2Q/HQBHVXU+GchtrkJS39iIdusN+7QMAzBPWZGJOU0g==} peerDependencies: @@ -5354,21 +5219,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zustand@4.5.5: - resolution: {integrity: sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==} - engines: {node: '>=12.7.0'} - peerDependencies: - '@types/react': '>=16.8' - immer: '>=9.0.6' - react: '>=16.8' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -8530,84 +8380,6 @@ snapshots: '@popperjs/core@2.4.4': {} - '@reactflow/background@11.3.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.10)(immer@9.0.21)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - - '@reactflow/controls@11.2.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.10)(immer@9.0.21)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - - '@reactflow/core@11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@types/d3': 7.4.3 - '@types/d3-drag': 3.0.7 - '@types/d3-selection': 3.0.10 - '@types/d3-zoom': 3.0.8 - classcat: 5.0.5 - d3-drag: 3.0.0 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.10)(immer@9.0.21)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - - '@reactflow/minimap@11.7.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/d3-selection': 3.0.10 - '@types/d3-zoom': 3.0.8 - classcat: 5.0.5 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.10)(immer@9.0.21)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - - '@reactflow/node-resizer@2.2.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - classcat: 5.0.5 - d3-drag: 3.0.0 - d3-selection: 3.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.10)(immer@9.0.21)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - - '@reactflow/node-toolbar@1.3.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@reactflow/core': 11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - classcat: 5.0.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - zustand: 4.5.5(@types/react@18.3.10)(immer@9.0.21)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - '@reduxjs/toolkit@1.9.7(react-redux@8.1.3(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1))(react@18.3.1)': dependencies: immer: 9.0.21 @@ -8712,123 +8484,14 @@ snapshots: dependencies: '@babel/types': 7.25.6 - '@types/d3-array@3.2.1': {} - - '@types/d3-axis@3.0.6': - dependencies: - '@types/d3-selection': 3.0.10 - - '@types/d3-brush@3.0.6': - dependencies: - '@types/d3-selection': 3.0.10 - - '@types/d3-chord@3.0.6': {} - - '@types/d3-color@3.1.3': {} - - '@types/d3-contour@3.0.6': - dependencies: - '@types/d3-array': 3.2.1 - '@types/geojson': 7946.0.14 - - '@types/d3-delaunay@6.0.4': {} - - '@types/d3-dispatch@3.0.6': {} - - '@types/d3-drag@3.0.7': - dependencies: - '@types/d3-selection': 3.0.10 - - '@types/d3-dsv@3.0.7': {} - - '@types/d3-ease@3.0.2': {} - - '@types/d3-fetch@3.0.7': - dependencies: - '@types/d3-dsv': 3.0.7 - - '@types/d3-force@3.0.10': {} - - '@types/d3-format@3.0.4': {} - - '@types/d3-geo@3.1.0': - dependencies: - '@types/geojson': 7946.0.14 - - '@types/d3-hierarchy@3.1.7': {} - - '@types/d3-interpolate@3.0.4': - dependencies: - '@types/d3-color': 3.1.3 - - '@types/d3-path@3.1.0': {} - - '@types/d3-polygon@3.0.2': {} - - '@types/d3-quadtree@3.0.6': {} - - '@types/d3-random@3.0.3': {} - '@types/d3-scale-chromatic@3.0.3': {} '@types/d3-scale@4.0.8': dependencies: '@types/d3-time': 3.0.3 - '@types/d3-selection@3.0.10': {} - - '@types/d3-shape@3.1.6': - dependencies: - '@types/d3-path': 3.1.0 - - '@types/d3-time-format@4.0.3': {} - '@types/d3-time@3.0.3': {} - '@types/d3-timer@3.0.2': {} - - '@types/d3-transition@3.0.8': - dependencies: - '@types/d3-selection': 3.0.10 - - '@types/d3-zoom@3.0.8': - dependencies: - '@types/d3-interpolate': 3.0.4 - '@types/d3-selection': 3.0.10 - - '@types/d3@7.4.3': - dependencies: - '@types/d3-array': 3.2.1 - '@types/d3-axis': 3.0.6 - '@types/d3-brush': 3.0.6 - '@types/d3-chord': 3.0.6 - '@types/d3-color': 3.1.3 - '@types/d3-contour': 3.0.6 - '@types/d3-delaunay': 6.0.4 - '@types/d3-dispatch': 3.0.6 - '@types/d3-drag': 3.0.7 - '@types/d3-dsv': 3.0.7 - '@types/d3-ease': 3.0.2 - '@types/d3-fetch': 3.0.7 - '@types/d3-force': 3.0.10 - '@types/d3-format': 3.0.4 - '@types/d3-geo': 3.1.0 - '@types/d3-hierarchy': 3.1.7 - '@types/d3-interpolate': 3.0.4 - '@types/d3-path': 3.1.0 - '@types/d3-polygon': 3.0.2 - '@types/d3-quadtree': 3.0.6 - '@types/d3-random': 3.0.3 - '@types/d3-scale': 4.0.8 - '@types/d3-scale-chromatic': 3.0.3 - '@types/d3-selection': 3.0.10 - '@types/d3-shape': 3.1.6 - '@types/d3-time': 3.0.3 - '@types/d3-time-format': 4.0.3 - '@types/d3-timer': 3.0.2 - '@types/d3-transition': 3.0.8 - '@types/d3-zoom': 3.0.8 - '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 @@ -8841,8 +8504,6 @@ snapshots: '@types/estree@1.0.6': {} - '@types/geojson@7946.0.14': {} - '@types/hast@2.3.10': dependencies: '@types/unist': 2.0.11 @@ -9340,8 +9001,6 @@ snapshots: character-reference-invalid@2.0.1: {} - classcat@5.0.5: {} - classnames@2.5.1: {} cliui@7.0.4: @@ -11703,20 +11362,6 @@ snapshots: dependencies: loose-envify: 1.4.0 - reactflow@11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@reactflow/background': 11.3.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/controls': 11.2.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/core': 11.11.4(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/minimap': 11.7.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/node-resizer': 2.2.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@reactflow/node-toolbar': 1.3.14(@types/react@18.3.10)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - immer - reactivity-store@0.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@vue/reactivity': 3.5.10 @@ -12381,12 +12026,4 @@ snapshots: yocto-queue@0.1.0: {} - zustand@4.5.5(@types/react@18.3.10)(immer@9.0.21)(react@18.3.1): - dependencies: - use-sync-external-store: 1.2.2(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.10 - immer: 9.0.21 - react: 18.3.1 - zwitch@2.0.4: {} diff --git a/workbench-app/src/Constants.ts b/workbench-app/src/Constants.ts index 30aa393d..f2fb432c 100644 --- a/workbench-app/src/Constants.ts +++ b/workbench-app/src/Constants.ts @@ -19,9 +19,6 @@ export const Constants = { azureSpeechTokenRefreshIntervalMs: 540000, // 540000 ms = 9 minutes globalToasterId: 'global', }, - workflow: { - maxOutlets: 5, - }, service: { defaultEnvironmentId: 'local', environments: [ diff --git a/workbench-app/src/components/Assistants/AssistantAdd.tsx b/workbench-app/src/components/Assistants/AssistantAdd.tsx index 7d1efcc9..fb84f679 100644 --- a/workbench-app/src/components/Assistants/AssistantAdd.tsx +++ b/workbench-app/src/components/Assistants/AssistantAdd.tsx @@ -56,9 +56,7 @@ export const AssistantAdd: React.FC = (props) => { onAdd(assistant); }; - const unusedAssistants = assistants - .filter((assistant) => assistant.metadata?.workflow_run_id === undefined) - .filter((assistant) => !exceptAssistantIds?.includes(assistant.id)); + const unusedAssistants = assistants.filter((assistant) => !exceptAssistantIds?.includes(assistant.id)); return (
diff --git a/workbench-app/src/components/Assistants/MyAssistants.tsx b/workbench-app/src/components/Assistants/MyAssistants.tsx index 757d9b95..d51849f6 100644 --- a/workbench-app/src/components/Assistants/MyAssistants.tsx +++ b/workbench-app/src/components/Assistants/MyAssistants.tsx @@ -39,8 +39,7 @@ export const MyAssistants: React.FC = (props) => { return ( assistant.metadata?.workflow_run_id === undefined) - .toSorted((a, b) => a.name.localeCompare(b.name)) + ?.toSorted((a, b) => a.name.localeCompare(b.name)) .map((assistant) => ( = (props) => { - const { open, mode, conversation, conversationAssistants, selectedAssistant } = props; - const classes = useClasses(); - - let title = ''; - if (!conversationAssistants || conversationAssistants.length === 0 || conversationAssistants.length > 1) { - title = 'Assistants'; - } else { - title = conversationAssistants[0].name; - } - - const canvasContent = - conversationAssistants && conversationAssistants.length > 0 ? ( - - ) : ( -
No assistants participating in conversation.
- ); - - return ( - - {canvasContent} - - ); -}; diff --git a/workbench-app/src/components/Conversations/Canvas/AssistantInspector.tsx b/workbench-app/src/components/Conversations/Canvas/AssistantInspector.tsx index 615eb30c..292a4ef2 100644 --- a/workbench-app/src/components/Conversations/Canvas/AssistantInspector.tsx +++ b/workbench-app/src/components/Conversations/Canvas/AssistantInspector.tsx @@ -8,9 +8,8 @@ import Form from '@rjsf/fluentui-rc'; import { RegistryWidgetsType } from '@rjsf/utils'; import validator from '@rjsf/validator-ajv8'; import React from 'react'; -import { WorkbenchEventSource, WorkbenchEventSourceType } from '../../../libs/WorkbenchEventSource'; -import { useEnvironment } from '../../../libs/useEnvironment'; import { AssistantStateDescription } from '../../../models/AssistantStateDescription'; +import { workbenchConversationEvents } from '../../../routes/FrontDoor'; import { useGetConversationStateQuery, useUpdateConversationStateMutation } from '../../../services/workbench'; import { CustomizedArrayFieldTemplate } from '../../App/FormWidgets/CustomizedArrayFieldTemplate'; import { CustomizedObjectFieldTemplate } from '../../App/FormWidgets/CustomizedObjectFieldTemplate'; @@ -73,37 +72,30 @@ export const AssistantInspector: React.FC = (props) => const [updateConversationState] = useUpdateConversationStateMutation(); const [formData, setFormData] = React.useState(); const [isSubmitting, setIsSubmitting] = React.useState(false); - const environment = useEnvironment(); if (stateError) { const errorMessage = JSON.stringify(stateError); throw new Error(`Error loading assistant state: ${errorMessage}`); } - React.useEffect(() => { - var workbenchEventSource: WorkbenchEventSource | undefined; - - const handleEvent = (event: EventSourceMessage) => { + const handleEvent = React.useCallback( + (event: EventSourceMessage) => { const { data } = JSON.parse(event.data); if (assistantId !== data['assistant_id']) return; if (stateDescription.id !== data['state_id']) return; if (conversationId !== data['conversation_id']) return; refetchState(); - }; + }, + [assistantId, stateDescription.id, conversationId, refetchState], + ); - (async () => { - workbenchEventSource = await WorkbenchEventSource.createOrUpdate( - environment.url, - WorkbenchEventSourceType.Conversation, - conversationId, - ); - workbenchEventSource.addEventListener('assistant.state.updated', handleEvent); - })(); + React.useEffect(() => { + workbenchConversationEvents.addEventListener('assistant.state.updated', handleEvent); return () => { - workbenchEventSource?.removeEventListener('assistant.state.updated', handleEvent); + workbenchConversationEvents.removeEventListener('assistant.state.updated', handleEvent); }; - }, [environment, assistantId, stateDescription.id, conversationId, refetchState]); + }, [handleEvent]); React.useEffect(() => { if (isFetchingState) return; @@ -210,7 +202,6 @@ export const AssistantInspector: React.FC = (props) => const a = document.createElement('a'); a.download = filename; a.href = href; - // document.body.appendChild(a); a.click(); }; diff --git a/workbench-app/src/components/Conversations/Canvas/CanvasControls.tsx b/workbench-app/src/components/Conversations/Canvas/CanvasControls.tsx deleted file mode 100644 index 8ac6247e..00000000 --- a/workbench-app/src/components/Conversations/Canvas/CanvasControls.tsx +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { Button, makeStyles, shorthands, tokens, Tooltip } from '@fluentui/react-components'; -import { AppsList24Regular, BookInformation24Regular, Dismiss24Regular } from '@fluentui/react-icons'; -import { EventSourceMessage } from '@microsoft/fetch-event-source'; -import React from 'react'; -import { useChatCanvasController } from '../../../libs/useChatCanvasController'; -import { useEnvironment } from '../../../libs/useEnvironment'; -import { WorkbenchEventSource, WorkbenchEventSourceType } from '../../../libs/WorkbenchEventSource'; -import { useAppSelector } from '../../../redux/app/hooks'; - -const useClasses = makeStyles({ - root: { - zIndex: tokens.zIndexFloating, - position: 'absolute', - backgroundColor: `rgba(255, 255, 255, 0.5)`, - borderBottomLeftRadius: tokens.borderRadiusMedium, - top: 0, - right: 0, - display: 'flex', - flexDirection: 'row', - gap: tokens.spacingHorizontalM, - ...shorthands.padding(tokens.spacingVerticalM, tokens.spacingHorizontalM), - }, -}); - -interface CanvasControlsProps { - conversationId: string; -} - -export const CanvasControls: React.FC = (props) => { - const { conversationId } = props; - const classes = useClasses(); - const chatCanvasState = useAppSelector((state) => state.chatCanvas); - const environment = useEnvironment(); - const chatCanvasController = useChatCanvasController(); - - React.useEffect(() => { - var workbenchEventSource: WorkbenchEventSource | undefined; - - const handleFocusEvent = async (event: EventSourceMessage) => { - const { data } = JSON.parse(event.data); - chatCanvasController.transitionToState({ - open: true, - mode: 'assistant', - selectedAssistantId: data['assistant_id'], - selectedAssistantStateId: data['state_id'], - }); - }; - - (async () => { - workbenchEventSource = await WorkbenchEventSource.createOrUpdate( - environment.url, - WorkbenchEventSourceType.Conversation, - conversationId, - ); - workbenchEventSource.addEventListener('assistant.state.focus', handleFocusEvent); - })(); - - return () => { - workbenchEventSource?.removeEventListener('assistant.state.focus', handleFocusEvent); - }; - }, [environment, conversationId, chatCanvasController]); - - const handleActivateConversation = () => { - chatCanvasController.transitionToState({ open: true, mode: 'conversation' }); - }; - - const handleActivateAssistant = () => { - chatCanvasController.transitionToState({ open: true, mode: 'assistant' }); - }; - - const handleDismiss = async () => { - chatCanvasController.transitionToState({ open: false }); - }; - - const conversationActive = chatCanvasState.mode === 'conversation' && chatCanvasState.open; - const assistantActive = chatCanvasState.mode === 'assistant' && chatCanvasState.open; - - return ( -
- -
- ); -}; diff --git a/workbench-app/src/components/Conversations/Canvas/CanvasDrawer.tsx b/workbench-app/src/components/Conversations/Canvas/CanvasDrawer.tsx deleted file mode 100644 index bc5465f9..00000000 --- a/workbench-app/src/components/Conversations/Canvas/CanvasDrawer.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { makeStyles, mergeClasses, shorthands, Title3, tokens } from '@fluentui/react-components'; -import React from 'react'; - -const useClasses = makeStyles({ - drawerContainer: { - top: 0, - height: '100%', - transition: `width ${tokens.durationNormal} ${tokens.curveEasyEase}`, - overflow: 'hidden', - backgroundColor: tokens.colorNeutralBackground1, - zIndex: tokens.zIndexContent, - boxShadow: tokens.shadow8Brand, - display: 'flex', - flexDirection: 'column', - }, - drawerTitle: { - flexShrink: 0, - ...shorthands.padding( - tokens.spacingVerticalXXL, - tokens.spacingHorizontalXXL, - tokens.spacingVerticalS, - tokens.spacingHorizontalXXL, - ), - }, - drawerContent: { - flexGrow: 1, - padding: tokens.spacingHorizontalM, - overflow: 'auto', - '::-webkit-scrollbar-track': { - backgroundColor: tokens.colorNeutralBackground1, - }, - '::-webkit-scrollbar-thumb': { - backgroundColor: tokens.colorNeutralStencil1Alpha, - }, - }, -}); - -interface CanvasDrawerProps { - openClassName?: string; - className?: string; - open?: boolean; - mode?: 'inline' | 'overlay'; - side?: 'left' | 'right'; - title?: string | React.ReactNode; - children?: React.ReactNode; -} - -export const CanvasDrawer: React.FC = (props) => { - const { openClassName, className, open, mode, side, title, children } = props; - const classes = useClasses(); - - const drawerStyle: React.CSSProperties = { - right: side === 'right' ? 0 : undefined, - width: open ? undefined : '0px', - position: mode === 'inline' ? 'relative' : 'fixed', - }; - - const titleContent = typeof title === 'string' ? {title} : title; - - return ( -
-
{titleContent}
-
{children}
-
- ); -}; diff --git a/workbench-app/src/components/Conversations/Canvas/ConversationDrawer.tsx b/workbench-app/src/components/Conversations/Canvas/ConversationDrawer.tsx deleted file mode 100644 index b764a3b7..00000000 --- a/workbench-app/src/components/Conversations/Canvas/ConversationDrawer.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { makeStyles, tokens } from '@fluentui/react-components'; -import React from 'react'; -import { Conversation } from '../../../models/Conversation'; -import { ConversationFile } from '../../../models/ConversationFile'; -import { ConversationParticipant } from '../../../models/ConversationParticipant'; -import { CanvasDrawer } from './CanvasDrawer'; -import { ConversationCanvas } from './ConversationCanvas'; - -const useClasses = makeStyles({ - drawer: { - backgroundImage: `linear-gradient(to right, ${tokens.colorNeutralBackground1}, ${tokens.colorBrandBackground2})`, - }, - drawerOpenInline: { - width: 'min(50vw, 500px)', - }, - drawerOpenOverlay: { - width: '100%', - }, -}); - -interface ConversationDrawerProps { - open: boolean; - mode: 'inline' | 'overlay'; - readOnly: boolean; - conversation: Conversation; - conversationParticipants: ConversationParticipant[]; - conversationFiles: ConversationFile[]; - preventAssistantModifyOnParticipantIds?: string[]; -} - -export const ConversationDrawer: React.FC = (props) => { - const { - open, - mode, - readOnly, - conversation, - conversationParticipants, - conversationFiles, - preventAssistantModifyOnParticipantIds, - } = props; - const classes = useClasses(); - - return ( - - - - ); -}; diff --git a/workbench-app/src/components/Conversations/Canvas/InteractCanvas.tsx b/workbench-app/src/components/Conversations/Canvas/InteractCanvas.tsx deleted file mode 100644 index aa52eb30..00000000 --- a/workbench-app/src/components/Conversations/Canvas/InteractCanvas.tsx +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { makeStyles, mergeClasses, tokens } from '@fluentui/react-components'; -import debug from 'debug'; -import React from 'react'; -import { Constants } from '../../../Constants'; -import { useChatCanvasController } from '../../../libs/useChatCanvasController'; -import { Assistant } from '../../../models/Assistant'; -import { Conversation } from '../../../models/Conversation'; -import { ConversationFile } from '../../../models/ConversationFile'; -import { ConversationParticipant } from '../../../models/ConversationParticipant'; -import { useAppSelector } from '../../../redux/app/hooks'; -import { AssistantDrawer } from './AssistantDrawer'; -import { CanvasControls } from './CanvasControls'; -import { ConversationDrawer } from './ConversationDrawer'; - -const log = debug(Constants.debug.root).extend('InteractCanvas'); - -const useClasses = makeStyles({ - controls: { - position: 'relative', - top: 0, - right: 0, - zIndex: tokens.zIndexFloating, - }, - controlsInline: { - position: 'absolute', - }, - controlsOverlay: { - position: 'fixed', - }, -}); - -interface InteractCanvasProps { - conversationAssistants?: Assistant[]; - conversationParticipants: ConversationParticipant[]; - conversationFiles: ConversationFile[]; - conversation: Conversation; - preventAssistantModifyOnParticipantIds?: string[]; - readOnly: boolean; -} - -export const InteractCanvas: React.FC = (props) => { - const { - conversationAssistants, - conversationParticipants, - conversationFiles, - conversation, - preventAssistantModifyOnParticipantIds, - readOnly, - } = props; - const classes = useClasses(); - const chatCanvasState = useAppSelector((state) => state.chatCanvas); - const chatCanvasController = useChatCanvasController(); - const [firstRun, setFirstRun] = React.useState(true); - const [selectedAssistant, setSelectedAssistant] = React.useState(); - const [drawerMode, setDrawerMode] = React.useState<'inline' | 'overlay'>('inline'); - - const onMediaQueryChange = React.useCallback( - (matches: boolean) => setDrawerMode(matches ? 'overlay' : 'inline'), - [setDrawerMode], - ); - - React.useEffect(() => { - const mediaQuery = window.matchMedia(`(max-width: ${Constants.app.responsiveBreakpoints.chatCanvas})`); - - if (mediaQuery.matches) { - setDrawerMode('overlay'); - } - - mediaQuery.addEventListener('change', (event) => onMediaQueryChange(event.matches)); - - return () => { - mediaQuery.removeEventListener('change', (event) => onMediaQueryChange(event.matches)); - }; - }, [onMediaQueryChange]); - - // Set the selected assistant based on the interact canvas state - React.useEffect(() => { - if (!chatCanvasState.selectedAssistantId || !chatCanvasState.open || chatCanvasState.mode !== 'assistant') { - // If the assistant id is not set, the canvas is closed, or the mode is not assistant, clear - // the selected assistant and exit early - setSelectedAssistant(undefined); - return; - } - - // If no assistants are in the conversation, unset the selected assistant - if (!conversationAssistants || conversationAssistants.length === 0) { - if (selectedAssistant) setSelectedAssistant(undefined); - // If this is the first run, transition to the conversation mode to add an assistant - if (firstRun) { - log('No assistants in the conversation on first run, transitioning to conversation mode'); - chatCanvasController.transitionToState({ open: true, mode: 'conversation' }); - setFirstRun(false); - } - return; - } - - // Find the assistant that corresponds to the selected assistant id - const assistant = conversationAssistants.find( - (assistant) => assistant.id === chatCanvasState.selectedAssistantId, - ); - - // If the selected assistant is not found in the conversation, select the first assistant in the conversation - if (!assistant) { - log('Selected assistant not found in conversation, selecting the first assistant in the conversation'); - chatCanvasController.setState({ selectedAssistantId: conversationAssistants[0].id }); - return; - } - - // If the requested assistant is different from the selected assistant, set the selected assistant - if (selectedAssistant?.id !== assistant?.id) { - log(`Setting selected assistant to ${assistant.id}`); - setSelectedAssistant(assistant); - } - }, [conversationAssistants, firstRun, chatCanvasController, chatCanvasState, selectedAssistant]); - - // Determine which drawer to open, default to none - const openDrawer = chatCanvasState.open ? chatCanvasState.mode : 'none'; - - const controlsClassName = mergeClasses( - classes.controls, - openDrawer !== 'none' && drawerMode === 'overlay' ? classes.controlsOverlay : classes.controlsInline, - ); - - return ( - <> -
- -
- - - - ); -}; diff --git a/workbench-app/src/components/Conversations/InteractInput.tsx b/workbench-app/src/components/Conversations/InteractInput.tsx index 904e709c..b6d85bdc 100644 --- a/workbench-app/src/components/Conversations/InteractInput.tsx +++ b/workbench-app/src/components/Conversations/InteractInput.tsx @@ -493,7 +493,7 @@ export const InteractInput: React.FC = (props) => { ) : (
- {/* // this is for injecting controls for supporting features like workflow */} + {/* this is for injecting controls for other features */} {additionalContent} = (props) => { return ( conversation.metadata?.workflow_run_id === undefined) - .toSorted((a, b) => a.title.toLocaleLowerCase().localeCompare(b.title.toLocaleLowerCase())) + ?.toSorted((a, b) => a.title.toLocaleLowerCase().localeCompare(b.title.toLocaleLowerCase())) .map((conversation) => ( } label={conversation.title} - linkUrl={`/conversation/${encodeURIComponent(conversation.id)}`} + linkUrl={`/${encodeURIComponent(conversation.id)}`} actions={ <> = (props) => { const chatCanvasController = useChatCanvasController(); React.useEffect(() => { - var workbenchEventSource: WorkbenchEventSource | undefined; - const handleFocusEvent = async (event: EventSourceMessage) => { const { data } = JSON.parse(event.data); chatCanvasController.transitionToState({ @@ -41,17 +39,10 @@ export const ChatControls: React.FC = (props) => { }); }; - (async () => { - workbenchEventSource = await WorkbenchEventSource.createOrUpdate( - environment.url, - WorkbenchEventSourceType.Conversation, - conversationId, - ); - workbenchEventSource.addEventListener('assistant.state.focus', handleFocusEvent); - })(); + workbenchConversationEvents.addEventListener('assistant.state.focus', handleFocusEvent); return () => { - workbenchEventSource?.removeEventListener('assistant.state.focus', handleFocusEvent); + workbenchConversationEvents.removeEventListener('assistant.state.focus', handleFocusEvent); }; }, [environment, conversationId, chatCanvasController]); diff --git a/workbench-app/src/components/FrontDoor/Controls/ConversationList.tsx b/workbench-app/src/components/FrontDoor/Controls/ConversationList.tsx index 85b67475..983b2f26 100644 --- a/workbench-app/src/components/FrontDoor/Controls/ConversationList.tsx +++ b/workbench-app/src/components/FrontDoor/Controls/ConversationList.tsx @@ -6,9 +6,9 @@ import { EventSourceMessage } from '@microsoft/fetch-event-source'; import React from 'react'; import { useConversationUtility } from '../../../libs/useConversationUtility'; import { useEnvironment } from '../../../libs/useEnvironment'; -import { WorkbenchEventSource, WorkbenchEventSourceType } from '../../../libs/WorkbenchEventSource'; import { Conversation } from '../../../models/Conversation'; import { useAppSelector } from '../../../redux/app/hooks'; +import { workbenchUserEvents } from '../../../routes/FrontDoor'; import { useGetConversationsQuery } from '../../../services/workbench'; import { Loading } from '../../App/Loading'; import { PresenceMotionList } from '../../App/PresenceMotionList'; @@ -73,29 +73,19 @@ export const ConversationList: React.FC = () => { await refetchConversations(); }; - (async () => { - // create or update the event source - const workbenchEventSource = await WorkbenchEventSource.createOrUpdate( - environment.url, - WorkbenchEventSourceType.User, - ); - workbenchEventSource.addEventListener('message.created', conversationHandler); - workbenchEventSource.addEventListener('message.deleted', conversationHandler); - workbenchEventSource.addEventListener('conversation.updated', conversationHandler); - workbenchEventSource.addEventListener('participant.created', conversationHandler); - workbenchEventSource.addEventListener('participant.updated', conversationHandler); - })(); + workbenchUserEvents.addEventListener('message.created', conversationHandler); + workbenchUserEvents.addEventListener('message.deleted', conversationHandler); + workbenchUserEvents.addEventListener('conversation.updated', conversationHandler); + workbenchUserEvents.addEventListener('participant.created', conversationHandler); + workbenchUserEvents.addEventListener('participant.updated', conversationHandler); return () => { // remove event listeners - (async () => { - const workbenchEventSource = await WorkbenchEventSource.getInstance(WorkbenchEventSourceType.User); - workbenchEventSource.removeEventListener('message.created', conversationHandler); - workbenchEventSource.removeEventListener('message.deleted', conversationHandler); - workbenchEventSource.removeEventListener('conversation.updated', conversationHandler); - workbenchEventSource.removeEventListener('participant.created', conversationHandler); - workbenchEventSource.removeEventListener('participant.updated', conversationHandler); - })(); + workbenchUserEvents.removeEventListener('message.created', conversationHandler); + workbenchUserEvents.removeEventListener('message.deleted', conversationHandler); + workbenchUserEvents.removeEventListener('conversation.updated', conversationHandler); + workbenchUserEvents.removeEventListener('participant.created', conversationHandler); + workbenchUserEvents.removeEventListener('participant.updated', conversationHandler); }; }, [conversationsLoading, conversationsUninitialized, environment.url, refetchConversations]); diff --git a/workbench-app/src/components/FrontDoor/Controls/ConversationListOptions.tsx b/workbench-app/src/components/FrontDoor/Controls/ConversationListOptions.tsx index 7e8ff118..01d2964c 100644 --- a/workbench-app/src/components/FrontDoor/Controls/ConversationListOptions.tsx +++ b/workbench-app/src/components/FrontDoor/Controls/ConversationListOptions.tsx @@ -152,9 +152,6 @@ export const ConversationListOptions: React.FC = ( // split conversations into pinned and unpinned const splitByPinned: Record = { pinned: [], unpinned: [] }; filteredConversations.forEach((conversation) => { - if (conversation.metadata?.workflow_run_id !== undefined) { - return; - } if (isPinned(conversation)) { splitByPinned.pinned.push(conversation); } else { diff --git a/workbench-app/src/components/Workflows/MyWorkflows.tsx b/workbench-app/src/components/Workflows/MyWorkflows.tsx deleted file mode 100644 index 9901b226..00000000 --- a/workbench-app/src/components/Workflows/MyWorkflows.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { Button } from '@fluentui/react-components'; -import { EditRegular, Flowchart24Regular } from '@fluentui/react-icons'; -import React from 'react'; -import { Link } from 'react-router-dom'; -import { WorkflowDefinition } from '../../models/WorkflowDefinition'; -import { useGetWorkflowDefinitionsQuery } from '../../services/workbench/workflow'; -import { CommandButton } from '../App/CommandButton'; -import { MiniControl } from '../App/MiniControl'; -import { MyItemsManager } from '../App/MyItemsManager'; -import { WorkflowCreate } from './WorkflowCreate'; -import { WorkflowRemove } from './WorkflowRemove'; - -interface MyWorkflowsProps { - workflowDefinitions?: WorkflowDefinition[]; - title?: string; - hideInstruction?: boolean; - onCreate?: (workflow: WorkflowDefinition) => void; -} - -export const MyWorkflows: React.FC = (props) => { - const { workflowDefinitions, title, hideInstruction, onCreate } = props; - const { refetch: refetchWorkflowDefinitions } = useGetWorkflowDefinitionsQuery(); - const [workflowCreateOpen, setWorkflowCreateOpen] = React.useState(false); - - const handleWorkflowDefinitionCreate = async (workflowDefinition: WorkflowDefinition) => { - await refetchWorkflowDefinitions(); - onCreate?.(workflowDefinition); - }; - - return ( - a.label.localeCompare(b.label)) - .map((workflow) => ( - } - label={workflow.label} - linkUrl={`/workflow/${encodeURIComponent(workflow.id)}`} - actions={ - <> - - - - )} - {workflowRun.conversationMappings.length > 1 && ( - <> - / Current Conversation: {currentConversationLabel} - - )} -
- ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowConversation.tsx b/workbench-app/src/components/Workflows/WorkflowConversation.tsx deleted file mode 100644 index 0a2be597..00000000 --- a/workbench-app/src/components/Workflows/WorkflowConversation.tsx +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { - Button, - MessageBar, - MessageBarBody, - MessageBarTitle, - makeStyles, - mergeClasses, - shorthands, - tokens, -} from '@fluentui/react-components'; -import { BookInformation24Regular } from '@fluentui/react-icons'; -import { EventSourceMessage } from '@microsoft/fetch-event-source'; -import React from 'react'; -import { Constants } from '../../Constants'; -import { InteractHistory } from '../../components/Conversations/InteractHistory'; -import { InteractInput } from '../../components/Conversations/InteractInput'; -import { WorkbenchEventSource, WorkbenchEventSourceType } from '../../libs/WorkbenchEventSource'; -import { useChatCanvasController } from '../../libs/useChatCanvasController'; -import { useEnvironment } from '../../libs/useEnvironment'; -import { useHistoryUtility } from '../../libs/useHistoryUtility'; -import { useSiteUtility } from '../../libs/useSiteUtility'; -import { WorkflowDefinition } from '../../models/WorkflowDefinition'; -import { WorkflowRun } from '../../models/WorkflowRun'; -import { useAppDispatch, useAppSelector } from '../../redux/app/hooks'; -import { setChatWidthPercent } from '../../redux/features/app/appSlice'; -import { useGetWorkflowRunAssistantsQuery } from '../../services/workbench'; -import { Loading } from '../App/Loading'; -import { ConversationCanvas } from '../Conversations/Canvas/ConversationCanvas'; -import { WorkflowControl } from './WorkflowControl'; - -const useClasses = makeStyles({ - root: { - display: 'grid', - gridTemplateColumns: '1fr auto', - gridTemplateRows: '1fr', - height: '100%', - }, - main: { - position: 'relative', - display: 'grid', - gridTemplateColumns: '1fr', - gridTemplateRows: '1fr auto', - height: '100%', - }, - history: { - position: 'relative', - display: 'flex', - flexDirection: 'row', - justifyContent: 'center', - gap: tokens.spacingVerticalM, - }, - controls: { - position: 'absolute', - top: 0, - left: 0, - bottom: 0, - display: 'flex', - flexDirection: 'row', - justifyContent: 'stretch', - zIndex: tokens.zIndexFloating, - }, - body: { - display: 'flex', - flexDirection: 'column', - gap: tokens.spacingVerticalM, - }, - drawerButton: { - position: 'absolute', - ...shorthands.padding(tokens.spacingVerticalS), - }, - resizer: { - ...shorthands.borderLeft(tokens.strokeWidthThin, 'solid', tokens.colorNeutralBackground5), - width: '8px', - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - cursor: 'col-resize', - resize: 'horizontal', - ':hover': { - borderLeftWidth: '4px', - }, - }, - resizerActive: { - borderLeftWidth: '4px', - borderLeftColor: tokens.colorNeutralBackground5Pressed, - }, - inspectorButton: { - position: 'absolute', - top: 0, - right: 0, - ...shorthands.padding(tokens.spacingVerticalS), - zIndex: tokens.zIndexFloating, - }, - inspectors: { - position: 'relative', - backgroundColor: tokens.colorNeutralBackgroundAlpha, - height: '100%', - overflowY: 'auto', - }, - input: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'center', - backgroundColor: tokens.colorNeutralBackgroundAlpha, - }, - historyContent: { - // do not use flexbox here, it breaks the virtuoso - width: '100%', - maxWidth: `${Constants.app.maxContentWidth}px`, - ...shorthands.padding(0, tokens.spacingHorizontalXXXL), - }, - historyContentWithInspector: { - paddingRight: tokens.spacingHorizontalNone, - }, -}); - -interface WorkflowConversationProps { - workflowDefinition: WorkflowDefinition; - workflowRun: WorkflowRun; - conversationId: string; -} - -export const WorkflowConversation: React.FC = (props) => { - const { conversationId, workflowRun } = props; - - const classes = useClasses(); - const chatWidthPercent = useAppSelector((state) => state.app.chatWidthPercent); - const chatCanvasState = useAppSelector((state) => state.chatCanvas); - const dispatch = useAppDispatch(); - const chatCanvasController = useChatCanvasController(); - const animationFrame = React.useRef(0); - const resizeHandleRef = React.useRef(null); - const [isResizing, setIsResizing] = React.useState(false); - const siteUtility = useSiteUtility(); - const environment = useEnvironment(); - - const { - data: workflowRunAssistants, - isLoading: isLoadingWorkflowRunAssistants, - error: workflowRunAssistantsError, - } = useGetWorkflowRunAssistantsQuery(workflowRun.id); - - const { - conversation, - allConversationMessages, - conversationParticipants, - assistants, - assistantCapabilities, - error: historyError, - isLoading: historyIsLoading, - assistantCapabilitiesIsFetching, - } = useHistoryUtility(conversationId); - - if (workflowRunAssistantsError) { - const errorMessage = JSON.stringify(workflowRunAssistantsError); - throw new Error(`Error loading workflow run assistants: ${errorMessage}`); - } - - if (historyError) { - const errorMessage = JSON.stringify(historyError); - throw new Error(`Error loading conversation (${conversationId}): ${errorMessage}`); - } - - React.useEffect(() => { - if (conversation) { - siteUtility.setDocumentTitle(conversation.title); - } - }, [conversation, siteUtility]); - - const startResizing = React.useCallback(() => setIsResizing(true), []); - const stopResizing = React.useCallback(() => setIsResizing(false), []); - - const resize = React.useCallback( - (event: { clientX: number }) => { - animationFrame.current = requestAnimationFrame(() => { - if (isResizing && resizeHandleRef.current) { - const desiredWidth = - resizeHandleRef.current.getBoundingClientRect().left + - (event.clientX - resizeHandleRef.current.getBoundingClientRect().left); - const desiredWidthPercent = (desiredWidth / window.innerWidth) * 100; - const minChatWidthPercent = Constants.app.minChatWidthPercent; - dispatch( - setChatWidthPercent( - Math.max(minChatWidthPercent, Math.min(desiredWidthPercent, 100 - minChatWidthPercent)), - ), - ); - } - }); - }, - [dispatch, isResizing], - ); - - React.useEffect(() => { - window.addEventListener('mousemove', resize); - window.addEventListener('mouseup', stopResizing); - - return () => { - cancelAnimationFrame(animationFrame.current); - window.removeEventListener('mousemove', resize); - window.removeEventListener('mouseup', stopResizing); - }; - }, [resize, stopResizing]); - - React.useEffect(() => { - var workbenchEventSource: WorkbenchEventSource | undefined; - - const handleFocusEvent = (event: EventSourceMessage) => { - const { data } = JSON.parse(event.data); - chatCanvasController.transitionToState({ - open: true, - mode: 'assistant', - selectedAssistantId: data['assistant_id'], - selectedAssistantStateId: data['state_id'], - }); - }; - - (async () => { - workbenchEventSource = await WorkbenchEventSource.createOrUpdate( - environment.url, - WorkbenchEventSourceType.Conversation, - conversationId, - ); - workbenchEventSource.addEventListener('assistant.state.focus', handleFocusEvent); - })(); - - return () => { - workbenchEventSource?.removeEventListener('assistant.state.focus', handleFocusEvent); - }; - }, [environment, conversationId, dispatch, chatCanvasController]); - - if ( - isLoadingWorkflowRunAssistants || - historyIsLoading || - assistantCapabilitiesIsFetching || - !assistantCapabilities || - !conversation || - !allConversationMessages || - !conversationParticipants || - !assistants || - !workflowRunAssistants - ) { - return ; - } - - const readOnly = conversation.conversationPermission === 'read'; - - return ( -
-
-
-
- -
-
-
- - - - Workflow Run Support Under Construction: - All features are early versions that need further testing. UX will be refined - further. - - - - - } - /> -
- {!chatCanvasState.open && ( -
-
- )} -
-
event.preventDefault()}> -
- {chatCanvasState.open && ( - assistant.id)} - /> - )} -
-
- ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowCreate.tsx b/workbench-app/src/components/Workflows/WorkflowCreate.tsx deleted file mode 100644 index d2e61ec8..00000000 --- a/workbench-app/src/components/Workflows/WorkflowCreate.tsx +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { generateUuid } from '@azure/ms-rest-js'; -import { - Button, - DialogOpenChangeData, - DialogOpenChangeEvent, - DialogTrigger, - Field, - Input, - makeStyles, - tokens, -} from '@fluentui/react-components'; -import React from 'react'; -import { useWorkbenchService } from '../../libs/useWorkbenchService'; -import { ConversationDefinition, WorkflowDefinition, WorkflowState } from '../../models/WorkflowDefinition'; -import { useCreateWorkflowDefinitionMutation } from '../../services/workbench/workflow'; -import { DialogControl } from '../App/DialogControl'; - -const useClasses = makeStyles({ - dialogContent: { - display: 'flex', - flexDirection: 'column', - gap: tokens.spacingVerticalM, - }, -}); - -interface WorkflowCreateProps { - open: boolean; - onOpenChange?: (open: boolean) => void; - onCreate?: (workflowDefinition: WorkflowDefinition) => void; -} - -export const WorkflowCreate: React.FC = (props) => { - const { open, onOpenChange, onCreate } = props; - const classes = useClasses(); - const [createWorkflowDefinition] = useCreateWorkflowDefinitionMutation(); - const [label, setLabel] = React.useState(''); - const [submitted, setSubmitted] = React.useState(false); - const workbenchService = useWorkbenchService(); - - const handleSave = async () => { - if (submitted) { - return; - } - setSubmitted(true); - - const rootConversationDefinition: ConversationDefinition = { - id: generateUuid(), - title: 'Main Conversation', - }; - - const initialState: WorkflowState = { - id: generateUuid(), - label: 'Start', - conversationDefinitionId: rootConversationDefinition.id, - assistantDataList: [], - editorData: { - position: { x: 0, y: 0 }, - }, - outlets: [ - { - id: generateUuid(), - label: 'Next Step', - prompts: { - evaluateTransition: 'User has indicated they want to proceed to the next step.', - contextTransfer: 'Summarize the recent conversation.', - }, - }, - ], - }; - - const defaults = await workbenchService.getWorkflowDefinitionDefaultsAsync(); - if (!defaults) { - throw new Error('Failed to get workflow definition defaults'); - } - - const workflowDefinition = await createWorkflowDefinition({ - ...defaults, - label, - startStateId: initialState.id, - states: [...defaults.states, initialState], - definitions: { - ...defaults.definitions, - conversations: [...defaults.definitions.conversations, rootConversationDefinition], - }, - }).unwrap(); - onOpenChange?.(false); - onCreate?.(workflowDefinition); - - setSubmitted(false); - }; - - React.useEffect(() => { - if (!open) { - return; - } - setLabel(''); - setSubmitted(false); - }, [open]); - - const handleOpenChange = React.useCallback( - (_event: DialogOpenChangeEvent, data: DialogOpenChangeData) => { - onOpenChange?.(data.open); - }, - [onOpenChange], - ); - - return ( - { - event.preventDefault(); - handleSave(); - }} - > - - setLabel(data.value)} - aria-autocomplete="none" - /> - - - , - ]} - /> - ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowDesigner/AssistantDefinitionAdd.tsx b/workbench-app/src/components/Workflows/WorkflowDesigner/AssistantDefinitionAdd.tsx deleted file mode 100644 index a9497305..00000000 --- a/workbench-app/src/components/Workflows/WorkflowDesigner/AssistantDefinitionAdd.tsx +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { - Button, - Divider, - Menu, - MenuItem, - MenuList, - MenuPopover, - MenuTrigger, - makeStyles, -} from '@fluentui/react-components'; -import { Bot24Regular, BotAddRegular, Sparkle24Regular } from '@fluentui/react-icons'; -import React from 'react'; -import { useWorkbenchService } from '../../../libs/useWorkbenchService'; -import { AssistantDefinition, WorkflowDefinition } from '../../../models/WorkflowDefinition'; -import { AssistantDefinitionCreate } from './AssistantDefinitionCreate'; - -const useClasses = makeStyles({ - menuList: { - maxHeight: 'calc(100vh - 200px)', - }, -}); - -interface AssistantAddProps { - workflowDefinition: WorkflowDefinition; - stateIdToEdit: string; - onChange: (newValue: WorkflowDefinition) => void; -} - -export const AssistantDefinitionAdd: React.FC = (props) => { - const { workflowDefinition, stateIdToEdit, onChange } = props; - const classes = useClasses(); - const workbenchService = useWorkbenchService(); - const [assistantCreateOpen, setAssistantCreateOpen] = React.useState(false); - - const stateToEdit = workflowDefinition.states.find((state) => state.id === stateIdToEdit); - if (!stateToEdit) { - throw new Error(`State not found: ${stateIdToEdit}`); - } - - const handleNewAssistant = () => { - setAssistantCreateOpen(true); - }; - - const handleCreateAssistantDefinition = async (assistantDefinition: AssistantDefinition) => { - const assistantServiceInfo = await workbenchService.getAssistantServiceInfoAsync( - assistantDefinition.assistantServiceId, - ); - if (!assistantServiceInfo) { - throw new Error(`Assistant service not found for assistant ${assistantDefinition.id}`); - } - - onChange({ - ...workflowDefinition, - definitions: { - ...workflowDefinition.definitions, - assistants: [...workflowDefinition.definitions.assistants, assistantDefinition], - }, - states: workflowDefinition.states.map((state) => { - if (state.id === stateIdToEdit) { - return { - ...state, - assistantDataList: [ - ...(state.assistantDataList || []), - { - assistantDefinitionId: assistantDefinition.id, - configData: assistantServiceInfo.defaultConfig.config, - }, - ], - }; - } - return state; - }), - }); - }; - - const handleAddAssistant = async (assistantDefinitionId: string) => { - const assistantDefinition = workflowDefinition.definitions.assistants.find( - (possibleAssistantDefinition) => possibleAssistantDefinition.id === assistantDefinitionId, - ); - if (!assistantDefinition) { - throw new Error(`Assistant definition not found: ${assistantDefinitionId}`); - } - - const assistantServiceInfo = await workbenchService.getAssistantServiceInfoAsync( - assistantDefinition.assistantServiceId, - ); - if (!assistantServiceInfo) { - throw new Error(`Assistant service not found for assistant ${assistantDefinitionId}`); - } - - onChange({ - ...workflowDefinition, - states: workflowDefinition.states.map((state) => { - if (state.id === stateIdToEdit) { - return { - ...state, - assistantDataList: [ - ...(state.assistantDataList || []), - { - assistantDefinitionId, - configData: assistantServiceInfo.defaultConfig.config, - }, - ], - }; - } - return state; - }), - }); - }; - - const unusedAssistantDefinitions = workflowDefinition.definitions.assistants.filter( - (assistantDefinition) => - !stateToEdit.assistantDataList?.some( - (assistantData) => assistantData.assistantDefinitionId === assistantDefinition.id, - ), - ); - - return ( -
- setAssistantCreateOpen(open)} - onCreate={handleCreateAssistantDefinition} - /> - - - - - - - {unusedAssistantDefinitions.length === 0 && No assistants available} - {unusedAssistantDefinitions - .sort((a, b) => a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase())) - .map((assistantDefinition) => ( - } - onClick={() => handleAddAssistant(assistantDefinition.id)} - > - {assistantDefinition.name} - - ))} - - - } onClick={handleNewAssistant}> - Create new assistant - - - -
- ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowDesigner/AssistantDefinitionCreate.tsx b/workbench-app/src/components/Workflows/WorkflowDesigner/AssistantDefinitionCreate.tsx deleted file mode 100644 index 3d6d9c04..00000000 --- a/workbench-app/src/components/Workflows/WorkflowDesigner/AssistantDefinitionCreate.tsx +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { generateUuid } from '@azure/ms-rest-js'; -import { - Button, - DialogOpenChangeData, - DialogOpenChangeEvent, - DialogTrigger, - Divider, - Dropdown, - Field, - Input, - Label, - Option, - OptionGroup, - Tooltip, - makeStyles, - tokens, -} from '@fluentui/react-components'; -import { Info16Regular } from '@fluentui/react-icons'; -import React from 'react'; -import { Constants } from '../../../Constants'; -import { AssistantServiceRegistration } from '../../../models/AssistantServiceRegistration'; -import { AssistantDefinition } from '../../../models/WorkflowDefinition'; -import { useGetAssistantServiceRegistrationsQuery } from '../../../services/workbench'; -import { DialogControl } from '../../App/DialogControl'; - -const useClasses = makeStyles({ - dialogContent: { - display: 'flex', - flexDirection: 'column', - gap: tokens.spacingVerticalM, - }, - option: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - gap: tokens.spacingHorizontalXS, - }, - optionDescription: { - display: 'flex', - flexDirection: 'column', - gap: tokens.spacingVerticalXS, - }, -}); - -interface AssistantCreateProps { - open: boolean; - onOpenChange?: (open: boolean) => void; - onCreate?: (assistantDefinition: AssistantDefinition) => void; -} - -export const AssistantDefinitionCreate: React.FC = (props) => { - const { open, onOpenChange, onCreate } = props; - const classes = useClasses(); - const [name, setName] = React.useState(''); - const [assistantServiceId, setAssistantServiceId] = React.useState(''); - const [submitted, setSubmitted] = React.useState(false); - - const { - data: assistantServices, - error: getAssistantServicesError, - isLoading: isLoadingAssistantServices, - } = useGetAssistantServiceRegistrationsQuery({}); - - if (getAssistantServicesError) { - const errorMessage = JSON.stringify(getAssistantServicesError); - throw new Error(`Error loading assistant services: ${errorMessage}`); - } - - const handleSave = React.useCallback(async () => { - if (submitted) { - return; - } - setSubmitted(true); - - try { - onOpenChange?.(false); - onCreate?.({ - id: generateUuid(), - name, - assistantServiceId, - }); - } finally { - setSubmitted(false); - } - }, [assistantServiceId, name, onCreate, onOpenChange, submitted]); - - const handleOpenChange = React.useCallback( - (_event: DialogOpenChangeEvent, data: DialogOpenChangeData) => { - onOpenChange?.(data.open); - }, - [onOpenChange], - ); - - const orderedCategories = Object.keys(Constants.assistantCategories); - - const categorizedAssistantServices = (assistantServices ?? []).reduce((acc, assistantService) => { - let assignedCategory = 'Other'; - for (const [category, serviceIds] of Object.entries(Constants.assistantCategories)) { - if (!serviceIds.includes(assistantService.assistantServiceId)) { - continue; - } - assignedCategory = category; - break; - } - if (!acc[assignedCategory]) { - acc[assignedCategory] = []; - } - acc[assignedCategory].push(assistantService); - return acc; - }, {} as Record); - - for (const category of Object.keys(categorizedAssistantServices)) { - if (!orderedCategories.includes(category)) { - orderedCategories.push(category); - } - } - - const options = orderedCategories.map((category) => ( - - {(categorizedAssistantServices[category] ?? []) - .toSorted((a, b) => a.name.localeCompare(b.name)) - .map((assistantService) => ( -
- - ))} - - )); - - if (isLoadingAssistantServices) { - return null; - } - - return ( - } - classNames={{ - dialogContent: classes.dialogContent, - }} - title="New Instance of Assistant" - content={ -
{ - event.preventDefault(); - handleSave(); - return true; - }} - > - - setName(data?.value)} - aria-autocomplete="none" - /> - - - { - if (data.optionValue) { - setAssistantServiceId(data.optionValue as string); - } - - if (data.optionText && name === '') { - setName(data.optionText); - } - }} - > - {options} - - -
- , - ]} - /> - ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowDesigner/ConversationDefinitionCreate.tsx b/workbench-app/src/components/Workflows/WorkflowDesigner/ConversationDefinitionCreate.tsx deleted file mode 100644 index 5c73436b..00000000 --- a/workbench-app/src/components/Workflows/WorkflowDesigner/ConversationDefinitionCreate.tsx +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { generateUuid } from '@azure/ms-rest-js'; -import { - Button, - DialogOpenChangeData, - DialogOpenChangeEvent, - Field, - Input, - makeStyles, - tokens, -} from '@fluentui/react-components'; -import React from 'react'; -import { ConversationDefinition } from '../../../models/WorkflowDefinition'; -import { DialogControl } from '../../App/DialogControl'; - -const useClasses = makeStyles({ - dialogContent: { - display: 'flex', - flexDirection: 'column', - gap: tokens.spacingVerticalM, - }, -}); - -interface ConversationDefinitionCreateProps { - open: boolean; - onOpenChange?: (open: boolean) => void; - onCreate?: (conversationDefinition: ConversationDefinition) => void; -} - -export const ConversationDefinitionCreate: React.FC = (props) => { - const { open, onOpenChange, onCreate } = props; - const classes = useClasses(); - const [title, setTitle] = React.useState(''); - const [submitted, setSubmitted] = React.useState(false); - - const handleSave = React.useCallback(() => { - if (submitted) { - return; - } - setSubmitted(true); - - try { - onOpenChange?.(false); - onCreate?.({ - id: generateUuid(), - title, - }); - } finally { - setSubmitted(false); - } - }, [onCreate, onOpenChange, submitted, title]); - - React.useEffect(() => { - if (!open) { - return; - } - setTitle(''); - }, [open]); - - const handleOpenChange = React.useCallback( - (_event: DialogOpenChangeEvent, data: DialogOpenChangeData) => { - onOpenChange?.(data.open); - }, - [onOpenChange], - ); - - return ( - { - event.preventDefault(); - handleSave(); - }} - > - - setTitle(data?.value)} - aria-autocomplete="none" - /> - - , - ]} - /> - ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowDesigner/DefinitionPropertiesEditor.tsx b/workbench-app/src/components/Workflows/WorkflowDesigner/DefinitionPropertiesEditor.tsx deleted file mode 100644 index 421e5404..00000000 --- a/workbench-app/src/components/Workflows/WorkflowDesigner/DefinitionPropertiesEditor.tsx +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { - Button, - Drawer, - DrawerBody, - DrawerHeader, - DrawerHeaderTitle, - makeStyles, - shorthands, - tokens, -} from '@fluentui/react-components'; -import { Dismiss24Regular, SettingsRegular } from '@fluentui/react-icons'; -import React from 'react'; -import { WorkflowDefinition } from '../../../models/WorkflowDefinition'; -import { CommandButton } from '../../App/CommandButton'; -import { WorkflowDefinitionEditor } from './WorkflowDefinitionEditor'; - -const useClasses = makeStyles({ - drawer: { - '& > .fui-DrawerBody': { - backgroundImage: `linear-gradient(to right, ${tokens.colorNeutralBackground1}, ${tokens.colorBrandBackground2})`, - backgroundSize: '100%', - }, - }, - header: { - ...shorthands.borderBottom(tokens.strokeWidthThick, 'solid', tokens.colorNeutralStroke3), - }, - body: { - ...shorthands.padding(tokens.spacingVerticalM, tokens.spacingHorizontalM), - }, -}); - -interface DefinitionPropertiesEditorProps { - workflowDefinition: WorkflowDefinition; - onChange: (newValue: WorkflowDefinition) => void; - open?: boolean; - onOpenChange?: (open: boolean) => void; -} - -export const DefinitionPropertiesEditor: React.FC = (props) => { - const { workflowDefinition, onChange, open, onOpenChange } = props; - const classes = useClasses(); - const [isOpen, setIsOpen] = React.useState(open ?? false); - - React.useEffect(() => { - setIsOpen(open ?? false); - }, [open]); - - const handleOpenChange = (newOpen: boolean) => { - setIsOpen(newOpen); - if (onOpenChange) { - onOpenChange(newOpen); - } - }; - - return ( - <> - } - iconOnly - asToolbarButton - label="Edit" - onClick={() => handleOpenChange(true)} - /> - handleOpenChange(false)} - position="end" - className={classes.drawer} - > - - } - onClick={() => handleOpenChange(false)} - /> - } - > - Edit Workflow Configuration - - - -
- { - onChange(newValue); - }} - /> -
-
-
- - ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowDesigner/StatePropertiesEditor.tsx b/workbench-app/src/components/Workflows/WorkflowDesigner/StatePropertiesEditor.tsx deleted file mode 100644 index e88511e1..00000000 --- a/workbench-app/src/components/Workflows/WorkflowDesigner/StatePropertiesEditor.tsx +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { - Button, - Drawer, - DrawerBody, - DrawerHeader, - DrawerHeaderTitle, - makeStyles, - shorthands, - tokens, -} from '@fluentui/react-components'; -import { Dismiss24Regular } from '@fluentui/react-icons'; -import React from 'react'; -import { WorkflowDefinition } from '../../../models/WorkflowDefinition'; -import { WorkflowStateEditor } from './WorkflowStateEditor'; - -const useClasses = makeStyles({ - drawer: { - '& > .fui-DrawerBody': { - backgroundImage: `linear-gradient(to right, ${tokens.colorNeutralBackground1}, ${tokens.colorBrandBackground2})`, - backgroundSize: '100%', - }, - }, - header: { - ...shorthands.borderBottom(tokens.strokeWidthThick, 'solid', tokens.colorNeutralStroke3), - }, - body: { - ...shorthands.padding(tokens.spacingVerticalM, tokens.spacingHorizontalM), - }, -}); - -interface StatePropertiesEditorProps { - workflowDefinition: WorkflowDefinition; - stateIdToEdit?: string; - onChange: (newValue: WorkflowDefinition) => void; - onClose: () => void; -} - -export const StatePropertiesEditor: React.FC = (props) => { - const { workflowDefinition, stateIdToEdit, onChange, onClose } = props; - const classes = useClasses(); - - return ( - - - } onClick={onClose} />} - > - Edit State Configuration - - - -
- {stateIdToEdit && ( - { - onChange(newValue); - }} - /> - )} -
-
-
- ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowCanvas.tsx b/workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowCanvas.tsx deleted file mode 100644 index a1c3ddbf..00000000 --- a/workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowCanvas.tsx +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { generateUuid } from '@azure/ms-rest-js'; -import { Button, DialogTrigger, makeStyles } from '@fluentui/react-components'; -import React from 'react'; -import ReactFlow, { - Background, - Connection, - Controls, - Edge, - EdgeChange, - MiniMap, - Node, - NodeChange, - OnConnect, - OnConnectEnd, - OnConnectStart, - OnConnectStartParams, - OnSelectionChangeParams, - addEdge, - applyEdgeChanges, - applyNodeChanges, - useReactFlow, -} from 'reactflow'; -import { Utility } from '../../../libs/Utility'; -import { - ConversationDefinition, - OutletData, - WorkflowDefinition, - WorkflowState, -} from '../../../models/WorkflowDefinition'; -import { DialogControl } from '../../App/DialogControl'; -import { WorkflowStateNode, WorkflowStateNodeData } from './WorkflowStateNode'; - -const useClasses = makeStyles({ - canvas: { - height: '100%', - }, -}); - -const nodeTypes = { - workflowState: WorkflowStateNode, -}; - -interface WorkflowForReactFlow { - id: string; - nodes: Node[]; - edges: Edge[]; -} - -interface WorkflowCanvasProps { - workflowDefinition: WorkflowDefinition; - onChange: (newWorkflowDefinition: WorkflowDefinition) => void; - onSelectWorkflowStateToEdit: (workflowStateId: string) => void; -} - -const defaultExitOutletData: OutletData = { - id: generateUuid(), - label: 'Exit', - prompts: { - evaluateTransition: 'User has indicated they want abort the current task.', - contextTransfer: - 'Capture a summary of the recent conversation including any details about why and what the user chose to abort.', - }, -}; - -export const WorkflowCanvas: React.FC = (props) => { - const { workflowDefinition, onChange, onSelectWorkflowStateToEdit } = props; - const classes = useClasses(); - const reactFlowWrapper = React.useRef(null); - const connectingNodeId = React.useRef(null); - const connectingHandleId = React.useRef(null); - const [selectedNodes, setSelectedNodes] = React.useState([]); - const [selectedEdges, setSelectedEdges] = React.useState([]); - const [showConfirmDelete, setShowConfigDelete] = React.useState(false); - const { screenToFlowPosition } = useReactFlow(); - const [conversationDefinitions, setConversationDefinitions] = React.useState( - workflowDefinition.definitions.conversations, - ); - - const workflowDefinitionToReactFlow = React.useCallback( - (value: WorkflowDefinition, onEdit: (stateId: string) => void): WorkflowForReactFlow => { - if (conversationDefinitions !== value.definitions.conversations) { - setConversationDefinitions(value.definitions.conversations); - } - - return { - id: value.id, - nodes: value.states.map((state) => { - const node: Node = { - id: state.id, - type: 'workflowState', - position: state.editorData.position, - data: { - ...state, - isStart: state.id === value.startStateId, - onEdit: () => onEdit(state.id), - }, - }; - return node; - }), - edges: value.transitions.map((transition) => { - const source = value.states.find((state) => - state.outlets.some((outlet) => outlet.id === transition.sourceOutletId), - ); - if (!source) { - throw new Error(`Source state not found: ${transition.sourceOutletId}`); - } - return { - id: transition.id, - source: source.id, - sourceHandle: transition.sourceOutletId, - target: transition.targetStateId, - }; - }), - }; - }, - [conversationDefinitions], - ); - - // transform the data to the format expected by react-flow - const workflowForReactFlow = React.useMemo( - () => workflowDefinitionToReactFlow(workflowDefinition, onSelectWorkflowStateToEdit), - [workflowDefinitionToReactFlow, workflowDefinition, onSelectWorkflowStateToEdit], - ); - - const reactFlowToWorkflowDefinition = React.useCallback( - (nodes: Node[], edges: Edge[]): WorkflowDefinition => { - return { - ...workflowDefinition, - definitions: { - ...workflowDefinition.definitions, - conversations: conversationDefinitions, - }, - states: nodes.map((node) => { - const { id, position, data } = node; - return { - id, - label: data.label, - conversationDefinitionId: data.conversationDefinitionId, - forceNewConversationInstance: data.forceNewConversationInstance, - assistantDataList: data.assistantDataList, - editorData: { - position, - }, - outlets: data.outlets, - }; - }), - transitions: edges.map((edge) => { - const sourceNode = nodes.find((node) => node.id === edge.source); - if (!sourceNode) { - throw new Error(`Source node not found: ${edge.source}`); - } - const sourceOutlet = sourceNode.data.outlets.find((outlet) => outlet.id === edge.sourceHandle); - if (!sourceOutlet) { - throw new Error(`Source outlet not found: ${edge.sourceHandle}`); - } - return { - id: edge.id, - sourceOutletId: sourceOutlet.id, - targetStateId: edge.target, - }; - }), - }; - }, - [conversationDefinitions, workflowDefinition], - ); - - const updateWorkflow = (nodes: Node[], edges: Edge[]) => { - const validEdges = edges.filter((edge) => { - const sourceNodeId = edge.source; - const node = nodes.find((n) => n.id === sourceNodeId); - if (!node) { - return false; - } - - const sourceHandleId = edge.sourceHandle; - if (sourceHandleId) { - const handle = node.data.outlets.find((outlet) => outlet.id === sourceHandleId); - if (!handle) { - return false; - } - } - - const targetNodeId = edge.target; - const targetNode = nodes.find((n) => n.id === targetNodeId); - if (!targetNode) { - return false; - } - - const targetHandleId = edge.targetHandle; - if (targetHandleId) { - const handle = targetNode.data.outlets.find((outlet) => outlet.id === targetHandleId); - if (!handle) { - return false; - } - } - - return true; - }); - - const updatedWorkflowDefinition = reactFlowToWorkflowDefinition(nodes, validEdges); - - const differences = Utility.deepDiff(updatedWorkflowDefinition, workflowDefinition); - if (Object.entries(differences).length > 0) { - onChange(updatedWorkflowDefinition); - } - }; - - const createNewNode = ( - sourceNodeId: string, - sourceNodeHandleId: string | null, - screenPosition: { x: number; y: number }, - ) => { - // allowing for re-use of the same node configuration as a starting point - const sourceNode = workflowForReactFlow.nodes.find((node) => node.id === sourceNodeId); - if (!sourceNode) { - throw new Error(`Source node not found: ${sourceNodeId}`); - } - - // find the source outlet, to get the label for a starting label for the new node - const sourceOutlet = sourceNode.data.outlets.find((outlet) => outlet.id === sourceNodeHandleId); - if (!sourceOutlet) { - throw new Error(`Source outlet not found: ${sourceNodeHandleId}`); - } - - // create a new conversation for the new node - const newConversationDefinition: ConversationDefinition = { - id: generateUuid(), - title: sourceOutlet.label, - }; - setConversationDefinitions((value) => [...value, newConversationDefinition]); - - // copy the data from the source node as the defaults for the new node - const newNodeId = generateUuid(); - const data: WorkflowState = { - ...sourceNode.data, - label: sourceOutlet.label, - conversationDefinitionId: newConversationDefinition.id, - outlets: sourceNode.data.outlets.map((outlet) => ({ - ...outlet, - id: generateUuid(), - })), - }; - - // add a default exit outlet if one does not already exist - if (data.outlets.every((outlet) => outlet.label !== defaultExitOutletData.label)) { - const exitOutlet = { - ...defaultExitOutletData, - id: generateUuid(), - }; - data.outlets.push(exitOutlet); - } - - // set the new node's position to the click location - const position = screenToFlowPosition({ - x: screenPosition.x, - y: screenPosition.y, - }); - - // create the new node - const newNode: Node = { - id: newNodeId, - position, - type: 'workflowState', - data, - }; - - const nodes = applyNodeChanges( - [ - { - item: newNode, - type: 'add', - }, - ], - workflowForReactFlow.nodes, - ); - - const edges = applyEdgeChanges( - [ - { - item: { - id: generateUuid(), - source: sourceNodeId, - sourceHandle: sourceNodeHandleId, - target: newNodeId, - }, - type: 'add', - }, - ], - workflowForReactFlow.edges, - ); - - updateWorkflow(nodes, edges); - }; - - const handleConnect: OnConnect = (params) => { - connectingNodeId.current = null; - connectingHandleId.current = null; - - const nodes = workflowForReactFlow.nodes; - const edges = addEdge(params, workflowForReactFlow.edges); - - updateWorkflow(nodes, edges); - }; - - const handleConnectStart: OnConnectStart = (_event, params: OnConnectStartParams) => { - const { nodeId, handleId } = params; - - connectingNodeId.current = nodeId; - connectingHandleId.current = handleId; - }; - - const isValidConnection = (connection: Connection) => { - const sourceNode = workflowForReactFlow.nodes.find((node) => node.id === connection.source); - const sourceOutlet = sourceNode?.data.outlets.find((outlet) => outlet.id === connection.sourceHandle); - - const targetNode = workflowForReactFlow.nodes.find((node) => node.id === connection.target); - const targetOutlet = targetNode?.data.outlets.find((outlet) => outlet.id === connection.targetHandle); - - // prevent connecting to an outlet that already has a connection - if ( - workflowForReactFlow.edges.some( - (edge) => - (sourceOutlet && - (edge.sourceHandle === sourceOutlet.id || edge.targetHandle === sourceOutlet.id)) || - (targetOutlet && (edge.sourceHandle === targetOutlet.id || edge.targetHandle === targetOutlet.id)), - ) - ) { - return false; - } - - return true; - }; - - const handleConnectEnd: OnConnectEnd = (event) => { - const sourceNodeId = connectingNodeId.current; - const sourceNodeHandleId = connectingHandleId.current; - if (!sourceNodeId) return; - if (!(event instanceof MouseEvent)) return; - - let targetIsPane = false; - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const target = event.target as any; - // eslint-disable-next-line no-empty - targetIsPane = target.className?.includes?.('react-flow__pane'); - } catch (e) { - // eslint-disable-next-line no-empty - } - - if (targetIsPane) { - // exit early if the source outlet already has a connection - if ( - workflowForReactFlow.edges.some( - (edge) => edge.sourceHandle === sourceNodeHandleId || edge.targetHandle === sourceNodeHandleId, - ) - ) { - return; - } - - // create a new node at the position of the click - createNewNode(sourceNodeId, sourceNodeHandleId, { x: event.clientX, y: event.clientY }); - } - }; - - const handleNodesChange = (changedNodes: NodeChange[]) => { - const nodes = applyNodeChanges( - changedNodes.filter((node) => node.type !== 'remove'), - workflowForReactFlow.nodes, - ); - - const edges = workflowForReactFlow.edges; - - updateWorkflow(nodes, edges); - }; - - const handleEdgesChange = (changedEdges: EdgeChange[]) => { - const nodes = workflowForReactFlow.nodes; - - const edges = applyEdgeChanges( - changedEdges.filter((edge) => edge.type !== 'remove'), - workflowForReactFlow.edges, - ); - - updateWorkflow(nodes, edges); - }; - - const handleEdgesDelete = (edges: Edge[]) => { - if (edges.length > 0) { - setShowConfigDelete(true); - } - }; - - const handleNodesDelete = (nodes: Node[]) => { - // do not allow deleting the start node - if (nodes.some((node) => node.data.isStart)) { - return; - } - - if (nodes.length > 0) { - setShowConfigDelete(true); - } - }; - - const handleSelectionChange = (selection: OnSelectionChangeParams) => { - const { nodes, edges } = selection; - setSelectedNodes(nodes); - setSelectedEdges(edges); - }; - - const onDelete = () => { - const nodeChanges: NodeChange[] = selectedNodes - .filter((node) => !node.data.isRoot) - .map((node) => { - return { - id: node.id, - item: node, - type: 'remove', - }; - }); - - const edgeChanges: EdgeChange[] = selectedEdges.map((edge) => { - return { - id: edge.id, - item: edge, - type: 'remove', - }; - }); - - const nodes = applyNodeChanges(nodeChanges, workflowForReactFlow.nodes); - const edges = applyEdgeChanges(edgeChanges, workflowForReactFlow.edges); - - updateWorkflow(nodes, edges); - setShowConfigDelete(false); - }; - - return ( -
- setShowConfigDelete(false)} - trigger={ - , - ]} - /> - - - - - -
- ); -}; diff --git a/workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowDefinitionEditor.tsx b/workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowDefinitionEditor.tsx deleted file mode 100644 index 65879b0e..00000000 --- a/workbench-app/src/components/Workflows/WorkflowDesigner/WorkflowDefinitionEditor.tsx +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -import { - Card, - Field, - Input, - MessageBar, - MessageBarBody, - Text, - Textarea, - makeStyles, - tokens, -} from '@fluentui/react-components'; -import React from 'react'; -import { WorkflowDefinition } from '../../../models/WorkflowDefinition'; -import { LabelWithDescription } from '../../App/LabelWithDescription'; - -const useClasses = makeStyles({ - root: { - display: 'flex', - flexDirection: 'column', - gap: tokens.spacingVerticalXL, - }, - card: { - backgroundColor: tokens.colorNeutralBackground2, - }, - section: { - display: 'flex', - flexDirection: 'column', - gap: tokens.spacingVerticalM, - }, -}); - -export const isValidWorkflowDefinitionData = ( - value: WorkflowDefinition, -): { - isValid: boolean; - errors: string[]; -} => { - const errors: string[] = []; - if (!value.label) { - errors.push('Label is required'); - } - if (!value.instructions.contextTransfer) { - errors.push('Context transfer instruction is required'); - } - return { - isValid: errors.length === 0, - errors, - }; -}; - -interface WorkflowDefinitionEditorProps { - workflowDefinition: WorkflowDefinition; - onChange: (newValue: WorkflowDefinition) => void; -} - -export const WorkflowDefinitionEditor: React.FC = (props) => { - const { workflowDefinition, onChange } = props; - const classes = useClasses(); - - const checkValid = isValidWorkflowDefinitionData(workflowDefinition); - - return ( -
- {checkValid.errors.length > 0 && - checkValid.errors.map((error, index) => ( - - - {error} - - - ))} - - Label}> - { - onChange({ - ...workflowDefinition, - label: data.value, - }); - }} - aria-autocomplete="none" - /> - - - } - > -