-
Notifications
You must be signed in to change notification settings - Fork 481
POC: DotAiChat #35226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
POC: DotAiChat #35226
Changes from all commits
383b432
91e7b5f
1228725
0d37ade
a315508
52ae90c
d6f7fc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,34 +1,70 @@ | ||
| { | ||
| "version": "0.2.0", | ||
| "configurations": [ | ||
| { | ||
| "name": "Launch dotCMS UI (Dev Server)", | ||
| "type": "node", | ||
| "request": "launch", | ||
| "cwd": "${workspaceFolder}", | ||
| "runtimeExecutable": "yarn", | ||
| "runtimeArgs": ["nx", "serve", "dotcms-ui"], | ||
| "console": "integratedTerminal", | ||
| "internalConsoleOptions": "neverOpen", | ||
| "serverReadyAction": { | ||
| "pattern": "Local:.*?(https?://[^\\s]+)", | ||
| "uriFormat": "%s", | ||
| "action": "openExternally" | ||
| } | ||
| }, | ||
| { | ||
| "name": "Debug dotCMS UI in Chrome", | ||
| "type": "chrome", | ||
| "request": "launch", | ||
| "url": "http://localhost:4200", | ||
| "webRoot": "${workspaceFolder}", | ||
| "sourceMapPathOverrides": { | ||
| "webpack:/*": "${webRoot}/*", | ||
| "/./*": "${webRoot}/*", | ||
| "/src/*": "${webRoot}/*", | ||
| "/*": "*", | ||
| "/./~/*": "${webRoot}/node_modules/*" | ||
| } | ||
| } | ||
| ] | ||
| "version": "0.2.0", | ||
| "configurations": [ | ||
| { | ||
| "name": "Launch dotCMS UI (Dev Server)", | ||
| "type": "node", | ||
| "request": "launch", | ||
| "cwd": "${workspaceFolder}", | ||
| "runtimeExecutable": "yarn", | ||
| "runtimeArgs": ["nx", "serve", "dotcms-ui"], | ||
| "console": "integratedTerminal", | ||
| "internalConsoleOptions": "neverOpen", | ||
| "serverReadyAction": { | ||
| "pattern": "Local:.*?(https?://[^\\s]+)", | ||
| "uriFormat": "%s", | ||
| "action": "openExternally" | ||
| } | ||
| }, | ||
| { | ||
| "name": "Debug dotCMS UI in Chrome", | ||
| "type": "chrome", | ||
| "request": "launch", | ||
| "url": "http://localhost:4200", | ||
| "webRoot": "${workspaceFolder}", | ||
| "sourceMapPathOverrides": { | ||
| "webpack:/*": "${webRoot}/*", | ||
| "/./*": "${webRoot}/*", | ||
| "/src/*": "${webRoot}/*", | ||
| "/*": "*", | ||
| "/./~/*": "${webRoot}/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=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. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate launch configs with same name, one wrongLow Severity There are two identical "Debug api-dot-ai-chat with Nx" configurations in Reviewed by Cursor Bugbot for commit d6f7fc7. Configure here. |
||
| ] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "extends": ["../../.eslintrc.base.json"], | ||
| "ignorePatterns": ["!**/*"], | ||
| "overrides": [ | ||
| { | ||
| "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
| "rules": {} | ||
| }, | ||
| { | ||
| "files": ["*.ts", "*.tsx"], | ||
| "rules": {} | ||
| }, | ||
| { | ||
| "files": ["*.js", "*.jsx"], | ||
| "rules": {} | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| export default { | ||
| displayName: 'api-dot-ai-chat-e2e', | ||
| preset: '../../jest.preset.js', | ||
| globalSetup: '<rootDir>/src/support/global-setup.ts', | ||
| globalTeardown: '<rootDir>/src/support/global-teardown.ts', | ||
| setupFiles: ['<rootDir>/src/support/test-setup.ts'], | ||
| testEnvironment: 'node', | ||
| transform: { | ||
| '^.+\\.[tj]s$': [ | ||
| 'ts-jest', | ||
| { | ||
| tsconfig: '<rootDir>/tsconfig.spec.json' | ||
| } | ||
| ] | ||
| }, | ||
| moduleFileExtensions: ['ts', 'js', 'html'], | ||
| coverageDirectory: '../../coverage/api-dot-ai-chat-e2e' | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "name": "api-dot-ai-chat-e2e", | ||
| "$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
| "projectType": "application", | ||
| "implicitDependencies": ["api-dot-ai-chat"], | ||
| "targets": { | ||
| "e2e": { | ||
| "executor": "@nx/jest:jest", | ||
| "outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"], | ||
| "options": { | ||
| "jestConfig": "apps/api-dot-ai-chat-e2e/jest.config.cts", | ||
| "passWithNoTests": true | ||
| }, | ||
| "dependsOn": ["api-dot-ai-chat:build", "api-dot-ai-chat:serve"] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,10 @@ | ||||||||||||||||||||||||||||||||||||||
| import axios from 'axios'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| describe('GET /', () => { | ||||||||||||||||||||||||||||||||||||||
| it('should return a message', async () => { | ||||||||||||||||||||||||||||||||||||||
| const res = await axios.get(`/`); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| expect(res.status).toBe(200); | ||||||||||||||||||||||||||||||||||||||
| expect(res.data).toEqual({ message: 'Hello API' }); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+8
|
||||||||||||||||||||||||||||||||||||||
| describe('GET /', () => { | |
| it('should return a message', async () => { | |
| const res = await axios.get(`/`); | |
| expect(res.status).toBe(200); | |
| expect(res.data).toEqual({ message: 'Hello API' }); | |
| describe('POST /dotaichat/chat', () => { | |
| it('should hit an existing API endpoint', async () => { | |
| const res = await axios.post( | |
| `/dotaichat/chat`, | |
| {}, | |
| { | |
| validateStatus: () => true, | |
| } | |
| ); | |
| expect(res.status).not.toBe(404); | |
| expect(res.status).not.toBe(405); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||
| import { waitForPortOpen } from '@nx/node/utils'; | ||||||
|
|
||||||
| /* eslint-disable */ | ||||||
| var __TEARDOWN_MESSAGE__: string; | ||||||
|
|
||||||
| module.exports = async function () { | ||||||
| // Start services that that the app needs to run (e.g. database, docker-compose, etc.). | ||||||
| console.log('\nSetting up...\n'); | ||||||
|
|
||||||
| const host = process.env.HOST ?? 'localhost'; | ||||||
| const port = process.env.PORT ? Number(process.env.PORT) : 3000; | ||||||
|
||||||
| const port = process.env.PORT ? Number(process.env.PORT) : 3000; | |
| const port = process.env.PORT ? Number(process.env.PORT) : 3333; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { killPort } from '@nx/node/utils'; | ||
| /* eslint-disable */ | ||
|
|
||
| module.exports = async function () { | ||
| // Put clean up logic here (e.g. stopping services, docker-compose, etc.). | ||
| // Hint: `globalThis` is shared between setup and teardown. | ||
| const port = process.env.PORT ? Number(process.env.PORT) : 3000; | ||
| await killPort(port); | ||
| console.log(globalThis.__TEARDOWN_MESSAGE__); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| /* eslint-disable */ | ||
| import axios from 'axios'; | ||
|
|
||
| module.exports = async function () { | ||
| // Configure axios for tests to use. | ||
| const host = process.env.HOST ?? 'localhost'; | ||
| const port = process.env.PORT ?? '3000'; | ||
| axios.defaults.baseURL = `http://${host}:${port}`; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "extends": "../../tsconfig.base.json", | ||
| "files": [], | ||
| "include": [], | ||
| "references": [ | ||
| { | ||
| "path": "./tsconfig.spec.json" | ||
| } | ||
| ], | ||
| "compilerOptions": { | ||
| "esModuleInterop": true | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "extends": "./tsconfig.json", | ||
| "compilerOptions": { | ||
| "outDir": "../../dist/out-tsc", | ||
| "module": "commonjs", | ||
| "types": ["jest", "node"] | ||
| }, | ||
| "include": ["jest.config.ts", "src/**/*.ts"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "extends": ["../../.eslintrc.base.json"], | ||
| "ignorePatterns": ["!**/*"], | ||
| "overrides": [ | ||
| { | ||
| "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
| "rules": {} | ||
| }, | ||
| { | ||
| "files": ["*.ts", "*.tsx"], | ||
| "rules": {} | ||
| }, | ||
| { | ||
| "files": ["*.js", "*.jsx"], | ||
| "rules": {} | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| module.exports = { | ||
| displayName: 'api-dot-ai-chat', | ||
| preset: '../../jest.preset.js', | ||
| testEnvironment: 'node', | ||
| transform: { | ||
| '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }] | ||
| }, | ||
| moduleFileExtensions: ['ts', 'js', 'html'], | ||
| coverageDirectory: '../../coverage/apps/api-dot-ai-chat' | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| { | ||
| "name": "api-dot-ai-chat", | ||
| "$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
| "sourceRoot": "apps/api-dot-ai-chat/src", | ||
| "projectType": "application", | ||
| "tags": [], | ||
| "targets": { | ||
| "prune-lockfile": { | ||
| "dependsOn": ["build"], | ||
| "cache": true, | ||
| "executor": "@nx/js:prune-lockfile", | ||
| "outputs": [ | ||
| "{workspaceRoot}/dist/apps/api-dot-ai-chat/package.json", | ||
| "{workspaceRoot}/dist/apps/api-dot-ai-chat/yarn.lock" | ||
| ], | ||
| "options": { | ||
| "buildTarget": "build" | ||
| } | ||
| }, | ||
| "copy-workspace-modules": { | ||
| "dependsOn": ["build"], | ||
| "cache": true, | ||
| "outputs": ["{workspaceRoot}/dist/apps/api-dot-ai-chat/workspace_modules"], | ||
| "executor": "@nx/js:copy-workspace-modules", | ||
| "options": { | ||
| "buildTarget": "build" | ||
| } | ||
| }, | ||
| "prune": { | ||
| "dependsOn": ["prune-lockfile", "copy-workspace-modules"], | ||
| "executor": "nx:noop" | ||
| }, | ||
| "serve": { | ||
| "continuous": true, | ||
| "executor": "@nx/js:node", | ||
| "defaultConfiguration": "development", | ||
| "dependsOn": ["build"], | ||
| "options": { | ||
| "buildTarget": "api-dot-ai-chat:build", | ||
| "runBuildTargetDependencies": false | ||
| }, | ||
| "configurations": { | ||
| "development": { | ||
| "buildTarget": "api-dot-ai-chat:build:development" | ||
| }, | ||
| "production": { | ||
| "buildTarget": "api-dot-ai-chat:build:production" | ||
| } | ||
| } | ||
| }, | ||
| "test": { | ||
| "executor": "@nx/jest:jest", | ||
| "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], | ||
| "options": { | ||
| "jestConfig": "apps/api-dot-ai-chat/jest.config.cts", | ||
| "passWithNoTests": true | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { HashbrownOpenAI } from '@hashbrownai/openai'; | ||
| import express from 'express'; | ||
|
|
||
| const apiKey = process.env['OPENAI_API_KEY']; | ||
|
|
||
| if (!apiKey) { | ||
| throw new Error('OPENAI_API_KEY is not set'); | ||
| } | ||
|
|
||
| const app = express(); | ||
|
|
||
| app.use(express.json()); | ||
| app.use((req, res, next) => { | ||
| res.header('Access-Control-Allow-Origin', '*'); | ||
| res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); | ||
| res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'); | ||
|
|
||
|
Comment on lines
+13
to
+17
|
||
| if (req.method === 'OPTIONS') { | ||
| res.sendStatus(204); | ||
| return; | ||
| } | ||
|
|
||
| next(); | ||
| }); | ||
|
|
||
| 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(); | ||
| }); | ||
|
Comment on lines
+26
to
+39
|
||
|
|
||
| const port = process.env.PORT || 3333; | ||
| const server = app.listen(port, () => { | ||
| // eslint-disable-next-line no-console | ||
| console.log(`Listening at http://localhost:${port}/dotaichat`); | ||
| }); | ||
| server.on('error', console.error); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "extends": "./tsconfig.json", | ||
| "compilerOptions": { | ||
| "outDir": "../../dist/out-tsc", | ||
| "module": "commonjs", | ||
| "moduleResolution": "node10", | ||
| "types": ["node", "express"] | ||
| }, | ||
| "include": ["src/**/*.ts"], | ||
| "exclude": ["jest.config.ts", "jest.config.cts", "src/**/*.spec.ts", "src/**/*.test.ts"] | ||
| } |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 underapps/api-dot-ai-chat. This can make VS Code debugging pick the wrong config and fail to map sources.