diff --git a/.eslintrc.json b/.eslintrc.json index 6d62c78893..c908e6055f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,6 +6,7 @@ "node": true }, "parserOptions": { + "ecmaVersion": "latest", "project": [ "apps/*/tsconfig.eslint.json", "libs/*/tsconfig.eslint.json", @@ -21,6 +22,9 @@ "ignorePatterns": [ "apps/*/build", "libs/*/build", - "tools/*/build" + "tools/*/build", + "apps/*/dist", + "libs/*/dist", + "tools/*/dist" ] } diff --git a/.gitignore b/.gitignore index 795c698c66..d12bece289 100644 --- a/.gitignore +++ b/.gitignore @@ -38,8 +38,3 @@ package-lock.json !.yarn/releases !.yarn/sdks !.yarn/versions - -# Ignored apps -apps/* -!apps/assisted-ui -!apps/assisted-disconnected-ui diff --git a/apps/assisted-chatbot/.eslintrc.cjs b/apps/assisted-chatbot/.eslintrc.cjs new file mode 100644 index 0000000000..96e1ba897f --- /dev/null +++ b/apps/assisted-chatbot/.eslintrc.cjs @@ -0,0 +1,39 @@ +/** @type {import('eslint').ESLint.ConfigData} */ +module.exports = { + overrides: [ + { + files: ['./vite.config.ts'], + extends: ['@openshift-assisted/eslint-config'], + env: { + browser: false, + }, + parserOptions: { + tsconfigRootDir: __dirname, + }, + rules: { + 'no-console': 'off', + }, + }, + { + files: ['./src/**/*.{ts,tsx}'], + extends: ['@openshift-assisted/eslint-config', 'plugin:react/jsx-runtime'], + parserOptions: { + tsconfigRootDir: __dirname, + }, + rules: { + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'react-i18next', + importNames: ['useTranslation'], + message: 'Import `useTranslation` from `@openshift-assisted/ui-lib/common` instead', + }, + ], + }, + ], + }, + }, + ], +}; diff --git a/apps/assisted-chatbot/README.md b/apps/assisted-chatbot/README.md new file mode 100644 index 0000000000..19f40a01b2 --- /dev/null +++ b/apps/assisted-chatbot/README.md @@ -0,0 +1,19 @@ +# Assisted Installer Chatbot UI + +This is a federated module for +[Astro Virtual Assistant UI](https://github.com/RedHatInsights/astro-virtual-assistant-frontend) + +## Run the project + +1. Run the Astro + 1. For now you need to use my fork which can load this new assisted chatbot module - clone the + repo + [Astro fork](https://github.com/rawagner/astro-virtual-assistant-frontend/tree/chatbot_app) + 2. run `npm install` + 3. start the Astro `npm run start` +2. Run the Assisted Chatbot + + 1. run `yarn start:assisted_chatbot:static` + +3. Open [c.rh.c](https://prod.foo.redhat.com:1337/). The chatbot will appear on the bottom left + corner. You should be able to choose Assisted Installer chatbot diff --git a/apps/assisted-chatbot/deploy/frontend.yaml b/apps/assisted-chatbot/deploy/frontend.yaml new file mode 100644 index 0000000000..26ec5fb965 --- /dev/null +++ b/apps/assisted-chatbot/deploy/frontend.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: v1 +kind: Template +metadata: + name: assisted-installer-ui-chatbot +objects: + - apiVersion: cloud.redhat.com/v1alpha1 + kind: Frontend + metadata: + name: assisted-installer-ui-chatbot + spec: + feoConfigEnabled: true + envName: ${ENV_NAME} + title: Assisted Installer UI Chatbot + deploymentRepo: https://github.com/openshift-assisted/assisted-installer-ui + frontend: + paths: + - /apps/assisted-installer-ui-chatbot + API: + versions: + - v1 + image: ${IMAGE}:${IMAGE_TAG} + module: + manifestLocation: '/apps/assisted-installer-ui-chatbot/fed-mods.json' + moduleConfig: + ssoScopes: + - rhfull +parameters: + - name: ENV_NAME + required: true + - name: IMAGE_TAG + required: true + - name: IMAGE + value: quay.io/app-sre/assisted-installer-ui-chatbot diff --git a/apps/assisted-chatbot/fec.config.js b/apps/assisted-chatbot/fec.config.js new file mode 100644 index 0000000000..b9cc13eda1 --- /dev/null +++ b/apps/assisted-chatbot/fec.config.js @@ -0,0 +1,29 @@ +const path = require('path'); +const { insights } = require('./package.json'); +const moduleName = insights.appname.replace(/-(\w)/g, (_, match) => match.toUpperCase()); + +module.exports = { + appUrl: '/openshift/assisted-installer-ui-chatbot', + appEntry: path.resolve(__dirname, './src/AppEntry.tsx'), + debug: true, + useProxy: true, + proxyVerbose: true, + stripAllPfStyles: true, + sassPrefix: `.${moduleName}`, + interceptChromeConfig: false, + plugins: [], + hotReload: process.env.HOT === 'true', + nodeModulesDirectories: '../../node_modules', + moduleFederation: { + exposes: { + './ChatbotMessageEntry': path.resolve( + __dirname, + './src/components/ChatbotMessageEntry/ChatbotMessageEntry.tsx', + ), + './useAsyncChatbot': path.resolve(__dirname, './src/hooks/useAsyncChatbot.tsx'), + }, + }, + routes: { + '/apps/assisted-installer-ui-chatbot': { host: 'http://localhost:8003' }, + }, +}; diff --git a/apps/assisted-chatbot/package.json b/apps/assisted-chatbot/package.json new file mode 100644 index 0000000000..0ad1d5aaec --- /dev/null +++ b/apps/assisted-chatbot/package.json @@ -0,0 +1,55 @@ +{ + "name": "@openshift-assisted/assisted-installer-ui-chatbot", + "version": "1.0.0", + "main": "index.js", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + }, + "scripts": { + "build": "fec build", + "check_types": "yarn run -T tsc --noEmit", + "clean": "yarn run -T rimraf node_modules dist .cache", + "patch:hosts": "fec patch-etc-hosts", + "format": "yarn run -T prettier --cache --check . \"!dist\"", + "fix-code-style": "yarn lint --fix && yarn format --write", + "lint": "yarn run -T eslint --cache --cache-location node_modules/.cache/eslint/.eslint-cache .", + "start": "HOT=true fec dev --clouddotEnv=prod --uiEnv=stable", + "start:static": "fec static --port=7003 --config ../../node_modules/@redhat-cloud-services/frontend-components-config/bin/prod.webpack.config.js", + "static": "fec static", + "postinstall": "ts-patch install" + }, + "dependencies": { + "@openshift-assisted/chatbot": "workspace:*", + "@patternfly/chatbot": "6.4.1", + "@patternfly/patternfly": "6.4.0", + "@patternfly/react-core": "6.4.0", + "@patternfly/react-icons": "6.4.0", + "@patternfly/react-styles": "6.4.0", + "@patternfly/react-tokens": "6.4.0", + "@redhat-cloud-services/ai-client-common": "^0.13.0", + "@redhat-cloud-services/ai-client-state": "^0.15.0", + "@redhat-cloud-services/frontend-components": "^7.0.0", + "axios": ">=0.22.0 <2.0.0", + "i18next": "^20.4.0", + "parse-url": "^9.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^11.11.4", + "react-router-dom-v5-compat": "^6.21.2" + }, + "devDependencies": { + "@redhat-cloud-services/eslint-config-redhat-cloud-services": "^2.0.3", + "@redhat-cloud-services/frontend-components-config": "^6.0.17", + "@redhat-cloud-services/tsc-transform-imports": "^1.0.4", + "@tsconfig/vite-react": "^1.0.1", + "@types/react": "18.2.37", + "@types/react-dom": "^18.2.0", + "rimraf": "^5.0.5", + "ts-patch": "^3.0.2", + "typescript": "^5.2.2" + }, + "insights": { + "appname": "assisted-installer-ui-chatbot" + } +} diff --git a/apps/assisted-chatbot/src/AppEntry.tsx b/apps/assisted-chatbot/src/AppEntry.tsx new file mode 100644 index 0000000000..36b8364c6e --- /dev/null +++ b/apps/assisted-chatbot/src/AppEntry.tsx @@ -0,0 +1,9 @@ +// Minimal entry point for fec dev server +// This module only exposes federated components, no standalone app +import React from 'react'; + +const AppEntry: React.FC = () => { + return null; +}; + +export default AppEntry; diff --git a/apps/assisted-chatbot/src/assets/Ask_Red_Hat_OFFICIAL-whitebackground.svg b/apps/assisted-chatbot/src/assets/Ask_Red_Hat_OFFICIAL-whitebackground.svg new file mode 100644 index 0000000000..c7e17f1341 --- /dev/null +++ b/apps/assisted-chatbot/src/assets/Ask_Red_Hat_OFFICIAL-whitebackground.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/assisted-chatbot/src/components/AsyncMessagePlaceholder/AsyncMessagePlaceholder.css b/apps/assisted-chatbot/src/components/AsyncMessagePlaceholder/AsyncMessagePlaceholder.css new file mode 100644 index 0000000000..d5301f90be --- /dev/null +++ b/apps/assisted-chatbot/src/components/AsyncMessagePlaceholder/AsyncMessagePlaceholder.css @@ -0,0 +1,3 @@ +.ai-async-message-skeleton--slow { + --pf-v6-c-skeleton--after--AnimationDuration: 3.6s; +} diff --git a/apps/assisted-chatbot/src/components/AsyncMessagePlaceholder/AsyncMessagePlaceholder.tsx b/apps/assisted-chatbot/src/components/AsyncMessagePlaceholder/AsyncMessagePlaceholder.tsx new file mode 100644 index 0000000000..ae48875f53 --- /dev/null +++ b/apps/assisted-chatbot/src/components/AsyncMessagePlaceholder/AsyncMessagePlaceholder.tsx @@ -0,0 +1,35 @@ +import { Skeleton } from '@patternfly/react-core'; +import './AsyncMessagePlaceholder.css'; + +export const AsyncMessagePlaceholder = () => ( +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+); diff --git a/apps/assisted-chatbot/src/components/ChatbotMessageEntry/ChatbotMessageEntry.tsx b/apps/assisted-chatbot/src/components/ChatbotMessageEntry/ChatbotMessageEntry.tsx new file mode 100644 index 0000000000..5e025270ba --- /dev/null +++ b/apps/assisted-chatbot/src/components/ChatbotMessageEntry/ChatbotMessageEntry.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; +import { MessageEntry, MessageEntryProps } from '@openshift-assisted/chatbot'; +import { getBaseUrl } from '../../config'; + +const ChatbotMessageEntry = ( + props: Omit, +) => { + const { chromeHistory, ...chrome } = useChrome(); + + const onApiCall = React.useCallback(async (input, init) => { + const userToken = await chrome.auth.getToken(); + const api = new URL(getBaseUrl()); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + return fetch(`https://assisted-chat.${api.hostname}${input}`, { + ...(init || {}), + headers: { + ...(init?.headers || {}), + ...(userToken ? { Authorization: `Bearer ${userToken}` } : {}), + }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const openClusterDetails = React.useCallback( + (id) => { + chromeHistory.push(`/openshift/assisted-installer/clusters/${id}`); + }, + [chromeHistory], + ); + + return ; +}; + +export default ChatbotMessageEntry; diff --git a/apps/assisted-chatbot/src/config.ts b/apps/assisted-chatbot/src/config.ts new file mode 100644 index 0000000000..b419f4897b --- /dev/null +++ b/apps/assisted-chatbot/src/config.ts @@ -0,0 +1,38 @@ +const envs: { [key: string]: string } = { + integration: 'https://api.integration.openshift.com', + staging: 'https://api.stage.openshift.com', + production: 'https://api.openshift.com', +}; + +const ENV_OVERRIDE_LOCALSTORAGE_KEY = 'ocmOverridenEnvironment'; + +const parseEnvQueryParam = (): string | undefined => { + const queryParams = new URLSearchParams(window.location.search); + const envVal = queryParams.get('env'); + return envVal && envs[envVal] ? envVal : undefined; +}; + +export const getBaseUrl = (): string => { + let envOverrideStorageItem: string | null = null; + try { + envOverrideStorageItem = localStorage.getItem(ENV_OVERRIDE_LOCALSTORAGE_KEY); + } catch (e) { + // eslint-disable-next-line no-console + console.warn('failed to get override item', e); + } + const queryEnv = parseEnvQueryParam() || envOverrideStorageItem; + if (queryEnv && envs[queryEnv]) { + try { + localStorage.setItem(ENV_OVERRIDE_LOCALSTORAGE_KEY, queryEnv); + } catch (e) { + // eslint-disable-next-line no-console + console.warn('failed to store override item', e); + } + return envs[queryEnv]; + } + const defaultEnv = + window.location.host.includes('dev') || window.location.host.includes('foo') + ? 'staging' + : 'production'; + return envs[defaultEnv]; +}; diff --git a/apps/assisted-chatbot/src/hooks/useAsyncChatbot.tsx b/apps/assisted-chatbot/src/hooks/useAsyncChatbot.tsx new file mode 100644 index 0000000000..30344aea92 --- /dev/null +++ b/apps/assisted-chatbot/src/hooks/useAsyncChatbot.tsx @@ -0,0 +1,171 @@ +import React from 'react'; +import { createClientStateManager } from '@redhat-cloud-services/ai-client-state'; +import { Message as MessageType } from '@redhat-cloud-services/ai-client-state'; +import { + LightSpeedCoreAdditionalProperties, + LightspeedClient, +} from '@redhat-cloud-services/lightspeed-client'; +import { ScalprumComponent, ScalprumComponentProps } from '@scalprum/react-core'; + +import { Models, StateManagerConfiguration, UseManagerHook } from '../types'; +import ARH_BOT_ICON from '../assets/Ask_Red_Hat_OFFICIAL-whitebackground.svg'; +import { AsyncMessagePlaceholder } from '../components/AsyncMessagePlaceholder/AsyncMessagePlaceholder'; +import { Message } from '@patternfly/chatbot'; +import { getBaseUrl } from '../config'; +import useChrome from '@redhat-cloud-services/frontend-components/useChrome'; + +type LightspeedMessage = ScalprumComponentProps< + Record, + { + message: MessageType; + avatar: string; + conversationId: string | undefined; + } +>; + +const AsyncMessageError = () => ( + +); + +const LSCMessageEntry = ({ + message, + avatar, + conversationId, +}: { + message: MessageType; + avatar: string; + conversationId: string; +}) => { + const messageProps: LightspeedMessage = { + message, + avatar: message.role === 'user' ? avatar : ARH_BOT_ICON, + scope: 'assistedInstallerApp', + module: './ChatbotMessageEntry', + fallback: null, + conversationId, + }; + return ( + } + fallback={} + /> + ); +}; + +const useIsAuthenticated = () => { + const chrome = useChrome(); + const [isLoading, setIsLoading] = React.useState(true); + const [isAuthenticated, setIsAuthenticated] = React.useState(false); + + React.useEffect(() => { + void (async () => { + const api = new URL(getBaseUrl()); + const token = await chrome.auth.getToken(); + try { + const response = await fetch(`https://assisted-chat.${api.hostname}/v1/conversations`, { + method: 'GET', + headers: token + ? { + Authorization: `Bearer ${token}`, + } + : {}, + }); + setIsAuthenticated(response.ok); + } catch (error) { + // eslint-disable-next-line no-console + console.error('Error checking authentication:', error); + } finally { + setIsLoading(false); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return { + isAuthenticated, + isLoading, + }; +}; + +const useStateManager = (): UseManagerHook => { + const chrome = useChrome(); + const { isAuthenticated, isLoading } = useIsAuthenticated(); + const manager = React.useMemo(() => { + const api = new URL(getBaseUrl()); + const client = new LightspeedClient({ + baseUrl: `https://assisted-chat.${api.hostname}`, + fetchFunction: async (input, init) => { + const token = await chrome.auth.getToken(); + return fetch(input, { + ...init, + headers: { + ...init?.headers, + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }); + }, + }); + const stateManager = createClientStateManager(client); + const config: StateManagerConfiguration = { + model: Models.OAI, + stateManager, + historyManagement: true, + streamMessages: true, + modelName: 'OpenShift Assisted Installer', + docsUrl: + 'https://docs.redhat.com/en/documentation/assisted_installer_for_openshift_container_platform/2025/html/installing_openshift_container_platform_with_the_assisted_installer/index', + selectionTitle: 'OpenShift Assisted Installer', + selectionDescription: + 'Create, configure, and install OpenShift Container Platform clusters using the Assisted Installer.', + MessageEntryComponent: LSCMessageEntry, + // eslint-disable-next-line @typescript-eslint/no-misused-promises + handleNewChat: async (toggleDrawer) => { + // can't use hooks here, we are not yet within the correct React context + await stateManager.createNewConversation(); + toggleDrawer(false); + }, + isPreview: true, + welcome: { + buttons: [ + { + title: 'Create a new OpenShift cluster', + value: 'Create a new OpenShift cluster', + }, + { + title: 'List my OpenShift clusters', + value: 'List my OpenShift clusters', + }, + { + title: 'List available OpenShift versions', + value: 'List available OpenShift versions', + }, + ], + }, + routes: ['/openshift/assisted-installer/*'], + }; + + return config; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + if (isLoading) { + return { manager: null, loading: true }; + } + if (!isAuthenticated) { + return { manager: null, loading: false }; + } + + return { manager, loading: false }; +}; + +export default useStateManager; diff --git a/apps/assisted-chatbot/src/types.ts b/apps/assisted-chatbot/src/types.ts new file mode 100644 index 0000000000..051ebd36b5 --- /dev/null +++ b/apps/assisted-chatbot/src/types.ts @@ -0,0 +1,49 @@ +import { StateManager } from '@redhat-cloud-services/ai-client-state'; +import { IAIClient } from '@redhat-cloud-services/ai-client-common'; + +export enum Models { + ASK_RED_HAT = 'Ask Red Hat', + RHEL_LIGHTSPEED = 'RHEL LightSpeed', + VA = 'Virtual Assistant', + OAI = 'OpenShift assisted Installer', +} + +export interface WelcomeButton { + /** Title for the welcome button */ + title: string; + /** Optional message to display below the title */ + message?: string; + /** Message to send when the button is clicked */ + value: string; +} + +export interface WelcomeConfig { + /** Welcome message content to display */ + content?: string; + /** Optional array of interactive buttons */ + buttons?: WelcomeButton[]; +} + +export type StateManagerConfiguration = { + model: Models; + historyManagement: boolean; + streamMessages: boolean; + modelName: string; + docsUrl: string; + selectionTitle: string; + selectionDescription: string; + stateManager: StateManager, S>; + isPreview?: boolean; + handleNewChat?: (toggleDrawer: (isOpen: boolean) => void) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + MessageEntryComponent?: React.ComponentType; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + FooterComponent?: React.ComponentType; + welcome?: WelcomeConfig; + routes?: string[]; +}; + +export type UseManagerHook = { + manager: StateManagerConfiguration | null; + loading: boolean; +}; diff --git a/apps/assisted-chatbot/src/typings.d.ts b/apps/assisted-chatbot/src/typings.d.ts new file mode 100644 index 0000000000..cdb2b1a9a2 --- /dev/null +++ b/apps/assisted-chatbot/src/typings.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const content: string; + export default content; +} diff --git a/apps/assisted-chatbot/tsconfig.json b/apps/assisted-chatbot/tsconfig.json new file mode 100644 index 0000000000..12be2ceae2 --- /dev/null +++ b/apps/assisted-chatbot/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "sourceMap": true, + "moduleResolution": "node", + "target": "es5", + "allowSyntheticDefaultImports": true, + "noErrorTruncation": true, + "strict": true, + "rootDir": ".", + "skipLibCheck": true, + "lib": ["dom", "esnext"], + "module": "esnext", + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true + }, + "include": ["src"] +} diff --git a/apps/assisted-disconnected-ui/package.json b/apps/assisted-disconnected-ui/package.json index c1dcf1ab82..54e2b21d57 100644 --- a/apps/assisted-disconnected-ui/package.json +++ b/apps/assisted-disconnected-ui/package.json @@ -1,14 +1,14 @@ { "dependencies": { "@openshift-assisted/ui-lib": "workspace:*", - "@openshift-console/dynamic-plugin-sdk": "0.0.3", - "@patternfly/patternfly": "6.2.3", - "@patternfly/react-code-editor": "6.2.2", - "@patternfly/react-core": "6.2.2", - "@patternfly/react-icons": "6.2.2", - "@patternfly/react-styles": "6.2.2", - "@patternfly/react-table": "6.2.2", - "@patternfly/react-tokens": "6.2.2", + "@openshift-console/dynamic-plugin-sdk": "1.0.0", + "@patternfly/patternfly": "6.4.0", + "@patternfly/react-code-editor": "6.4.0", + "@patternfly/react-core": "6.4.0", + "@patternfly/react-icons": "6.4.0", + "@patternfly/react-styles": "6.4.0", + "@patternfly/react-table": "6.4.0", + "@patternfly/react-tokens": "6.4.0", "@reduxjs/toolkit": "^1.9.1", "@sentry/browser": "^7.119", "axios": ">=0.22.0 <2.0.0", @@ -21,7 +21,7 @@ "react-i18next": "^11.11.4", "react-monaco-editor": "^0.55.0", "react-redux": "^8.0.5", - "react-router-dom": "^5.3.3", + "react-router-dom": "5.3.x", "react-router-dom-v5-compat": "^6.21.2", "react-tagsinput": "^3.20", "redux": "^4", @@ -31,18 +31,14 @@ "description": "A stand-alone web UI for the disconnected environments", "devDependencies": { "@tsconfig/vite-react": "^1.0.1", - "@types/react": "17.0.x", + "@types/react": "18.2.37", + "@types/react-dom": "^18.2.0", "@vitejs/plugin-react-swc": "^3.0.1", "concurrently": "^8.2.2", "nodemon": "^3.0.3", "vite": "^5.4.21", "vite-plugin-environment": "^1.1.3" }, - "overrides": { - "@patternfly/react-core": { - "attr-accept": "2.2.2" - } - }, "engines": { "node": ">=14" }, diff --git a/apps/assisted-ui/README.md b/apps/assisted-ui/README.md index 96c34900f8..1e3c02777d 100644 --- a/apps/assisted-ui/README.md +++ b/apps/assisted-ui/README.md @@ -91,14 +91,6 @@ You can build the container image by running: $ podman build -t quay.io/edge-infrastructure/assisted-installer-ui:latest . --build-arg AIUI_APP_GIT_SHA="$(git rev-parse HEAD)" --build-arg AIUI_APP_VERSION=latest ``` -## ChatBot - -You can run the standalone UI with chatbot enabled. You need to be logged in via `ocm`. - -``` -$ AIUI_CHAT_API_URL= OCM_REFRESH_TOKEN=$(ocm token --refresh) AIUI_SSO_API_URL=https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/token yarn start:assisted_ui -``` - ## Available Scripts In the project directory, you can run: diff --git a/apps/assisted-ui/deploy/nginx.conf b/apps/assisted-ui/deploy/nginx.conf index 8bb6fd1652..78bcc840ed 100644 --- a/apps/assisted-ui/deploy/nginx.conf +++ b/apps/assisted-ui/deploy/nginx.conf @@ -14,20 +14,6 @@ location /api { client_max_body_size 2M; } -location /chatbot/ { - proxy_pass $AIUI_CHAT_API_URL; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - proxy_connect_timeout 120; - proxy_send_timeout 120; - proxy_read_timeout 120; - send_timeout 120; - client_max_body_size 2M; -} - location /token { proxy_pass $AIUI_SSO_API_URL; proxy_http_version 1.1; diff --git a/apps/assisted-ui/package.json b/apps/assisted-ui/package.json index a00f9cc074..cc574883e4 100644 --- a/apps/assisted-ui/package.json +++ b/apps/assisted-ui/package.json @@ -1,18 +1,14 @@ { "dependencies": { - "@openshift-assisted/chatbot": "workspace:*", "@openshift-assisted/ui-lib": "workspace:*", - "@openshift-console/dynamic-plugin-sdk": "0.0.3", - "@patternfly-6/patternfly": "npm:@patternfly/patternfly@6.3.1", - "@patternfly-6/react-core": "npm:@patternfly/react-core@6.3.1", - "@patternfly/chatbot": "6.4.0-prerelease.4", - "@patternfly/patternfly": "6.2.3", - "@patternfly/react-code-editor": "6.2.2", - "@patternfly/react-core": "6.2.2", - "@patternfly/react-icons": "6.2.2", - "@patternfly/react-styles": "6.2.2", - "@patternfly/react-table": "6.2.2", - "@patternfly/react-tokens": "6.2.2", + "@openshift-console/dynamic-plugin-sdk": "1.0.0", + "@patternfly/patternfly": "6.4.0", + "@patternfly/react-code-editor": "6.4.0", + "@patternfly/react-core": "6.4.0", + "@patternfly/react-icons": "6.4.0", + "@patternfly/react-styles": "6.4.0", + "@patternfly/react-table": "6.4.0", + "@patternfly/react-tokens": "6.4.0", "@reduxjs/toolkit": "^1.9.1", "@sentry/browser": "^7.119", "axios": ">=0.22.0 <2.0.0", @@ -25,7 +21,7 @@ "react-i18next": "^11.11.4", "react-monaco-editor": "^0.55.0", "react-redux": "^8.0.5", - "react-router-dom": "^5.3.3", + "react-router-dom": "5.3.x", "react-router-dom-v5-compat": "^6.21.2", "react-tagsinput": "^3.20", "redux": "^4", @@ -35,16 +31,14 @@ "description": "A stand-alone web UI for the Assisted Installer", "devDependencies": { "@tsconfig/vite-react": "^1.0.1", - "@types/react": "17.0.x", + "@types/react": "18.2.37", + "@types/react-dom": "^18.2.0", + "@types/react-router": "^5.1.x", + "@types/react-router-dom": "5.3.x", "@vitejs/plugin-react-swc": "^3.0.1", "vite": "^5.4.21", "vite-plugin-environment": "^1.1.3" }, - "overrides": { - "@patternfly/react-core": { - "attr-accept": "2.2.2" - } - }, "engines": { "node": ">=14" }, diff --git a/apps/assisted-ui/src/components/App.tsx b/apps/assisted-ui/src/components/App.tsx index 758129b100..2fbea9eed1 100755 --- a/apps/assisted-ui/src/components/App.tsx +++ b/apps/assisted-ui/src/components/App.tsx @@ -4,7 +4,6 @@ import { CompatRouter, Route } from 'react-router-dom-v5-compat'; import { Page } from '@patternfly/react-core'; import * as OCM from '@openshift-assisted/ui-lib/ocm'; import { Header } from './Header'; -import ChatBot, { refreshToken } from './Chatbot'; import '../i18n'; const { HostsClusterDetailTabMock, UILibRoutes, Features, Config } = OCM; @@ -14,10 +13,7 @@ export const App: React.FC = () => ( } isManagedSidebar defaultManagedSidebarIsOpen={false}> - : undefined} - > + } /> diff --git a/apps/assisted-ui/src/components/Chatbot.tsx b/apps/assisted-ui/src/components/Chatbot.tsx deleted file mode 100644 index 72c9a9f80b..0000000000 --- a/apps/assisted-ui/src/components/Chatbot.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import * as React from 'react'; -import { ChatBot as AIChatBot, ChatBotWindowProps } from '@openshift-assisted/chatbot'; -import { useNavigate } from 'react-router-dom-v5-compat'; - -import '@patternfly-6/react-core/dist/styles/base.css'; -import '@patternfly/chatbot/dist/css/main.css'; -import '@patternfly-6/patternfly/patternfly-addons.css'; - -export const refreshToken = - (import.meta.env.AIUI_OCM_REFRESH_TOKEN as string | undefined) || window.OCM_REFRESH_TOKEN; -let expiration = Date.now(); -let token = ''; - -export const getOcmToken = async () => { - // if token expires in less than 5s, refresh it - if (Date.now() > expiration - 5000) { - if (!refreshToken) { - throw new Error('No refresh token available'); - } - const params = new URLSearchParams(); - params.append('grant_type', 'refresh_token'); - params.append('refresh_token', refreshToken || ''); - params.append('client_id', 'cloud-services'); - - try { - const response = await fetch('/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: params.toString(), - }); - - if (!response.ok) { - throw new Error(`Token refresh failed: ${response.status}`); - } - - const data = (await response.json()) as { access_token: string; expires_in: number }; - token = data.access_token; - expiration = Date.now() + data.expires_in * 1000; - } catch (error) { - // eslint-disable-next-line - console.error('Failed to refresh token:', error); - throw error; - } - } - return token; -}; - -const ChatBot = () => { - const navigate = useNavigate(); - const onApiCall = React.useCallback(async (input, init) => { - const token = await getOcmToken(); - return fetch(`/chatbot${input.toString()}`, { - ...(init || {}), - headers: { - ...(init?.headers || {}), - Authorization: `Bearer ${token}`, - }, - }); - }, []); - - const openClusterDetails = React.useCallback( - (id) => navigate(`/assisted-installer/clusters/${id}`), - [navigate], - ); - - return ( - - ); -}; - -export default ChatBot; diff --git a/apps/assisted-ui/vite.config.ts b/apps/assisted-ui/vite.config.ts index 141fb2274b..7dae76efd1 100644 --- a/apps/assisted-ui/vite.config.ts +++ b/apps/assisted-ui/vite.config.ts @@ -48,11 +48,6 @@ export default defineConfig(async ({ mode }) => { target: env.AIUI_APP_API_URL, changeOrigin: true, }, - '/chatbot': { - target: env.AIUI_CHAT_API_URL, - changeOrigin: true, - rewrite: (path: string) => path.replace(/^\/chatbot/, ''), - }, '/token': { target: env.AIUI_SSO_API_URL, changeOrigin: true, diff --git a/libs/chatbot/lib/components/ChatBot/AIAlert.tsx b/libs/chatbot/lib/components/ChatBot/AIAlert.tsx index d9187cda17..d3012c222c 100644 --- a/libs/chatbot/lib/components/ChatBot/AIAlert.tsx +++ b/libs/chatbot/lib/components/ChatBot/AIAlert.tsx @@ -1,4 +1,4 @@ -import { Alert, AlertActionCloseButton, Stack, StackItem } from '@patternfly-6/react-core'; +import { Alert, AlertActionCloseButton, Stack, StackItem } from '@patternfly/react-core'; import * as React from 'react'; import ExternalLink from './ExternalLink'; diff --git a/libs/chatbot/lib/components/ChatBot/BotMessage.tsx b/libs/chatbot/lib/components/ChatBot/BotMessage.tsx index d4d1df3196..12ddd8ec18 100644 --- a/libs/chatbot/lib/components/ChatBot/BotMessage.tsx +++ b/libs/chatbot/lib/components/ChatBot/BotMessage.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import { Message } from '@patternfly/chatbot'; import MessageLoading from '@patternfly/chatbot/dist/cjs/Message/MessageLoading'; import { MsgProps } from './helpers'; -import { Button, Stack, StackItem } from '@patternfly-6/react-core'; +import { Button, Stack, StackItem } from '@patternfly/react-core'; import { saveAs } from 'file-saver'; -import { DownloadIcon, ExternalLinkAltIcon } from '@patternfly-6/react-icons'; +import { DownloadIcon, ExternalLinkAltIcon } from '@patternfly/react-icons'; import FeedbackForm from './FeedbackCard'; import AIAvatar from '../../assets/lightspeed-logo.svg'; diff --git a/libs/chatbot/lib/components/ChatBot/ChatBotButton.tsx b/libs/chatbot/lib/components/ChatBot/ChatBotButton.tsx index a1d0b9b6f4..1776d9711b 100644 --- a/libs/chatbot/lib/components/ChatBot/ChatBotButton.tsx +++ b/libs/chatbot/lib/components/ChatBot/ChatBotButton.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { Button, Icon, Popover } from '@patternfly-6/react-core'; -import { InfoCircleIcon } from '@patternfly-6/react-icons'; +import { Button, Icon, Popover } from '@patternfly/react-core'; +import { InfoCircleIcon } from '@patternfly/react-icons'; import LightSpeedLogo from '../../assets/lightspeed-logo.svg'; diff --git a/libs/chatbot/lib/components/ChatBot/ChatBotHistory.tsx b/libs/chatbot/lib/components/ChatBot/ChatBotHistory.tsx index 9c22aa76cd..c04e0d2931 100644 --- a/libs/chatbot/lib/components/ChatBot/ChatBotHistory.tsx +++ b/libs/chatbot/lib/components/ChatBot/ChatBotHistory.tsx @@ -4,9 +4,9 @@ import { ChatbotDisplayMode, Conversation, } from '@patternfly/chatbot'; -import { Alert, MenuItemAction } from '@patternfly-6/react-core'; +import { Alert, MenuItemAction } from '@patternfly/react-core'; import { getErrorMessage } from './helpers'; -import { TrashAltIcon } from '@patternfly-6/react-icons'; +import { TrashAltIcon } from '@patternfly/react-icons'; import DeleteConversationModal from './DeleteConversationModal'; type ConversationHistory = { conversations: { conversation_id: string; created_at: string }[] }; diff --git a/libs/chatbot/lib/components/ChatBot/ChatBotWindow.tsx b/libs/chatbot/lib/components/ChatBot/ChatBotWindow.tsx index 79215c0d52..4d472fe04d 100644 --- a/libs/chatbot/lib/components/ChatBot/ChatBotWindow.tsx +++ b/libs/chatbot/lib/components/ChatBot/ChatBotWindow.tsx @@ -18,7 +18,7 @@ import { MessageBox, MessageBoxHandle, } from '@patternfly/chatbot'; -import { Brand, EmptyState, Spinner } from '@patternfly-6/react-core'; +import { Brand, EmptyState, Spinner } from '@patternfly/react-core'; import BotMessage from './BotMessage'; import { MESSAGE_BAR_ID, botRole, userRole, MsgProps } from './helpers'; diff --git a/libs/chatbot/lib/components/ChatBot/DeleteConversationModal.tsx b/libs/chatbot/lib/components/ChatBot/DeleteConversationModal.tsx index 1945a8ecdd..ce12c27d11 100644 --- a/libs/chatbot/lib/components/ChatBot/DeleteConversationModal.tsx +++ b/libs/chatbot/lib/components/ChatBot/DeleteConversationModal.tsx @@ -7,7 +7,7 @@ import { ModalHeader, Stack, StackItem, -} from '@patternfly-6/react-core'; +} from '@patternfly/react-core'; import * as React from 'react'; import { getErrorMessage } from './helpers'; import { Conversation } from '@patternfly/chatbot'; diff --git a/libs/chatbot/lib/components/ChatBot/ExternalLink.tsx b/libs/chatbot/lib/components/ChatBot/ExternalLink.tsx index 58d6ae83df..4d80532d7a 100644 --- a/libs/chatbot/lib/components/ChatBot/ExternalLink.tsx +++ b/libs/chatbot/lib/components/ChatBot/ExternalLink.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { Button } from '@patternfly-6/react-core'; -import { ExternalLinkAltIcon } from '@patternfly-6/react-icons'; +import { Button } from '@patternfly/react-core'; +import { ExternalLinkAltIcon } from '@patternfly/react-icons'; const ExternalLink = ({ href, children }: React.PropsWithChildren<{ href: string }>) => (