From 597b7224ce596e6e5080af743e1d63d6165a372c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 21 Jul 2026 15:08:06 +0000 Subject: [PATCH 001/112] Add Automations product prototype with React Flow editor Standalone Automations prototype at /prototypes/automations with product nav (Home, Templates, Favorites, Recents), tag/status/scope-filtered home list, 6-template gallery, node canvas editor with inspector and AI agent panel, test run, and routed run/change history. Extends GlobalHeader with an Automations product brand. Co-authored-by: Matthew Birtch --- package-lock.json | 242 +++++- package.json | 1 + .../components/GlobalHeader/GlobalHeader.tsx | 8 +- src/manifests/prototypes.ts | 18 +- .../prototypes/automations/Automations.tsx | 79 ++ .../automations/AutomationsShell.module.scss | 43 + .../automations/AutomationsShell.tsx | 43 + .../components/ProductNav.module.scss | 85 ++ .../automations/components/ProductNav.tsx | 104 +++ .../components/editor/AgentPanel.tsx | 116 +++ .../components/editor/InspectorPanel.tsx | 192 +++++ .../components/editor/StepsPalette.tsx | 118 +++ .../components/editor/WorkflowEditor.tsx | 414 +++++++++ .../components/editor/WorkflowNode.tsx | 28 + .../components/editor/editor.module.scss | 363 ++++++++ .../components/history/ChangeHistoryPage.tsx | 88 ++ .../components/history/RunDetailPage.tsx | 99 +++ .../components/history/RunsPage.tsx | 115 +++ .../components/history/history.module.scss | 123 +++ .../components/home/HomePage.module.scss | 167 ++++ .../automations/components/home/HomePage.tsx | 388 +++++++++ .../templates/TemplatesPage.module.scss | 83 ++ .../components/templates/TemplatesPage.tsx | 46 + .../context/AutomationsContext.tsx | 277 ++++++ .../prototypes/automations/data/actions.ts | 106 +++ .../automations/data/aiCreateScript.ts | 62 ++ .../automations/data/automationsData.ts | 789 ++++++++++++++++++ .../prototypes/automations/data/storage.ts | 40 + .../prototypes/automations/data/triggers.ts | 39 + .../prototypes/automations/data/types.ts | 85 ++ src/router/index.tsx | 8 +- 31 files changed, 4364 insertions(+), 5 deletions(-) create mode 100644 src/pages/prototypes/automations/Automations.tsx create mode 100644 src/pages/prototypes/automations/AutomationsShell.module.scss create mode 100644 src/pages/prototypes/automations/AutomationsShell.tsx create mode 100644 src/pages/prototypes/automations/components/ProductNav.module.scss create mode 100644 src/pages/prototypes/automations/components/ProductNav.tsx create mode 100644 src/pages/prototypes/automations/components/editor/AgentPanel.tsx create mode 100644 src/pages/prototypes/automations/components/editor/InspectorPanel.tsx create mode 100644 src/pages/prototypes/automations/components/editor/StepsPalette.tsx create mode 100644 src/pages/prototypes/automations/components/editor/WorkflowEditor.tsx create mode 100644 src/pages/prototypes/automations/components/editor/WorkflowNode.tsx create mode 100644 src/pages/prototypes/automations/components/editor/editor.module.scss create mode 100644 src/pages/prototypes/automations/components/history/ChangeHistoryPage.tsx create mode 100644 src/pages/prototypes/automations/components/history/RunDetailPage.tsx create mode 100644 src/pages/prototypes/automations/components/history/RunsPage.tsx create mode 100644 src/pages/prototypes/automations/components/history/history.module.scss create mode 100644 src/pages/prototypes/automations/components/home/HomePage.module.scss create mode 100644 src/pages/prototypes/automations/components/home/HomePage.tsx create mode 100644 src/pages/prototypes/automations/components/templates/TemplatesPage.module.scss create mode 100644 src/pages/prototypes/automations/components/templates/TemplatesPage.tsx create mode 100644 src/pages/prototypes/automations/context/AutomationsContext.tsx create mode 100644 src/pages/prototypes/automations/data/actions.ts create mode 100644 src/pages/prototypes/automations/data/aiCreateScript.ts create mode 100644 src/pages/prototypes/automations/data/automationsData.ts create mode 100644 src/pages/prototypes/automations/data/storage.ts create mode 100644 src/pages/prototypes/automations/data/triggers.ts create mode 100644 src/pages/prototypes/automations/data/types.ts diff --git a/package-lock.json b/package-lock.json index d8976211..95b33cf2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@mattermost/compass-icons": "^0.1.53", "@mattermost/compass-ui": "workspace:*", "@mdx-js/react": "^3.1.1", + "@xyflow/react": "^12.11.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^6.28.0", @@ -3264,6 +3265,55 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", @@ -3351,7 +3401,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3913,6 +3963,48 @@ "dev": true, "license": "MIT" }, + "node_modules/@xyflow/react": { + "version": "12.11.2", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.2.tgz", + "integrity": "sha512-eLAlDWJfWnQEhJwGMjlWdAXO9eYllKpliUmPQlAmOLxz6mExXuzMVDUKLMquixgkrtmMFFtug3jGKmYYld12cA==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.79", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "@types/react": ">=17", + "@types/react-dom": ">=17", + "react": ">=17", + "react-dom": ">=17" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.79", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.79.tgz", + "integrity": "sha512-czLyOh91NF0hIzbNzwi8I6GlqG23BHh2435OddfI6uiaLH3xdrdygO93gqgH1Bv9mhy8XPFQJOBn1FTq4LvEWA==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -4500,6 +4592,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, "node_modules/collapse-white-space": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", @@ -4616,6 +4714,111 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -10483,6 +10686,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -10928,6 +11140,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "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 + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index c814a53b..548390eb 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@mattermost/compass-icons": "^0.1.53", "@mattermost/compass-ui": "workspace:*", "@mdx-js/react": "^3.1.1", + "@xyflow/react": "^12.11.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^6.28.0", diff --git a/packages/compass-ui/src/components/GlobalHeader/GlobalHeader.tsx b/packages/compass-ui/src/components/GlobalHeader/GlobalHeader.tsx index 71a13950..237b0cd9 100644 --- a/packages/compass-ui/src/components/GlobalHeader/GlobalHeader.tsx +++ b/packages/compass-ui/src/components/GlobalHeader/GlobalHeader.tsx @@ -2,6 +2,7 @@ import ProductsIcon from '@mattermost/compass-icons/components/products'; import ProductChannelsIcon from '@mattermost/compass-icons/components/product-channels'; import ProductPlaybooksIcon from '@mattermost/compass-icons/components/product-playbooks'; import ProductBoardsIcon from '@mattermost/compass-icons/components/product-boards'; +import LightningBoltOutlineIcon from '@mattermost/compass-icons/components/lightning-bolt-outline'; import ArrowLeftIcon from '@mattermost/compass-icons/components/arrow-left'; import ArrowRightIcon from '@mattermost/compass-icons/components/arrow-right'; import MagnifyIcon from '@mattermost/compass-icons/components/magnify'; @@ -17,7 +18,11 @@ import UserAvatar from '@/components/UserAvatar/UserAvatar'; import type { ComponentType } from 'react'; import styles from './GlobalHeader.module.scss'; -export type GlobalHeaderProduct = 'Channels' | 'Playbooks' | 'Boards'; +export type GlobalHeaderProduct = + | 'Channels' + | 'Playbooks' + | 'Boards' + | 'Automations'; export interface GlobalHeaderProps { /** Optional CSS class name. */ @@ -41,6 +46,7 @@ const PRODUCT_ICON: Record< Channels: ProductChannelsIcon, Playbooks: ProductPlaybooksIcon, Boards: ProductBoardsIcon, + Automations: LightningBoltOutlineIcon, }; function InvertedIconButton({ diff --git a/src/manifests/prototypes.ts b/src/manifests/prototypes.ts index 989022bf..e008bd7b 100644 --- a/src/manifests/prototypes.ts +++ b/src/manifests/prototypes.ts @@ -1,4 +1,5 @@ import type { ComponentType } from 'react'; +import Automations from '@/pages/prototypes/automations/Automations'; import ExampleFlow from '@/pages/prototypes/example-flow/ExampleFlow'; import ExternalCallParticipants from '@/pages/prototypes/external-call-participants/ExternalCallParticipants'; import OutboundCalls from '@/pages/prototypes/outbound-calls/OutboundCalls'; @@ -8,9 +9,18 @@ export type PrototypeEntry = { label: string; path: string; component: ComponentType; + /** When true, also match nested paths under `path` (e.g. `/prototypes/foo/*`). */ + nested?: boolean; }; export const PROTOTYPES: PrototypeEntry[] = [ + { + id: 'automations', + label: 'Automations', + path: '/prototypes/automations', + component: Automations, + nested: true, + }, { id: 'example-flow', label: 'Example Flow', @@ -34,5 +44,11 @@ export const PROTOTYPES: PrototypeEntry[] = [ export function getPrototypeByPath(pathname: string): PrototypeEntry | undefined { const normalized = pathname.length > 1 && pathname.endsWith('/') ? pathname.slice(0, -1) : pathname; - return PROTOTYPES.find((p) => p.path === normalized); + return PROTOTYPES.find((p) => { + if (p.path === normalized) return true; + if (p.nested && (normalized === p.path || normalized.startsWith(`${p.path}/`))) { + return true; + } + return false; + }); } diff --git a/src/pages/prototypes/automations/Automations.tsx b/src/pages/prototypes/automations/Automations.tsx new file mode 100644 index 00000000..809459d1 --- /dev/null +++ b/src/pages/prototypes/automations/Automations.tsx @@ -0,0 +1,79 @@ +import { Navigate, Route, Routes } from 'react-router-dom'; +import type { ReactNode } from 'react'; +import AutomationsShell from './AutomationsShell'; +import { AutomationsProvider } from './context/AutomationsContext'; +import HomePage from './components/home/HomePage'; +import TemplatesPage from './components/templates/TemplatesPage'; +import WorkflowEditor from './components/editor/WorkflowEditor'; +import RunsPage from './components/history/RunsPage'; +import RunDetailPage from './components/history/RunDetailPage'; +import ChangeHistoryPage from './components/history/ChangeHistoryPage'; + +function BrowseLayout({ children }: { children: ReactNode }) { + return {children}; +} + +function FocusLayout({ children }: { children: ReactNode }) { + return {children}; +} + +/** + * Automations product prototype — nested under `/prototypes/automations/*`. + */ +export default function Automations() { + return ( + + + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + + + ); +} diff --git a/src/pages/prototypes/automations/AutomationsShell.module.scss b/src/pages/prototypes/automations/AutomationsShell.module.scss new file mode 100644 index 00000000..3be5011a --- /dev/null +++ b/src/pages/prototypes/automations/AutomationsShell.module.scss @@ -0,0 +1,43 @@ +.automations-shell { + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; + background: var(--center-channel-bg, #fff); + color: var(--center-channel-color, #3f4350); +} + +.automations-shell__global-header { + flex: 0 0 auto; +} + +.automations-shell__body { + display: flex; + flex: 1; + min-height: 0; + overflow: hidden; +} + +.automations-shell__nav { + flex: 0 0 240px; + border-right: 1px solid rgba(var(--center-channel-color-rgb, 63, 67, 80), 0.12); + background: rgba(var(--center-channel-color-rgb, 63, 67, 80), 0.02); + min-height: 0; +} + +.automations-shell__main { + flex: 1; + min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.automations-shell__toast { + position: fixed; + bottom: 24px; + left: 50%; + transform: translateX(-50%); + z-index: 40; +} diff --git a/src/pages/prototypes/automations/AutomationsShell.tsx b/src/pages/prototypes/automations/AutomationsShell.tsx new file mode 100644 index 00000000..7937cb24 --- /dev/null +++ b/src/pages/prototypes/automations/AutomationsShell.tsx @@ -0,0 +1,43 @@ +import { GlobalHeader, Toast } from '@mattermost/compass-ui'; +import type { ReactNode } from 'react'; +import avatar from '@/assets/avatars/Danielle Okoro.png'; +import { useAutomations } from './context/AutomationsContext'; +import ProductNav from './components/ProductNav'; +import styles from './AutomationsShell.module.scss'; + +export interface AutomationsShellProps { + children: ReactNode; + showProductNav?: boolean; +} + +export default function AutomationsShell({ + children, + showProductNav = true, +}: AutomationsShellProps) { + const { toast, dismissToast } = useAutomations(); + + return ( +
+
+ +
+
+ {showProductNav ? ( + + ) : null} +
{children}
+
+ {toast ? ( +
+ +
+ ) : null} +
+ ); +} diff --git a/src/pages/prototypes/automations/components/ProductNav.module.scss b/src/pages/prototypes/automations/components/ProductNav.module.scss new file mode 100644 index 00000000..9160669f --- /dev/null +++ b/src/pages/prototypes/automations/components/ProductNav.module.scss @@ -0,0 +1,85 @@ +.product-nav { + display: flex; + flex-direction: column; + height: 100%; + padding: 12px 8px; + gap: 8px; +} + +.product-nav__links { + display: flex; + flex-direction: column; + gap: 2px; +} + +.product-nav__link { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + border: 0; + background: transparent; + border-radius: 4px; + padding: 8px 10px; + text-align: left; + cursor: pointer; + color: inherit; + font: inherit; + font-weight: 600; + font-size: 14px; +} + +.product-nav__link:hover { + background: rgba(var(--center-channel-color-rgb, 63, 67, 80), 0.08); +} + +.product-nav__link--active { + background: rgba(var(--button-bg-rgb, 28, 88, 217), 0.12); + color: var(--button-bg, #1c58d9); +} + +.product-nav__section { + display: flex; + flex-direction: column; + gap: 4px; + min-height: 0; + margin-top: 8px; +} + +.product-nav__section-title { + margin: 0; + padding: 4px 10px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + color: rgba(var(--center-channel-color-rgb, 63, 67, 80), 0.64); +} + +.product-nav__item { + display: block; + width: 100%; + border: 0; + background: transparent; + border-radius: 4px; + padding: 6px 10px; + text-align: left; + cursor: pointer; + color: inherit; + font: inherit; + font-size: 13px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.product-nav__item:hover { + background: rgba(var(--center-channel-color-rgb, 63, 67, 80), 0.08); +} + +.product-nav__empty { + margin: 0; + padding: 4px 10px; + font-size: 12px; + color: rgba(var(--center-channel-color-rgb, 63, 67, 80), 0.56); +} diff --git a/src/pages/prototypes/automations/components/ProductNav.tsx b/src/pages/prototypes/automations/components/ProductNav.tsx new file mode 100644 index 00000000..16559a95 --- /dev/null +++ b/src/pages/prototypes/automations/components/ProductNav.tsx @@ -0,0 +1,104 @@ +import HomeVariantOutlineIcon from '@mattermost/compass-icons/components/home-variant-outline'; +import LightningBoltOutlineIcon from '@mattermost/compass-icons/components/lightning-bolt-outline'; +import { Icon, Scrollbar } from '@mattermost/compass-ui'; +import { useMemo } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useAutomations } from '../context/AutomationsContext'; +import styles from './ProductNav.module.scss'; + +const BASE = '/prototypes/automations'; + +export default function ProductNav() { + const navigate = useNavigate(); + const { pathname } = useLocation(); + const { automations, favoriteIds, recentIds } = useAutomations(); + + const favorites = useMemo( + () => + favoriteIds + .map((id) => automations.find((a) => a.id === id)) + .filter((a): a is NonNullable => Boolean(a)), + [automations, favoriteIds], + ); + + const recents = useMemo( + () => + recentIds + .map((id) => automations.find((a) => a.id === id)) + .filter((a): a is NonNullable => Boolean(a)) + .slice(0, 10), + [automations, recentIds], + ); + + const homeActive = pathname === BASE || pathname === `${BASE}/`; + const templatesActive = pathname.startsWith(`${BASE}/templates`); + + return ( + +
+ + +
+ +
+

Favorites

+ {favorites.length === 0 ? ( +

No favorites yet

+ ) : ( + favorites.map((a) => ( + + )) + )} +
+ +
+

Recents

+ {recents.length === 0 ? ( +

No recent automations

+ ) : ( + recents.map((a) => ( + + )) + )} +
+
+ ); +} diff --git a/src/pages/prototypes/automations/components/editor/AgentPanel.tsx b/src/pages/prototypes/automations/components/editor/AgentPanel.tsx new file mode 100644 index 00000000..94d2be98 --- /dev/null +++ b/src/pages/prototypes/automations/components/editor/AgentPanel.tsx @@ -0,0 +1,116 @@ +import CreationOutlineIcon from '@mattermost/compass-icons/components/creation-outline'; +import { Button, Icon, Scrollbar, TextArea } from '@mattermost/compass-ui'; +import { useState, type ChangeEvent } from 'react'; +import { + AI_SUGGESTED_PROMPTS, + buildAiProgressionScript, + graphSlice, + type AiScriptTurn, +} from '../../data/aiCreateScript'; +import type { WorkflowEdge, WorkflowNode } from '../../data/types'; +import styles from './editor.module.scss'; + +type AgentPanelProps = { + onClose: () => void; + onApplyGraph: (nodes: WorkflowNode[], edges: WorkflowEdge[]) => void; +}; + +export default function AgentPanel({ + onClose, + onApplyGraph, +}: AgentPanelProps) { + const [messages, setMessages] = useState([ + { + role: 'assistant', + text: 'Describe what you want to happen. I’ll draft a workflow from available triggers and steps.', + }, + ]); + const [draft, setDraft] = useState(''); + const [busy, setBusy] = useState(false); + + const runScript = (prompt: string) => { + if (busy) return; + setBusy(true); + const turns = buildAiProgressionScript(prompt); + setMessages((prev) => [...prev, { role: 'user', text: prompt }]); + setDraft(''); + + let i = 1; + const tick = () => { + if (i >= turns.length) { + setBusy(false); + return; + } + const turn = turns[i]; + setMessages((prev) => [...prev, turn]); + if (turn.revealThroughNodeIndex != null) { + const slice = graphSlice(turn.revealThroughNodeIndex); + onApplyGraph(slice.nodes, slice.edges); + } + i += 1; + window.setTimeout(tick, 700); + }; + window.setTimeout(tick, 500); + }; + + return ( +
+
+

+ } /> Describe a workflow +

+ +
+ +
+ {messages.map((m, idx) => ( +
+ {m.text} +
+ ))} +
+ {!busy && messages.length < 3 ? ( +
+ {AI_SUGGESTED_PROMPTS.map((p) => ( + + ))} +
+ ) : null} +
+
+