From a980e10a757f94771d9e6cb022e582251b43bfbd Mon Sep 17 00:00:00 2001 From: Kai Rohwer Date: Wed, 19 Feb 2025 23:01:20 +0100 Subject: [PATCH 1/4] Add BPMN Chatbot feature and integrate into modeler toolbar --- FeatureFlags.js | 3 + .../processes/[processId]/modeler-toolbar.tsx | 15 + .../components/bpmn-chatbot.tsx | 294 ++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 src/management-system-v2/components/bpmn-chatbot.tsx diff --git a/FeatureFlags.js b/FeatureFlags.js index 19aafd41c..539ae268e 100644 --- a/FeatureFlags.js +++ b/FeatureFlags.js @@ -45,6 +45,9 @@ module.exports = { // Whether the Chatbot UserInterface and its functionality should be enabled enableChatbot: false, + // AI Chatbot in the BPMN modeler view. + enableBPMNChatbot: true, + //feature to switch to prisma from fs enableUseDB: true, //feature to use GCP_bucket / fs depending on deployment env to store blobs diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/modeler-toolbar.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/modeler-toolbar.tsx index eb7a09cfa..d0836f20c 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/modeler-toolbar.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/modeler-toolbar.tsx @@ -12,6 +12,7 @@ import Icon, { ArrowUpOutlined, FilePdfOutlined, FormOutlined, + RobotOutlined, } from '@ant-design/icons'; import { SvgXML } from '@/components/svg'; import PropertiesPanel from './properties-panel'; @@ -33,6 +34,8 @@ import UserTaskBuilder from './_user-task-builder'; import ScriptEditor from '@/app/(dashboard)/[environmentId]/processes/[processId]/script-editor'; import { EnvVarsContext } from '@/components/env-vars-context'; import { wrapServerCall } from '@/lib/wrap-server-call'; +import { enableBPMNChatbot } from 'FeatureFlags'; +import ChatbotDialog from '@/components/bpmn-chatbot'; const LATEST_VERSION = { id: '-1', name: 'Latest Version', description: '' }; @@ -212,6 +215,7 @@ const ModelerToolbar = ({ versions.find((version) => version.id === (selectedVersionId ?? '-1')) ?? LATEST_VERSION; const showMobileView = useMobileModeler(); + const [showChatbotDialog, setShowChatbotDialog] = useState(false); return ( <> @@ -335,6 +339,14 @@ const ModelerToolbar = ({ )} + {enableBPMNChatbot && ( + + + + )} {showPropertiesPanel && selectedElement && ( @@ -344,6 +356,9 @@ const ModelerToolbar = ({ selectedElement={selectedElement} /> )} + {enableBPMNChatbot && ( + + )} diff --git a/src/management-system-v2/components/bpmn-chatbot.tsx b/src/management-system-v2/components/bpmn-chatbot.tsx new file mode 100644 index 000000000..e0c7f8389 --- /dev/null +++ b/src/management-system-v2/components/bpmn-chatbot.tsx @@ -0,0 +1,294 @@ +import { BPMNCanvasRef } from '@/components/bpmn-canvas'; +import { Button, Card, Form, Input, List, Space, Tag, Tooltip } from 'antd'; +import React, { useRef, useState } from 'react'; +import { getNewShapePosition } from 'bpmn-js/lib/features/auto-place/BpmnAutoPlaceUtil'; +import { Element, Shape } from 'bpmn-js/lib/model/Types'; +import { FileOutlined, MessageOutlined, SendOutlined } from '@ant-design/icons'; +import { FaIcons } from 'react-icons/fa6'; +import { set } from 'zod'; + +type ChatbotDialogProps = { + show: boolean; + modeler: BPMNCanvasRef | null; +}; + +type FieldType = { + prompt: string; +}; + +const ChatbotDialog: React.FC = ({ show, modeler }) => { + const [lastPrompts, setLastPrompts] = useState< + { message: string; isUser: boolean; bpmn?: string }[] + >([]); + const [waitForResponse, setWaitForResponse] = useState(false); + const [prompt, setPrompt] = useState(''); + const root = modeler!.getCurrentRoot(); + const modeling = modeler!.getModeling(); + //const elementFactory = modeler!.getElementFactory(); + + function onPrompt({ prompt }: FieldType) { + setWaitForResponse(true); + getProcessXml().then((process) => { + /*sendToAPI(prompt, process) + .then((res) => { + if (res) { + processResponse(res); + } + setLastPrompts( + lastPrompts.concat({ + userPrompt: prompt, + bpmnProcess: process, + chatbotResponse: res, + }), + ); + }) + .finally(() => setWaitForResponse(false));*/ + }); + } + + //see tools definitions + function append_shape( + bpmn_type: string, + new_element_name: string, + source_element_id_or_name: string, + created: { name: string; shape: Shape }[], + label: string, + ) { + let source = modeler?.getElement(source_element_id_or_name) as Shape; + if (!source) { + console.log(created); + source = created.find((e) => e.name == source_element_id_or_name)!.shape; + } + /*let shape = elementFactory.createShape({ type: 'bpmn:' + bpmn_type }); + const position = getNewShapePosition(source, shape); + shape = modeling.createShape({ type: 'bpmn:' + bpmn_type }, position, root!); + const connection = modeling.createConnection( + source, + shape, + { type: 'bpmn:SequenceFlow' }, + root!, + ); + modeling.updateLabel(connection, label); + modeling.updateLabel(shape, new_element_name); + return shape;*/ + } + function create_connection( + source_element_id_or_name: string, + target_element_id_or_name: string, + created: { name: string; shape: Shape }[], + label?: string, + ): void { + let source = modeler?.getElement(source_element_id_or_name) as Shape; + if (!source) { + source = created.find((e) => e.name == source_element_id_or_name)!.shape; + } + let target = modeler?.getElement(target_element_id_or_name) as Shape; + if (!target) { + target = created.find((e) => e.name == target_element_id_or_name)!.shape; + } + const connection = modeling.createConnection( + source, + target, + { type: 'bpmn:SequenceFlow' }, + root!, + ); + if (label) { + modeling.updateLabel(connection, label); + } + } + function remove_elements(element_ids: string[]): void { + const elements: Element[] = []; + element_ids.forEach((e) => { + const element = modeler?.getElement(e); + if (element) { + elements.push(element); + } + }); + + modeling.removeElements(elements); + } + + //parsing tool uses listed in response + function processResponse(response: any[]) { + const created: { name: string; shape: Shape }[] = []; + response.forEach((res) => { + if (res.name == 'create_connection') { + create_connection( + res.args.source_element_id_or_name, + res.args.target_element_id_or_name, + created, + res.args.label, + ); + } else if (res.name == 'remove_elements') { + remove_elements(res.args.element_ids); + } else if (res.name == 'append_element') { + const shape = append_shape( + res.args.bpmn_type, + res.args.name, + res.args.source_element_id_or_name, + created, + res.args.label, + ); + created.push({ name: res.args.name, shape: shape as any }); + } + }); + } + + const onSubmit = async () => { + setPrompt(''); + setLastPrompts((lastPrompts) => lastPrompts.concat({ message: prompt, isUser: true })); + scrollToBottom(); + await new Promise((resolve) => setTimeout(resolve, 2000)); + if (lastPrompts.filter((p) => !p.isUser).length == 0) { + setLastPrompts((lastPrompts) => + lastPrompts.concat({ + message: "Here's a simple BPMN representation for a vacation application process:", + isUser: false, + bpmn: "", + }), + ); + } else if (lastPrompts.filter((p) => !p.isUser).length == 1) { + setLastPrompts((lastPrompts) => + lastPrompts.concat({ + message: + "Sure, here's an updated BPMN process that includes a form reference for the vacation request task:", + isUser: false, + bpmn: "", + }), + ); + } else if (lastPrompts.filter((p) => !p.isUser).length == 2) { + setLastPrompts((lastPrompts) => + lastPrompts.concat({ + message: ( +
+

Here's the updated BPMN XML with element colors.

+

Changes and Features Added:

+
    +
  1. + Form Attachment: +
      +
    • + The VacationApplicationTask has a form with fields like employeeName, + vacationStart, vacationEnd, and reason. +
    • +
    +
  2. +
  3. + Color Customization +
      +
    • Green (#5cb85c) for Start Event and Approved End Event.
    • +
    • Blue (#337ab7) for the Vacation Request Task.
    • +
    • Orange (#f0ad4e) for Manager Approval.
    • +
    • Red (#d9534f) for Rejection Gateway and Notification.
    • +
    • Light Blue (#5bc0de) for HR Processing.
    • +
    +
  4. +
+
+ ), + isUser: false, + bpmn: "", + }), + ); + } else { + setLastPrompts((lastPrompts) => + lastPrompts.concat({ + message: 'I am a simple chatbot and cannot do much more than this.', + isUser: false, + }), + ); + } + scrollToBottom(); + }; + + //get current xml of the ... part only + function getProcessXml(): Promise { + return modeler!.getXML().then((res) => { + if (res) { + const startIndex = res.indexOf('', startIndex); + if (endIndex == -1) { + return ''; + } + return res.slice(startIndex, endIndex) + ''; + } else { + return ''; + } + }); + } + + const listRef = useRef(null); + + const scrollToBottom = () => { + if (listRef.current) { + setTimeout(() => { + listRef.current!.scrollTop = listRef.current!.scrollHeight; + }, 100); + } + }; + + return ( + <> + + + ); +}; +export default ChatbotDialog; From dc18fbd17396c94290905cd128fd2a9af9a06bd0 Mon Sep 17 00:00:00 2001 From: Kai Rohwer Date: Wed, 19 Feb 2025 23:11:04 +0100 Subject: [PATCH 2/4] Update ChatbotDialog to use ReactNode for message type --- src/management-system-v2/components/bpmn-chatbot.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/management-system-v2/components/bpmn-chatbot.tsx b/src/management-system-v2/components/bpmn-chatbot.tsx index e0c7f8389..4d8acaa43 100644 --- a/src/management-system-v2/components/bpmn-chatbot.tsx +++ b/src/management-system-v2/components/bpmn-chatbot.tsx @@ -1,6 +1,6 @@ import { BPMNCanvasRef } from '@/components/bpmn-canvas'; import { Button, Card, Form, Input, List, Space, Tag, Tooltip } from 'antd'; -import React, { useRef, useState } from 'react'; +import React, { ReactNode, useRef, useState } from 'react'; import { getNewShapePosition } from 'bpmn-js/lib/features/auto-place/BpmnAutoPlaceUtil'; import { Element, Shape } from 'bpmn-js/lib/model/Types'; import { FileOutlined, MessageOutlined, SendOutlined } from '@ant-design/icons'; @@ -18,7 +18,7 @@ type FieldType = { const ChatbotDialog: React.FC = ({ show, modeler }) => { const [lastPrompts, setLastPrompts] = useState< - { message: string; isUser: boolean; bpmn?: string }[] + { message: ReactNode; isUser: boolean; bpmn?: string }[] >([]); const [waitForResponse, setWaitForResponse] = useState(false); const [prompt, setPrompt] = useState(''); From 926dae575c0285dbe0c6b56a72cf7e5b6834eb5c Mon Sep 17 00:00:00 2001 From: Kai Rohwer Date: Wed, 19 Feb 2025 23:22:13 +0100 Subject: [PATCH 3/4] Change height --- src/management-system-v2/components/bpmn-chatbot.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/management-system-v2/components/bpmn-chatbot.tsx b/src/management-system-v2/components/bpmn-chatbot.tsx index 4d8acaa43..be997f9a8 100644 --- a/src/management-system-v2/components/bpmn-chatbot.tsx +++ b/src/management-system-v2/components/bpmn-chatbot.tsx @@ -229,13 +229,8 @@ const ChatbotDialog: React.FC = ({ show, modeler }) => { return ( <> -