Conversation
|
Claude finished @nicobytes's task in 5m 20s —— View job Claude finished @nicobytes's task in 52s —— View job Rollback Safety Analysis
Result: ✅ Safe to Rollback All 57 changed files are confined to the frontend monorepo (
Rolling back to N-1 simply removes the new AI Chat feature ( |
|
Rollback Safety Analysis After reviewing all 55 changed files against every category in the rollback-unsafe reference: Result: ✅ Safe to Rollback All changes are confined to the frontend monorepo (
No unsafe categories matched:
Rolling back to N-1 simply removes the new AI Chat feature. No data, schema, or index state is affected. |
…prompt for documentation queries
There was a problem hiding this comment.
Pull request overview
This PR adds an AI chat proof-of-concept to the dotCMS Nx monorepo: an Nx/Express backend (api-dot-ai-chat) that streams responses via @hashbrownai/openai, plus a new Angular library (@dotcms/dot-ai-chat) integrated into the admin UI with basic testing/tooling scaffolding.
Changes:
- Introduces
api-dot-ai-chat(Express) to proxy/stream chat requests to OpenAI. - Adds
libs/dot-ai-chatwith Hashbrown chat UI, tools, and UI integration points indotcms-ui. - Scaffolds
api-dot-ai-chat-e2eplus dev/editor support (VS Code launch config, extension recommendations, justfile helper).
Reviewed changes
Copilot reviewed 55 out of 57 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| justfile | Adds a helper command to run the AI chat backend locally. |
| core-web/yarn.lock | Updates lockfile for new dependencies. |
| core-web/tsconfig.base.json | Adds TS path alias for @dotcms/dot-ai-chat. |
| core-web/package.json | Adds dependencies for Hashbrown AI + Express + tooling. |
| core-web/nx.json | Registers Nx webpack plugin settings. |
| core-web/libs/dot-ai-chat/tsconfig.spec.json | Jest TS config for the new lib. |
| core-web/libs/dot-ai-chat/tsconfig.lib.json | Build TS config for the new lib. |
| core-web/libs/dot-ai-chat/tsconfig.json | Base TS config for the new lib. |
| core-web/libs/dot-ai-chat/src/test-setup.ts | Sets up Angular zone test environment. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/tools/search-documentation.tool.ts | Adds a tool to search dotCMS documentation. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/tools/get-favorite-pages.tool.ts | Adds a tool to fetch user favorite pages. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/tools/get-content-types.tool.ts | Adds a tool to fetch content types. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/services/hashbrown-doc-search.service.ts | Implements doc search service used by the tool. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/services/hashbrown-chat-bridge.service.ts | Bridge service to open chat / send user messages. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/hashbrown-chat.prompt.ts | Defines the system prompt for the assistant. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/hashbrown-chat.component.ts | Implements the chat UI wrapper using Hashbrown. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/hashbrown-chat.component.html | Adds chat UI template layout. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/components/markdown.component.ts | Renders assistant markdown outputs. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/components/favorite-page-list.component.ts | UI wrapper component for favorite pages. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/components/favorite-page-card.component.ts | UI card component for favorite pages. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/components/content-type-list.component.ts | UI wrapper component for content types. |
| core-web/libs/dot-ai-chat/src/lib/hashbrown-poc/components/content-type-card.component.ts | UI card component for content types. |
| core-web/libs/dot-ai-chat/src/index.ts | Exposes the public API for the new lib. |
| core-web/libs/dot-ai-chat/README.md | Adds generated README for the new lib. |
| core-web/libs/dot-ai-chat/project.json | Adds Nx project configuration for the lib. |
| core-web/libs/dot-ai-chat/jest.config.cts | Adds Jest config for the lib. |
| core-web/libs/dot-ai-chat/.eslintrc.json | Adds ESLint config for the lib. |
| core-web/apps/dotcms-ui/src/app/view/components/main-legacy/main-legacy.component.ts | Adds AI chat drawer visibility state + bridge subscription. |
| core-web/apps/dotcms-ui/src/app/view/components/main-legacy/main-legacy.component.html | Adds PrimeNG drawer hosting the chat component. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/dot-toolbar.spec.ts | Adds toolbar tests for the AI chat trigger. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/dot-toolbar.component.ts | Adds input/output to control AI chat visibility and open action. |
| core-web/apps/dotcms-ui/src/app/view/components/dot-toolbar/dot-toolbar.component.html | Adds AI chat button to the toolbar. |
| core-web/apps/dotcms-ui/src/app/providers.ts | Registers DotPageListService provider for AI tool usage. |
| core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.html | Changes dialog modality setting. |
| core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/form/content-types-form.component.ts | Adds an action to open chat + send a message. |
| core-web/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/form/content-types-form.component.html | Adds a clickable AI icon to trigger the assistant. |
| core-web/apps/dotcms-ui/src/app/app.config.ts | Configures Hashbrown provider baseUrl. |
| core-web/apps/api-dot-ai-chat/webpack.config.js | Adds Nx webpack config for Node target bundling. |
| core-web/apps/api-dot-ai-chat/tsconfig.spec.json | Adds Jest TS config for the API app. |
| core-web/apps/api-dot-ai-chat/tsconfig.json | Adds base TS config for the API app. |
| core-web/apps/api-dot-ai-chat/tsconfig.app.json | Adds app TS config for the API app. |
| core-web/apps/api-dot-ai-chat/src/main.ts | Implements Express server and /dotaichat/chat streaming endpoint. |
| core-web/apps/api-dot-ai-chat/src/assets/.gitkeep | Adds placeholder assets folder. |
| core-web/apps/api-dot-ai-chat/project.json | Adds Nx project configuration/targets for API app. |
| core-web/apps/api-dot-ai-chat/jest.config.cts | Adds Jest config for the API app. |
| core-web/apps/api-dot-ai-chat/.eslintrc.json | Adds ESLint config for the API app. |
| core-web/apps/api-dot-ai-chat-e2e/tsconfig.spec.json | Adds TS config for API e2e tests. |
| core-web/apps/api-dot-ai-chat-e2e/tsconfig.json | Adds base TS config for API e2e tests. |
| core-web/apps/api-dot-ai-chat-e2e/src/support/test-setup.ts | Configures Axios baseURL for e2e tests. |
| core-web/apps/api-dot-ai-chat-e2e/src/support/global-teardown.ts | Adds teardown to kill the test port. |
| core-web/apps/api-dot-ai-chat-e2e/src/support/global-setup.ts | Adds setup to wait for port readiness. |
| core-web/apps/api-dot-ai-chat-e2e/src/api-dot-ai-chat/api-dot-ai-chat.spec.ts | Adds a sample e2e HTTP test. |
| core-web/apps/api-dot-ai-chat-e2e/project.json | Adds Nx e2e target configuration. |
| core-web/apps/api-dot-ai-chat-e2e/jest.config.cts | Adds Jest config for the e2e project. |
| core-web/apps/api-dot-ai-chat-e2e/.eslintrc.json | Adds ESLint config for the e2e project. |
| core-web/.vscode/launch.json | Adds debug launch configs for the new API app. |
| core-web/.vscode/extensions.json | Recommends Playwright extension. |
| # OPOC command: starts AI Chat backend for local testing only. | ||
| # OPOC usage: just dev-ai-chat <OPENAI_API_KEY> |
There was a problem hiding this comment.
The comment/usage label says "OPOC"; this looks like a typo for "POC" and could confuse contributors scanning the justfile for POC utilities.
| dev-ai-chat openai_api_key: | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| if [ -z "{{ openai_api_key }}" ]; then | ||
| echo "OPENAI_API_KEY is required." | ||
| echo "Usage: just dev-ai-chat <OPENAI_API_KEY>" | ||
| exit 1 | ||
| fi | ||
|
|
||
| cd core-web | ||
| OPENAI_API_KEY="{{ openai_api_key }}" yarn nx serve api-dot-ai-chat |
There was a problem hiding this comment.
Passing the OpenAI API key as a positional argument risks leaking it via shell history/process listings. Prefer reading OPENAI_API_KEY from the environment (or prompting) instead of embedding it in the command invocation.
| "document-register-element": "1.7.2", | ||
| "dom-autoscroller": "2.3.4", | ||
| "dragula": "3.7.3", | ||
| "execa": "9.6.0", | ||
| "express": "^4.21.2", | ||
| "font-awesome": "4.7.0", | ||
| "fs-extra": "11.3.2", | ||
| "gridstack": "8.1.1", | ||
| "htmldiff-js": "1.0.5", | ||
| "inquirer": "13.0.1", | ||
| "jstat": "1.9.6", | ||
| "marked": "12.0.2", | ||
| "md5": "2.3.0", | ||
| "next": "14.0.4", | ||
| "ng-packagr": "19.2.2", | ||
| "ng2-dragula": "5.0.1", | ||
| "ngx-markdown": "20.1.0", | ||
| "ngx-tiptap": "12.0.0", | ||
| "node-fetch": "2.6.1", | ||
| "openai": "^6.33.0", | ||
| "ora": "9.0.0", |
There was a problem hiding this comment.
New dependencies are added with caret (^) ranges (e.g., express/openai/webpack-cli). The rest of this package.json is largely pinned; using ranges can make installs non-reproducible across time/CI. Consider pinning exact versions here as well.
| const API_URL = 'https://cdn.dotcms.dev/api/v1/ai/search'; | ||
|
|
||
| const API_TOKEN = | ||
| 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcGk1MjhiN2E5ZS1iMDEwLTQ5NzUtOGE2Ny0wNmU2ZGFiY2IwYTQiLCJ4bW9kIjoxNzIzODIwMjkxMDAwLCJuYmYiOjE3MjM4MjAyOTEsImlzcyI6ImNvcnBzaXRlcy1oZWFkbGVzcyIsImxhYmVsIjoiZm9yIHNlYXJjaCBjaGF0Ym90IiwiZXhwIjoxODE4Mzg4ODAwLCJpYXQiOjE3MjM4MjAyOTEsImp0aSI6IjYyZmM5Zjk5LTdlYTktNGNhYy05MzBhLWU0NzQxZDU2Nzg1NiJ9.xDYRWbR0geBzyR4UM0w-rOGRmj-mISTVqp0-FIqoFbA'; | ||
|
|
There was a problem hiding this comment.
A bearer JWT is hardcoded in the frontend bundle (API_TOKEN). This is a secret that will be shipped to browsers and is effectively public once merged. Move this call behind a backend proxy (or use server-side auth) and source any required tokens from secure runtime configuration, not from committed code.
| ) | ||
| ), | ||
| provideHashbrown({ | ||
| baseUrl: 'http://localhost:3333/dotaichat/chat' |
There was a problem hiding this comment.
The Hashbrown baseUrl is hardcoded to localhost:3333. This will break in non-local environments and makes the UI implicitly depend on a dev-only sidecar. Prefer using environment configuration (or a relative URL via the existing proxy config) so builds can run in CI/prod without code changes.
| baseUrl: 'http://localhost:3333/dotaichat/chat' | |
| baseUrl: '/dotaichat/chat' |
| <i | ||
| (click)="sendMessageToAIAssistant()" | ||
| class="cursor-pointer pi pi-sparkles text-white-400 text-xl ml-1"></i> |
There was a problem hiding this comment.
This clickable icon is not keyboard-accessible and has no accessible name. Use a (or pButton) with an aria-label, and ensure it supports keyboard activation (Enter/Space) so the AI assistant entry point is accessible.
| <i | |
| (click)="sendMessageToAIAssistant()" | |
| class="cursor-pointer pi pi-sparkles text-white-400 text-xl ml-1"></i> | |
| <button | |
| type="button" | |
| (click)="sendMessageToAIAssistant()" | |
| aria-label="Open AI assistant" | |
| class="cursor-pointer ml-1 bg-transparent border-0 p-0 inline-flex items-center"> | |
| <i class="pi pi-sparkles text-white-400 text-xl" aria-hidden="true"></i> | |
| </button> |
| // eslint-disable-next-line @nx/enforce-module-boundaries | ||
| import { | ||
| DotPageListService, | ||
| ListPagesParams | ||
| } from '../../../../../../apps/dotcms-ui/src/app/portlets/dot-pages/services/dot-page-list.service'; |
There was a problem hiding this comment.
This tool bypasses Nx module boundaries by importing DotPageListService directly from the dotcms-ui app source. That couples the library to an app-internal path and will be fragile for refactors. Consider moving the page list service (or a thin wrapper API) into a shared lib (e.g., data-access) and importing it from there.
| private readonly defaultDetailRoute = | ||
| '/content-types-angular/edit/f6259cc9-5d78-453e-8167-efd7b72b2e96'; | ||
|
|
||
| readonly name = input.required<string>(); | ||
| readonly variable = input.required<string>(); | ||
| readonly type = input.required<string>(); | ||
| readonly action = input<string>(''); | ||
| readonly groupLabel = input<string>(''); | ||
| readonly iconClass = computed(() => this.getIconClass(this.type())); | ||
|
|
||
| detailRoute(): string { | ||
| return this.action() || this.defaultDetailRoute; | ||
| } |
There was a problem hiding this comment.
defaultDetailRoute hardcodes a specific content type UUID. If the AI/tooling doesn’t provide action, users may be routed to an unrelated or non-existent content type. Prefer requiring action (or deriving a route from the content type identifier/variable) instead of using a hardcoded fallback.
| { | ||
| "type": "node", | ||
| "request": "launch", | ||
| "name": "Debug api-dot-ai-chat with Nx", | ||
| "runtimeExecutable": "yarn", | ||
| "runtimeArgs": ["nx", "serve", "api-dot-ai-chat"], | ||
| "env": { | ||
| "NODE_OPTIONS": "--inspect=9229" | ||
| }, | ||
| "console": "integratedTerminal", | ||
| "internalConsoleOptions": "neverOpen", | ||
| "skipFiles": ["<node_internals>/**"], | ||
| "sourceMaps": true, | ||
| "outFiles": [ | ||
| "${workspaceFolder}/api-dot-ai-chat/dist/**/*.(m|c|)js", | ||
| "!**/node_modules/**" | ||
| ] | ||
| }, | ||
| { | ||
| "type": "node", | ||
| "request": "launch", | ||
| "name": "Debug api-dot-ai-chat with Nx", | ||
| "runtimeExecutable": "yarn", | ||
| "runtimeArgs": ["nx", "serve", "api-dot-ai-chat"], | ||
| "env": { | ||
| "NODE_OPTIONS": "--inspect=9230" | ||
| }, | ||
| "console": "integratedTerminal", | ||
| "internalConsoleOptions": "neverOpen", | ||
| "skipFiles": ["<node_internals>/**"], | ||
| "sourceMaps": true, | ||
| "outFiles": [ | ||
| "${workspaceFolder}/apps/api-dot-ai-chat/dist/**/*.(m|c|)js", | ||
| "!**/node_modules/**" | ||
| ] |
There was a problem hiding this comment.
There are two launch configurations with the same name ("Debug api-dot-ai-chat with Nx"), and the first one’s outFiles path points to ${workspaceFolder}/api-dot-ai-chat/dist/... which doesn’t match the app location under apps/api-dot-ai-chat. This can make VS Code debugging pick the wrong config and fail to map sources.
| app.post('/dotaichat/chat', async (req, res) => { | ||
| const stream = HashbrownOpenAI.stream.text({ | ||
| apiKey, | ||
| request: req.body | ||
| }); | ||
|
|
||
| res.header('Content-Type', 'application/octet-stream'); | ||
|
|
||
| for await (const chunk of stream) { | ||
| res.write(chunk); | ||
| } | ||
|
|
||
| res.end(); | ||
| }); |
There was a problem hiding this comment.
There is no GET endpoint (e.g., / or /health) implemented, but the repository’s e2e scaffold is testing GET /. Adding a lightweight health/root route (and keeping it aligned with tests) will make local/dev validation much clearer.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 5 potential issues.
Bugbot Autofix is kicking off a free cloud agent to fix these issues. This run is complimentary, but you can enable autofix for all future PRs in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.
|
|
||
| this.hashbrownChatBridgeService.requestOpenChat(); | ||
| this.hashbrownChatBridgeService.sendUserMessage(message); | ||
| } |
There was a problem hiding this comment.
Bridge message lost due to Subject timing race
High Severity
When sendMessageToAIAssistant calls requestOpenChat() and sendUserMessage() synchronously, the chat component isn't yet rendered. The plain userMessageSubject emits before a subscriber is active, causing the pre-filled message to be lost. The AI chat drawer opens empty.
Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.
| console.log('\nSetting up...\n'); | ||
|
|
||
| const host = process.env.HOST ?? 'localhost'; | ||
| const port = process.env.PORT ? Number(process.env.PORT) : 3000; |
There was a problem hiding this comment.
E2E default port doesn't match server default
Medium Severity
The e2e test setup (global-setup.ts, test-setup.ts) defaults to port 3000, but the Express server (main.ts) uses 3333. This port mismatch causes waitForPortOpen to hang, leading to test timeouts when the PORT environment variable isn't explicitly configured.
Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.
|
|
||
| expect(res.status).toBe(200); | ||
| expect(res.data).toEqual({ message: 'Hello API' }); | ||
| }); |
There was a problem hiding this comment.
E2E spec tests route that doesn't exist
Medium Severity
The api-dot-ai-chat e2e test sends GET / expecting { message: 'Hello API' }. The server only defines POST /dotaichat/chat, so there's no GET / handler. This means the test will always fail with a 404, as its route and expected payload don't match any actual server endpoint.
Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.
| const API_URL = 'https://cdn.dotcms.dev/api/v1/ai/search'; | ||
|
|
||
| const API_TOKEN = | ||
| 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcGk1MjhiN2E5ZS1iMDEwLTQ5NzUtOGE2Ny0wNmU2ZGFiY2IwYTQiLCJ4bW9kIjoxNzIzODIwMjkxMDAwLCJuYmYiOjE3MjM4MjAyOTEsImlzcyI6ImNvcnBzaXRlcy1oZWFkbGVzcyIsImxhYmVsIjoiZm9yIHNlYXJjaCBjaGF0Ym90IiwiZXhwIjoxODE4Mzg4ODAwLCJpYXQiOjE3MjM4MjAyOTEsImp0aSI6IjYyZmM5Zjk5LTdlYTktNGNhYy05MzBhLWU0NzQxZDU2Nzg1NiJ9.xDYRWbR0geBzyR4UM0w-rOGRmj-mISTVqp0-FIqoFbA'; |
There was a problem hiding this comment.
Hardcoded API token committed to source code
High Severity
The API_TOKEN, a long-lived JWT for the cdn.dotcms.dev AI search API, is hardcoded. This token is exposed in version control and the client-side bundle, allowing unauthorized API access.
Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.
| "${workspaceFolder}/apps/api-dot-ai-chat/dist/**/*.(m|c|)js", | ||
| "!**/node_modules/**" | ||
| ] | ||
| } |
There was a problem hiding this comment.
Duplicate launch configs with same name, one wrong
Low Severity
There are two identical "Debug api-dot-ai-chat with Nx" configurations in launch.json, making them indistinguishable in the VS Code debug dropdown. One of these duplicates also has an incorrect outFiles path, missing the apps/ prefix, which prevents source maps from resolving correctly. This appears to be an unintended leftover configuration.
Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here.


This pull request introduces a new backend API application,
api-dot-ai-chat, to the monorepo, along with its end-to-end (e2e) testing setup and supporting configuration. The changes include the main Express server implementation that proxies requests to the OpenAI API via@hashbrownai/openai, Nx project configuration for build and serve, Jest-based test setup, and a Playwright extension recommendation for VSCode. Additionally, an e2e test project is scaffolded with sample tests and setup/teardown scripts.The most important changes are:
New API Application:
api-dot-ai-chatapplication, including an Express server (main.ts) that exposes a/dotaichat/chatendpoint to stream OpenAI responses, with CORS and JSON middleware configured. The server requires theOPENAI_API_KEYenvironment variable.project.json), TypeScript build configs (tsconfig.json,tsconfig.app.json,tsconfig.spec.json), Jest test config (jest.config.cts), and a custom Webpack config for the new app. [1] [2] [3] [4] [5] [6]End-to-End (e2e) Testing Setup:
api-dot-ai-chat-e2e, with Nx and Jest configuration, TypeScript setup, and sample test (api-dot-ai-chat.spec.ts) that verifies the root endpoint returns a hello message. [1] [2] [3] [4] [5]Tooling and Editor Support:
Linting:
These changes collectively scaffold a new backend API service with robust local development, testing, and debugging support.
Note
High Risk
Introduces a new Express API that streams OpenAI responses (with permissive CORS) and wires it into the main UI via a global chat drawer, expanding the app’s external-network/security surface. Also adds a client-side doc-search service with a hardcoded bearer token, which is sensitive and could leak/abuse if shipped.
Overview
Adds a new Nx app,
api-dot-ai-chat, running an Express server that exposesPOST /dotaichat/chatto stream responses via@hashbrownai/openai(requiresOPENAI_API_KEY), plus related Webpack/TS/Jest config and anapi-dot-ai-chat-e2eJest project with setup/teardown helpers.Introduces a new
libs/dot-ai-chatpackage implementing a Hashbrown chat UI (dotcms-hashbrown-chat), bridge service, and tools to fetch content types, favorite pages, and search documentation; the UI is integrated intodotcms-uiviaprovideHashbrown, a new toolbar sparkles button, and a right-sidep-drawerthat can be opened programmatically from the content-types form banner.Also updates workspace tooling/config (VSCode recommendations/launch configs, Nx webpack plugin registration, dependency adds for Hashbrown/OpenAI/Express, and a
just dev-ai-chathelper command).Reviewed by Cursor Bugbot for commit d6f7fc7. Bugbot is set up for automated code reviews on this repo. Configure here.