mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Move bot (#6268)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Vendored
+23
@@ -389,6 +389,29 @@
|
||||
"showAsyncStacks": true,
|
||||
"outputCapture": "std",
|
||||
"cwd": "${workspaceRoot}/services/analytics-collector/pod-analytics-collector"
|
||||
},
|
||||
{
|
||||
"name": "Debug AI bot",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"args": ["src/index.ts"],
|
||||
"env": {
|
||||
"ACCOUNTS_URL": "http://localhost:3000",
|
||||
"MONGO_URL": "mongodb://localhost:27017",
|
||||
"PORT": "4008",
|
||||
"SERVER_SECRET": "secret",
|
||||
"SUPPORT_WORKSPACE": "support",
|
||||
"FIRST_NAME": "Jolie",
|
||||
"LAST_NAME": "AI",
|
||||
"PASSWORD": "password",
|
||||
"AVATAR_PATH": "./assets/avatar.png",
|
||||
"AVATAR_CONTENT_TYPE": ".png"
|
||||
},
|
||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||
"sourceMaps": true,
|
||||
"cwd": "${workspaceRoot}/services/ai-bot/pod-ai-bot",
|
||||
"protocol": "inspector",
|
||||
"outputCapture": "std"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+189
-1
@@ -71,6 +71,9 @@ dependencies:
|
||||
'@rush-temp/activity-resources':
|
||||
specifier: file:./projects/activity-resources.tgz
|
||||
version: file:projects/activity-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
|
||||
'@rush-temp/ai-bot':
|
||||
specifier: file:./projects/ai-bot.tgz
|
||||
version: file:projects/ai-bot.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
|
||||
'@rush-temp/analytics':
|
||||
specifier: file:./projects/analytics.tgz
|
||||
version: file:projects/analytics.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
|
||||
@@ -407,6 +410,9 @@ dependencies:
|
||||
'@rush-temp/model-server-activity':
|
||||
specifier: file:./projects/model-server-activity.tgz
|
||||
version: file:projects/model-server-activity.tgz
|
||||
'@rush-temp/model-server-ai-bot':
|
||||
specifier: file:./projects/model-server-ai-bot.tgz
|
||||
version: file:projects/model-server-ai-bot.tgz
|
||||
'@rush-temp/model-server-attachment':
|
||||
specifier: file:./projects/model-server-attachment.tgz
|
||||
version: file:projects/model-server-attachment.tgz
|
||||
@@ -572,6 +578,9 @@ dependencies:
|
||||
'@rush-temp/pod-account':
|
||||
specifier: file:./projects/pod-account.tgz
|
||||
version: file:projects/pod-account.tgz
|
||||
'@rush-temp/pod-ai-bot':
|
||||
specifier: file:./projects/pod-ai-bot.tgz
|
||||
version: file:projects/pod-ai-bot.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||
'@rush-temp/pod-analytics-collector':
|
||||
specifier: file:./projects/pod-analytics-collector.tgz
|
||||
version: file:projects/pod-analytics-collector.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||
@@ -701,6 +710,12 @@ dependencies:
|
||||
'@rush-temp/server-activity-resources':
|
||||
specifier: file:./projects/server-activity-resources.tgz
|
||||
version: file:projects/server-activity-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
|
||||
'@rush-temp/server-ai-bot':
|
||||
specifier: file:./projects/server-ai-bot.tgz
|
||||
version: file:projects/server-ai-bot.tgz(esbuild@0.20.1)(ts-node@10.9.2)
|
||||
'@rush-temp/server-ai-bot-resources':
|
||||
specifier: file:./projects/server-ai-bot-resources.tgz
|
||||
version: file:projects/server-ai-bot-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
|
||||
'@rush-temp/server-analytics-collector':
|
||||
specifier: file:./projects/server-analytics-collector.tgz
|
||||
version: file:projects/server-analytics-collector.tgz(esbuild@0.20.1)(ts-node@10.9.2)
|
||||
@@ -23971,6 +23986,37 @@ packages:
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/ai-bot.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-TH/kDh1LKw+pJRxLZZIjR7da6BQslR9S9Uy9HHXDtvtu5es55OmjRZEWPUnkBjKRmY6hTPBmaO4veq7RiaNz7g==, tarball: file:projects/ai-bot.tgz}
|
||||
id: file:projects/ai-bot.tgz
|
||||
name: '@rush-temp/ai-bot'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint: 8.56.0
|
||||
eslint-config-prettier: 8.10.0(eslint@8.56.0)
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@types/node'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- esbuild
|
||||
- node-notifier
|
||||
- supports-color
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/analytics-collector-assets.tgz(esbuild@0.20.1)(svelte@4.2.12)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-ThEhCV2dJchrcM0mfUjNFdD0/qOzl3qC7glianWUl4Qsj03IrYyjbxvdsV7Fy/49UhQ+YUt46X39kgIWVBnWiw==, tarball: file:projects/analytics-collector-assets.tgz}
|
||||
id: file:projects/analytics-collector-assets.tgz
|
||||
@@ -26613,7 +26659,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/lead-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-YFe9zCBoR5pPRxsoG3Oc9qVVHEWGDlmQrw27HY+3P0SMBXz8xWVjgM6SrL2458y83t/Xsj30krK1zEZkWORfpQ==, tarball: file:projects/lead-resources.tgz}
|
||||
resolution: {integrity: sha512-DIL0rCz2r3nKYgE2fOrEdVSwLGq1Zz2MjsU1HvDxnIs4IE1Q4s8oNMBtvtunERIQ2fNnJHcE0XXijawtYilrjQ==, tarball: file:projects/lead-resources.tgz}
|
||||
id: file:projects/lead-resources.tgz
|
||||
name: '@rush-temp/lead-resources'
|
||||
version: 0.0.0
|
||||
@@ -27562,6 +27608,24 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/model-server-ai-bot.tgz:
|
||||
resolution: {integrity: sha512-eo56EIdT6JLS0upi95IDRPBEXyASWLEpUdH9cfK2lLtpslJYlJkRPSqImEA5xk1fUUwexRPqVPW+bYFoIMsUxQ==, tarball: file:projects/model-server-ai-bot.tgz}
|
||||
name: '@rush-temp/model-server-ai-bot'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
prettier: 3.2.5
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/model-server-attachment.tgz:
|
||||
resolution: {integrity: sha512-F45oxBvR4Gh7Y70CqozTOyXQ/lWOkP/EDURRVxxETMWbtXej37/QCtWXlLJ2YNWmoG6ildVMOZlK/IaMSdU9mA==, tarball: file:projects/model-server-attachment.tgz}
|
||||
name: '@rush-temp/model-server-attachment'
|
||||
@@ -28861,6 +28925,62 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/pod-ai-bot.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-5Upjw52FvxxuZY9eSN8N5FUKoNMhqME7DwEAp4TTQEKR/Rs+Fnxi7S5ZQ6G2G40RmZ0F0mWfXfWTcyhFlyio5Q==, tarball: file:projects/pod-ai-bot.tgz}
|
||||
id: file:projects/pod-ai-bot.tgz
|
||||
name: '@rush-temp/pod-ai-bot'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/cors': 2.8.17
|
||||
'@types/express': 4.17.21
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 20.11.19
|
||||
'@types/node-fetch': 2.6.11
|
||||
'@types/ws': 8.5.11
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
||||
cors: 2.8.5
|
||||
dotenv: 16.0.3
|
||||
esbuild: 0.20.1
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-node: 11.1.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
express: 4.19.2
|
||||
fast-equals: 5.0.1
|
||||
form-data: 4.0.0
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
mongodb: 6.8.0
|
||||
node-fetch: 2.7.0
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
|
||||
ts-node: 10.9.2(@types/node@20.11.19)(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@mongodb-js/zstd'
|
||||
- '@swc/core'
|
||||
- '@swc/wasm'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- bufferutil
|
||||
- encoding
|
||||
- gcp-metadata
|
||||
- kerberos
|
||||
- mongodb-client-encryption
|
||||
- node-notifier
|
||||
- snappy
|
||||
- socks
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
file:projects/pod-analytics-collector.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-Sx9mTOCsgDTcoC8h9VXeemVHerKLMDzdW4aJ08fdsF9UA35tLWmbDXw+fn3gPUNj2l+QT5ExEF9rV1Iez1FIbw==, tarball: file:projects/pod-analytics-collector.tgz}
|
||||
id: file:projects/pod-analytics-collector.tgz
|
||||
@@ -30590,6 +30710,74 @@ packages:
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/server-ai-bot-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-YOa1XOLVz87AjouR9Qn4Wkiib/9nXkIiMIW7XVzCl+lVqfdjXDRn4kjxXj363Wh8bw5Ato7kpuj0M9C1Vsh+zw==, tarball: file:projects/server-ai-bot-resources.tgz}
|
||||
id: file:projects/server-ai-bot-resources.tgz
|
||||
name: '@rush-temp/server-ai-bot-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
mongodb: 6.8.0
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
- '@aws-sdk/credential-providers'
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- '@mongodb-js/zstd'
|
||||
- '@types/node'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- esbuild
|
||||
- gcp-metadata
|
||||
- kerberos
|
||||
- mongodb-client-encryption
|
||||
- node-notifier
|
||||
- snappy
|
||||
- socks
|
||||
- supports-color
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/server-ai-bot.tgz(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-9rB0TFsDFJ2KgmJwTCJRQG/rh+NORXysidGZq8v77VEN7Sh/xQlB33k4XgHutF3U1Bd0KUdRC/pg9grlgezIdA==, tarball: file:projects/server-ai-bot.tgz}
|
||||
id: file:projects/server-ai-bot.tgz
|
||||
name: '@rush-temp/server-ai-bot'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@types/jest': 29.5.12
|
||||
'@types/node': 20.11.19
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint: 8.56.0
|
||||
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.3.3)
|
||||
eslint-plugin-import: 2.29.1(eslint@8.56.0)
|
||||
eslint-plugin-n: 15.7.0(eslint@8.56.0)
|
||||
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
prettier: 3.2.5
|
||||
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
|
||||
typescript: 5.3.3
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@jest/types'
|
||||
- babel-jest
|
||||
- babel-plugin-macros
|
||||
- esbuild
|
||||
- node-notifier
|
||||
- supports-color
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/server-analytics-collector-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-cLqoyrE7bZmjUeQSenTvg0S2XdEUUulUTMYvYephOUR5orzG71xy3Z+HgV92YapcZMzi9RVdaataHwYhxTRDPw==, tarball: file:projects/server-analytics-collector-resources.tgz}
|
||||
id: file:projects/server-analytics-collector-resources.tgz
|
||||
|
||||
+18
-1
@@ -213,7 +213,24 @@ services:
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 300M
|
||||
memory: 300M
|
||||
aiBot:
|
||||
image: hardcoreeng/ai-bot
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- SERVER_SECRET=secret
|
||||
- MONGO_URL=mongodb://mongodb:27017
|
||||
- ACCOUNTS_URL=http://account:3000
|
||||
- SUPPORT_WORKSPACE=support
|
||||
- FIRST_NAME=Jolie
|
||||
- LAST_NAME=AI
|
||||
- PASSWORD=password
|
||||
- AVATAR_PATH=./avatar.png
|
||||
- AVATAR_CONTENT_TYPE=.png
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 300M
|
||||
volumes:
|
||||
db:
|
||||
files:
|
||||
|
||||
@@ -133,6 +133,8 @@
|
||||
"@hcengineering/server-guest-resources": "^0.6.0",
|
||||
"@hcengineering/server-time": "^0.6.0",
|
||||
"@hcengineering/server-time-resources": "^0.6.0",
|
||||
"@hcengineering/server-ai-bot": "^0.6.0",
|
||||
"@hcengineering/server-ai-bot-resources": "^0.6.0",
|
||||
"@hcengineering/setting": "^0.6.17",
|
||||
"@hcengineering/tags": "^0.6.16",
|
||||
"@hcengineering/task": "^0.6.20",
|
||||
|
||||
@@ -44,6 +44,7 @@ import { serverTelegramId } from '@hcengineering/server-telegram'
|
||||
import { serverTimeId } from '@hcengineering/server-time'
|
||||
import { serverTrackerId } from '@hcengineering/server-tracker'
|
||||
import { serverViewId } from '@hcengineering/server-view'
|
||||
import { serverAiBotId } from '@hcengineering/server-ai-bot'
|
||||
|
||||
addLocation(serverActivityId, () => import('@hcengineering/server-activity-resources'))
|
||||
addLocation(serverAttachmentId, () => import('@hcengineering/server-attachment-resources'))
|
||||
@@ -68,6 +69,7 @@ addLocation(serverDocumentId, () => import('@hcengineering/server-document-resou
|
||||
addLocation(serverTimeId, () => import('@hcengineering/server-time-resources'))
|
||||
addLocation(serverGuestId, () => import('@hcengineering/server-guest-resources'))
|
||||
addLocation(serverDriveId, () => import('@hcengineering/server-drive-resources'))
|
||||
addLocation(serverAiBotId, () => import('@hcengineering/server-ai-bot-resources'))
|
||||
|
||||
function prepareTools (): {
|
||||
mongodbUri: string
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
"@hcengineering/model-server-products": "^0.1.0",
|
||||
"@hcengineering/model-server-training": "^0.1.0",
|
||||
"@hcengineering/model-server-controlled-documents": "^0.1.0",
|
||||
"@hcengineering/model-analytics-collector": "^0.6.0"
|
||||
"@hcengineering/model-analytics-collector": "^0.6.0",
|
||||
"@hcengineering/model-server-ai-bot": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ import { serverTelegramId, createModel as serverTelegramModel } from '@hcenginee
|
||||
import { serverTemplatesId, createModel as serverTemplatesModel } from '@hcengineering/model-server-templates'
|
||||
import { serverTrackerId, createModel as serverTrackerModel } from '@hcengineering/model-server-tracker'
|
||||
import { serverViewId, createModel as serverViewModel } from '@hcengineering/model-server-view'
|
||||
import { serverAiBotId, createModel as serverAiBotModel } from '@hcengineering/model-server-ai-bot'
|
||||
import setting, { settingId, createModel as settingModel } from '@hcengineering/model-setting'
|
||||
import { driveId, createModel as driveModel } from '@hcengineering/model-drive'
|
||||
import { supportId, createModel as supportModel } from '@hcengineering/model-support'
|
||||
@@ -424,7 +425,8 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
|
||||
[serverDriveModel, serverDriveId],
|
||||
[serverProductsModel, serverProductsId],
|
||||
[serverTrainingModel, serverTrainingId],
|
||||
[serverDocumentsModel, serverDocumentsId]
|
||||
[serverDocumentsModel, serverDocumentsId],
|
||||
[serverAiBotModel, serverAiBotId]
|
||||
]
|
||||
|
||||
for (const [b, id, config] of builders) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "model"
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@hcengineering/model-server-ai-bot",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"author": "Hardcore Engineering Inc.",
|
||||
"template": "@hcengineering/model-package",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/model": "^0.6.11",
|
||||
"@hcengineering/model-activity": "^0.6.0",
|
||||
"@hcengineering/model-chunter": "^0.6.0",
|
||||
"@hcengineering/model-contact": "^0.6.1",
|
||||
"@hcengineering/model-core": "^0.6.0",
|
||||
"@hcengineering/model-notification": "^0.6.0",
|
||||
"@hcengineering/model-presentation": "^0.6.0",
|
||||
"@hcengineering/model-view": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-ai-bot": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/view": "^0.6.13"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { type Builder, Mixin, Model, Prop, TypeRef, TypeString } from '@hcengineering/model'
|
||||
import core, { type Account, type Class, type Doc, type Domain, type Ref, type Space } from '@hcengineering/core'
|
||||
import serverCore from '@hcengineering/server-core'
|
||||
import serverAiBot from '@hcengineering/server-ai-bot'
|
||||
import { TDoc } from '@hcengineering/model-core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import aiBot, {
|
||||
type AIBotEvent,
|
||||
type AIBotTransferEvent,
|
||||
type AIBotResponseEvent,
|
||||
type TransferredMessage
|
||||
} from '@hcengineering/ai-bot'
|
||||
import chunter, { type ChatMessage } from '@hcengineering/chunter'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { TChatMessage } from '@hcengineering/model-chunter'
|
||||
|
||||
export { serverAiBotId } from '@hcengineering/server-ai-bot'
|
||||
|
||||
export const DOMAIN_AI_BOT = 'ai_bot' as Domain
|
||||
|
||||
@Model(aiBot.class.AIBotEvent, core.class.Doc, DOMAIN_AI_BOT)
|
||||
export class TAIBotEvent extends TDoc implements AIBotEvent {
|
||||
@Prop(TypeRef(chunter.class.ChatMessage), core.string.Class)
|
||||
messageClass!: Ref<Class<ChatMessage>>
|
||||
|
||||
@Prop(TypeString(), getEmbeddedLabel('Collection'))
|
||||
collection!: string
|
||||
|
||||
@Prop(TypeString(), getEmbeddedLabel('Message'))
|
||||
message!: string
|
||||
}
|
||||
|
||||
@Model(aiBot.class.AIBotResponseEvent, core.class.Doc)
|
||||
export class TAIBotResponseEvent extends TAIBotEvent implements AIBotResponseEvent {
|
||||
@Prop(TypeRef(core.class.Doc), core.string.Object)
|
||||
objectId!: Ref<Doc>
|
||||
|
||||
@Prop(TypeRef(core.class.Class), core.string.Class)
|
||||
objectClass!: Ref<Class<Doc>>
|
||||
|
||||
@Prop(TypeRef(core.class.Space), core.string.Space)
|
||||
objectSpace!: Ref<Space>
|
||||
|
||||
@Prop(TypeRef(core.class.Account), core.string.Account)
|
||||
user!: Ref<Account>
|
||||
}
|
||||
|
||||
@Model(aiBot.class.AIBotTransferEvent, aiBot.class.AIBotEvent)
|
||||
export class TAIBotTransferEvent extends TAIBotEvent implements AIBotTransferEvent {
|
||||
toEmail!: string
|
||||
toWorkspace!: string
|
||||
fromWorkspace!: string
|
||||
messageId!: Ref<ChatMessage>
|
||||
parentMessageId?: Ref<ChatMessage>
|
||||
}
|
||||
|
||||
@Mixin(aiBot.mixin.TransferredMessage, chunter.class.ChatMessage)
|
||||
export class TTransferredMessage extends TChatMessage implements TransferredMessage {
|
||||
messageId!: Ref<ChatMessage>
|
||||
parentMessageId?: Ref<ChatMessage>
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TAIBotEvent, TAIBotTransferEvent, TAIBotResponseEvent, TTransferredMessage)
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverAiBot.trigger.OnMessageSend,
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverAiBot.trigger.OnMention,
|
||||
txMatch: {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: notification.class.MentionInboxNotification
|
||||
},
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverAiBot.trigger.OnMessageNotified,
|
||||
txMatch: {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: notification.class.ActivityInboxNotification
|
||||
},
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverAiBot.trigger.OnUserStatus,
|
||||
txMatch: {
|
||||
objectClass: core.class.UserStatus
|
||||
},
|
||||
isAsync: true
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@hcengineering/ai-bot",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"types/**/*",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"author": "Hardcore Engineering Inc",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/view": "^0.6.13"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Account, Class, Doc, type Mixin, Ref, Space } from '@hcengineering/core'
|
||||
import type { Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { ChatMessage } from '@hcengineering/chunter'
|
||||
|
||||
export const aiBotId = 'ai-bot' as Plugin
|
||||
|
||||
export const aiBotAccountEmail = 'huly.ai.bot@hc.engineering'
|
||||
|
||||
export interface AIBotEvent extends Doc {
|
||||
collection: string
|
||||
messageClass: Ref<Class<ChatMessage>>
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface AIBotResponseEvent extends AIBotEvent {
|
||||
objectId: Ref<Doc>
|
||||
objectClass: Ref<Class<Doc>>
|
||||
objectSpace: Ref<Space>
|
||||
user: Ref<Account>
|
||||
}
|
||||
|
||||
export interface AIBotTransferEvent extends AIBotEvent {
|
||||
toEmail: string
|
||||
toWorkspace: string
|
||||
fromWorkspace: string
|
||||
messageId: Ref<ChatMessage>
|
||||
parentMessageId?: Ref<ChatMessage>
|
||||
}
|
||||
|
||||
export interface TransferredMessage extends ChatMessage {
|
||||
messageId: Ref<ChatMessage>
|
||||
parentMessageId?: Ref<ChatMessage>
|
||||
}
|
||||
|
||||
const aiBot = plugin(aiBotId, {
|
||||
class: {
|
||||
AIBotEvent: '' as Ref<Class<AIBotEvent>>,
|
||||
AIBotTransferEvent: '' as Ref<Class<AIBotTransferEvent>>,
|
||||
AIBotResponseEvent: '' as Ref<Class<AIBotResponseEvent>>
|
||||
},
|
||||
mixin: {
|
||||
TransferredMessage: '' as Ref<Mixin<TransferredMessage>>
|
||||
},
|
||||
account: {
|
||||
AIBot: '' as Ref<Account>
|
||||
}
|
||||
})
|
||||
|
||||
export default aiBot
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,8 @@
|
||||
"@hcengineering/analytics-service": "^0.6.0",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/server-notification": "^0.6.1"
|
||||
"@hcengineering/server-notification": "^0.6.1",
|
||||
"@hcengineering/server-ai-bot": "^0.6.0",
|
||||
"@hcengineering/server-ai-bot-resources": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import serverNotification from '@hcengineering/server-notification'
|
||||
import serverToken from '@hcengineering/server-token'
|
||||
import { serverFactories } from '@hcengineering/server-ws/src/factories'
|
||||
import { SplitLogger, configureAnalytics } from '@hcengineering/analytics-service'
|
||||
import serverAiBot from '@hcengineering/server-ai-bot'
|
||||
import { join } from 'path'
|
||||
import { start } from '.'
|
||||
const serverFactory = serverFactories[(process.env.SERVER_PROVIDER as string) ?? 'ws'] ?? serverFactories.ws
|
||||
@@ -50,6 +51,7 @@ setMetadata(notification.metadata.PushPublicKey, config.pushPublicKey)
|
||||
setMetadata(serverNotification.metadata.PushPrivateKey, config.pushPrivateKey)
|
||||
setMetadata(serverNotification.metadata.PushSubject, config.pushSubject)
|
||||
setMetadata(serverCore.metadata.ElasticIndexName, config.elasticIndexName)
|
||||
setMetadata(serverAiBot.metadata.SupportWorkspaceId, process.env.SUPPORT_WORKSPACE)
|
||||
|
||||
const shutdown = start(config.url, {
|
||||
fullTextUrl: config.elasticUrl,
|
||||
|
||||
@@ -22,6 +22,8 @@ import { type Token } from '@hcengineering/server-token'
|
||||
import { ClientSession, start as startJsonRpc, type ServerFactory, type Session } from '@hcengineering/server-ws'
|
||||
|
||||
import { createServerPipeline, registerServerPlugins, registerStringLoaders } from '@hcengineering/server-pipeline'
|
||||
import { serverAiBotId } from '@hcengineering/server-ai-bot'
|
||||
import { createAIBotAdapter } from '@hcengineering/server-ai-bot-resources'
|
||||
|
||||
registerStringLoaders()
|
||||
|
||||
@@ -53,7 +55,20 @@ export function start (
|
||||
|
||||
const externalStorage = buildStorageFromConfig(opt.storageConfig, dbUrl)
|
||||
|
||||
const pipelineFactory = createServerPipeline(metrics, dbUrl, { ...opt, externalStorage })
|
||||
const pipelineFactory = createServerPipeline(
|
||||
metrics,
|
||||
dbUrl,
|
||||
{ ...opt, externalStorage },
|
||||
{
|
||||
serviceAdapters: {
|
||||
[serverAiBotId]: {
|
||||
factory: createAIBotAdapter,
|
||||
db: '%ai-bot',
|
||||
url: dbUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
const sessionFactory = (token: Token, pipeline: Pipeline): Session => {
|
||||
if (token.extra?.mode === 'backup') {
|
||||
return new BackupClientSession(token, pipeline)
|
||||
|
||||
@@ -2091,6 +2091,31 @@
|
||||
"packageName": "@hcengineering/server-analytics-collector-resources",
|
||||
"projectFolder": "server-plugins/analytics-collector-resources",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/pod-ai-bot",
|
||||
"projectFolder": "services/ai-bot/pod-ai-bot",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/ai-bot",
|
||||
"projectFolder": "plugins/ai-bot",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/model-server-ai-bot",
|
||||
"projectFolder": "models/server-ai-bot",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/server-ai-bot",
|
||||
"projectFolder": "server-plugins/ai-bot",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/server-ai-bot-resources",
|
||||
"projectFolder": "server-plugins/ai-bot-resources",
|
||||
"shouldPublish": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1 @@
|
||||
tsdoc-metadata.json
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@hcengineering/server-ai-bot-resources",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"types/**/*",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"author": "Hardcore Engineering Inc.",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/mongo": "^0.6.1",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-activity-resources": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-templates": "^0.6.0",
|
||||
"@hcengineering/templates": "^0.6.11",
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/server-ai-bot": "^0.6.0",
|
||||
"@hcengineering/analytics-collector": "^0.6.0",
|
||||
"mongodb": "^6.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MeasureContext, WorkspaceId } from '@hcengineering/core'
|
||||
import { getMongoClient, MongoClientReference } from '@hcengineering/mongo'
|
||||
import { AIBotServiceAdapter, WorkspaceInfoRecord } from '@hcengineering/server-ai-bot'
|
||||
import { Collection, Db, MongoClient } from 'mongodb'
|
||||
|
||||
class AIBotAdapter implements AIBotServiceAdapter {
|
||||
private readonly workspacesInfoCollection: Collection<WorkspaceInfoRecord>
|
||||
private readonly db: Db
|
||||
closed = false
|
||||
|
||||
constructor (
|
||||
private readonly _client: MongoClientReference,
|
||||
private readonly client: MongoClient,
|
||||
private readonly _metrics: MeasureContext,
|
||||
private readonly dbName: string
|
||||
) {
|
||||
this.db = client.db(dbName)
|
||||
this.workspacesInfoCollection = this.db.collection<WorkspaceInfoRecord>('workspacesInfo')
|
||||
}
|
||||
|
||||
async processWorkspace (workspace: WorkspaceId): Promise<void> {
|
||||
if (this.closed) {
|
||||
return
|
||||
}
|
||||
const existsRecord = await this.workspacesInfoCollection.findOne({
|
||||
workspace: workspace.name,
|
||||
productId: workspace.productId
|
||||
})
|
||||
|
||||
if (existsRecord != null && !existsRecord.active) {
|
||||
await this.workspacesInfoCollection.updateOne(
|
||||
{ workspace: workspace.name, productId: workspace.productId },
|
||||
{ $set: { active: true } }
|
||||
)
|
||||
} else if (existsRecord == null) {
|
||||
const record: WorkspaceInfoRecord = {
|
||||
workspace: workspace.name,
|
||||
productId: workspace.productId,
|
||||
active: true
|
||||
}
|
||||
|
||||
await this.workspacesInfoCollection.insertOne(record)
|
||||
}
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
this.closed = true
|
||||
this._client.close()
|
||||
}
|
||||
|
||||
metrics (): MeasureContext {
|
||||
return this._metrics
|
||||
}
|
||||
}
|
||||
|
||||
export async function createAIBotAdapter (url: string, db: string, metrics: MeasureContext): Promise<any> {
|
||||
const _client = getMongoClient(url)
|
||||
|
||||
return new AIBotAdapter(_client, await _client.getClient(), metrics, db)
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, {
|
||||
AccountRole,
|
||||
AttachedDoc,
|
||||
Data,
|
||||
Doc,
|
||||
Ref,
|
||||
toWorkspaceString,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxProcessor,
|
||||
TxUpdateDoc,
|
||||
UserStatus
|
||||
} from '@hcengineering/core'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import chunter, { ChatMessage, DirectMessage, ThreadMessage } from '@hcengineering/chunter'
|
||||
import aiBot, { aiBotAccountEmail, AIBotResponseEvent } from '@hcengineering/ai-bot'
|
||||
import serverAIBot, { AIBotServiceAdapter, serverAiBotId } from '@hcengineering/server-ai-bot'
|
||||
import contact, { PersonAccount } from '@hcengineering/contact'
|
||||
import { ActivityInboxNotification, MentionInboxNotification } from '@hcengineering/notification'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import analytics from '@hcengineering/analytics-collector'
|
||||
|
||||
async function processWorkspace (control: TriggerControl): Promise<void> {
|
||||
const adapter = control.serviceAdaptersManager.getAdapter(serverAiBotId) as AIBotServiceAdapter | undefined
|
||||
|
||||
if (adapter !== undefined) {
|
||||
await adapter.processWorkspace(control.workspace)
|
||||
} else {
|
||||
console.error('Cannot find server adapter: ', serverAiBotId)
|
||||
}
|
||||
}
|
||||
|
||||
async function isDirectAvailable (direct: DirectMessage, control: TriggerControl): Promise<boolean> {
|
||||
const { members } = direct
|
||||
|
||||
if (!members.includes(aiBot.account.AIBot)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const personAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
|
||||
_id: { $in: members as Ref<PersonAccount>[] }
|
||||
})
|
||||
const persons = new Set(personAccounts.map((account) => account.person))
|
||||
|
||||
return persons.size === 2
|
||||
}
|
||||
|
||||
async function isAvailableDoc (doc: Doc, control: TriggerControl): Promise<boolean> {
|
||||
if (doc._class === chunter.class.DirectMessage) {
|
||||
return await isDirectAvailable(doc as DirectMessage, control)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async function getMessageDoc (message: ChatMessage, control: TriggerControl): Promise<Doc | undefined> {
|
||||
if (control.hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) {
|
||||
const thread = message as ThreadMessage
|
||||
const _id = thread.objectId
|
||||
const _class = thread.objectClass
|
||||
|
||||
return (await control.findAll(_class, { _id }))[0]
|
||||
} else {
|
||||
const _id = message.attachedTo
|
||||
const _class = message.attachedToClass
|
||||
|
||||
return (await control.findAll(_class, { _id }))[0]
|
||||
}
|
||||
}
|
||||
|
||||
async function getMessageData (
|
||||
doc: Doc,
|
||||
message: ChatMessage,
|
||||
control: TriggerControl
|
||||
): Promise<Data<AIBotResponseEvent> | undefined> {
|
||||
if (!(await isAvailableDoc(doc, control))) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
objectId: message.attachedTo,
|
||||
objectClass: message.attachedToClass,
|
||||
objectSpace: doc.space,
|
||||
collection: message.collection,
|
||||
messageClass: message._class,
|
||||
message: message.message,
|
||||
user: message.createdBy ?? message.modifiedBy
|
||||
}
|
||||
}
|
||||
|
||||
async function getThreadMessageData (
|
||||
doc: Doc,
|
||||
message: ThreadMessage,
|
||||
control: TriggerControl
|
||||
): Promise<Data<AIBotResponseEvent> | undefined> {
|
||||
if (!(await isAvailableDoc(doc, control))) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
objectId: message.attachedTo,
|
||||
objectClass: message.attachedToClass,
|
||||
objectSpace: message.space,
|
||||
collection: message.collection,
|
||||
messageClass: message._class,
|
||||
message: message.message,
|
||||
user: message.createdBy ?? message.modifiedBy
|
||||
}
|
||||
}
|
||||
|
||||
// Note: temporally commented until open ai will be added
|
||||
// async function createAIBotEvent (message: ChatMessage, control: TriggerControl): Promise<Tx[]> {
|
||||
// const doc = await getMessageDoc(message, control)
|
||||
//
|
||||
// if (doc === undefined) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// let data: Data<AIBotResponseEvent> | undefined
|
||||
//
|
||||
// if (control.hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) {
|
||||
// data = await getThreadMessageData(doc, message as ThreadMessage, control)
|
||||
// } else {
|
||||
// data = await getMessageData(doc, message, control)
|
||||
// }
|
||||
//
|
||||
// if (data === undefined) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// const eventTx = control.txFactory.createTxCreateDoc(aiBot.class.AIBotResponseEvent, message.space, data)
|
||||
//
|
||||
// await processWorkspace(control)
|
||||
//
|
||||
// return [eventTx]
|
||||
// return []
|
||||
// }
|
||||
|
||||
async function getThreadParent (control: TriggerControl, message: ChatMessage): Promise<Ref<ChatMessage> | undefined> {
|
||||
if (!control.hierarchy.isDerived(message.attachedToClass, chunter.class.ChatMessage)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const parentInfo = (
|
||||
await control.findAll(message.attachedToClass, {
|
||||
_id: message.attachedTo as Ref<ChatMessage>,
|
||||
[aiBot.mixin.TransferredMessage]: { $exists: true }
|
||||
})
|
||||
)[0]
|
||||
|
||||
if (parentInfo !== undefined) {
|
||||
return control.hierarchy.as(parentInfo, aiBot.mixin.TransferredMessage).messageId
|
||||
}
|
||||
|
||||
return message.attachedTo as Ref<ChatMessage>
|
||||
}
|
||||
|
||||
function getSupportWorkspaceId (): string | undefined {
|
||||
const supportWorkspaceId = getMetadata(serverAIBot.metadata.SupportWorkspaceId)
|
||||
|
||||
if (supportWorkspaceId === '') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return supportWorkspaceId
|
||||
}
|
||||
|
||||
async function onBotDirectMessageSend (control: TriggerControl, message: ChatMessage): Promise<Tx[]> {
|
||||
const supportWorkspaceId = getSupportWorkspaceId()
|
||||
|
||||
if (supportWorkspaceId === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const direct = (await getMessageDoc(message, control)) as DirectMessage
|
||||
|
||||
if (direct === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const isAvailable = await isDirectAvailable(direct, control)
|
||||
|
||||
if (!isAvailable) {
|
||||
return []
|
||||
}
|
||||
|
||||
const account = control.modelDb.findAllSync(contact.class.PersonAccount, {
|
||||
_id: (message.createdBy ?? message.modifiedBy) as Ref<PersonAccount>
|
||||
})[0]
|
||||
|
||||
if (account === undefined || account.role !== AccountRole.Owner) {
|
||||
return []
|
||||
}
|
||||
|
||||
let data: Data<AIBotResponseEvent> | undefined
|
||||
|
||||
if (control.hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) {
|
||||
data = await getThreadMessageData(direct, message as ThreadMessage, control)
|
||||
} else {
|
||||
data = await getMessageData(direct, message, control)
|
||||
}
|
||||
|
||||
if (data === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const eventTx = control.txFactory.createTxCreateDoc(aiBot.class.AIBotTransferEvent, message.space, {
|
||||
messageClass: data.messageClass,
|
||||
message: message.message,
|
||||
collection: data.collection,
|
||||
toWorkspace: supportWorkspaceId,
|
||||
toEmail: account.email,
|
||||
fromWorkspace: toWorkspaceString(control.workspace),
|
||||
messageId: message._id,
|
||||
parentMessageId: await getThreadParent(control, message)
|
||||
})
|
||||
|
||||
await processWorkspace(control)
|
||||
|
||||
return [eventTx]
|
||||
}
|
||||
|
||||
async function onSupportWorkspaceMessage (control: TriggerControl, message: ChatMessage): Promise<Tx[]> {
|
||||
const supportWorkspaceId = getSupportWorkspaceId()
|
||||
|
||||
if (supportWorkspaceId === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (toWorkspaceString(control.workspace) !== supportWorkspaceId) {
|
||||
return []
|
||||
}
|
||||
|
||||
const channel = await getMessageDoc(message, control)
|
||||
|
||||
if (channel === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (!control.hierarchy.hasMixin(channel, analytics.mixin.AnalyticsChannel)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const mixin = control.hierarchy.as(channel, analytics.mixin.AnalyticsChannel)
|
||||
const { workspace, email } = mixin
|
||||
|
||||
let data: Data<AIBotResponseEvent> | undefined
|
||||
|
||||
if (control.hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) {
|
||||
data = await getThreadMessageData(channel, message as ThreadMessage, control)
|
||||
} else {
|
||||
data = await getMessageData(channel, message, control)
|
||||
}
|
||||
|
||||
if (data === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
await processWorkspace(control)
|
||||
|
||||
return [
|
||||
control.txFactory.createTxCreateDoc(aiBot.class.AIBotTransferEvent, message.space, {
|
||||
messageClass: data.messageClass,
|
||||
message: message.message,
|
||||
collection: data.collection,
|
||||
toEmail: email,
|
||||
toWorkspace: workspace,
|
||||
fromWorkspace: toWorkspaceString(control.workspace),
|
||||
messageId: message._id,
|
||||
parentMessageId: await getThreadParent(control, message)
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
export async function OnMessageSend (
|
||||
originTx: TxCollectionCUD<Doc, AttachedDoc>,
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const { hierarchy } = control
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCreateDoc<ChatMessage>
|
||||
if (tx._class !== core.class.TxCreateDoc || !hierarchy.isDerived(tx.objectClass, chunter.class.ChatMessage)) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (tx.modifiedBy === aiBot.account.AIBot || tx.modifiedBy === core.account.System) {
|
||||
return []
|
||||
}
|
||||
|
||||
const isThread = hierarchy.isDerived(tx.objectClass, chunter.class.ThreadMessage)
|
||||
const message = TxProcessor.createDoc2Doc(tx)
|
||||
|
||||
const docClass = isThread ? (message as ThreadMessage).objectClass : message.attachedToClass
|
||||
|
||||
if (!hierarchy.isDerived(docClass, chunter.class.ChunterSpace)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
|
||||
if (docClass === chunter.class.DirectMessage) {
|
||||
const txes = await onBotDirectMessageSend(control, message)
|
||||
res.push(...txes)
|
||||
}
|
||||
|
||||
if (docClass === chunter.class.Channel) {
|
||||
const txes = await onSupportWorkspaceMessage(control, message)
|
||||
res.push(...txes)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export async function OnMention (tx: TxCreateDoc<MentionInboxNotification>, control: TriggerControl): Promise<Tx[]> {
|
||||
// Note: temporally commented until open ai will be added
|
||||
// if (tx.objectClass !== notification.class.MentionInboxNotification || tx._class !== core.class.TxCreateDoc) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// const mention = TxProcessor.createDoc2Doc(tx)
|
||||
//
|
||||
// if (mention.user !== aiBot.account.AIBot) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// if (!control.hierarchy.isDerived(mention.mentionedInClass, chunter.class.ChatMessage)) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// const message = (
|
||||
// await control.findAll<ChatMessage>(mention.mentionedInClass, { _id: mention.mentionedIn as Ref<ChatMessage> })
|
||||
// )[0]
|
||||
//
|
||||
// if (message === undefined) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// await createAIBotEvent(message, control)
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnMessageNotified (
|
||||
tx: TxCreateDoc<ActivityInboxNotification>,
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
// Note: temporally commented until open ai will be added
|
||||
// if (tx.objectClass !== notification.class.ActivityInboxNotification || tx._class !== core.class.TxCreateDoc) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// const doc = TxProcessor.createDoc2Doc(tx)
|
||||
//
|
||||
// if (doc.user !== aiBot.account.AIBot) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// if (!control.hierarchy.isDerived(doc.attachedToClass, chunter.class.ChatMessage)) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// const personAccount = await control.modelDb.findOne(contact.class.PersonAccount, { email: aiBotAccountEmail })
|
||||
//
|
||||
// if (personAccount === undefined) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// const message = (
|
||||
// await control.findAll<ChatMessage>(doc.attachedToClass, { _id: doc.attachedTo as Ref<ChatMessage> })
|
||||
// )[0]
|
||||
//
|
||||
// if (message === undefined) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// if (isDocMentioned(personAccount.person, message.message)) {
|
||||
// return await createAIBotEvent(message, control)
|
||||
// }
|
||||
//
|
||||
// if (!control.hierarchy.isDerived(message._class, chunter.class.ThreadMessage)) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// const thread = message as ThreadMessage
|
||||
// TODO: do we really need to find parent???
|
||||
// const parent = (await control.findAll(thread.attachedToClass, { _id: thread.attachedTo }))[0]
|
||||
//
|
||||
// if (parent === undefined) {
|
||||
// return []
|
||||
// }
|
||||
//
|
||||
// if (parent.createdBy === aiBot.account.AIBot) {
|
||||
// return await createAIBotEvent(message, control)
|
||||
// }
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnUserStatus (originTx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
||||
|
||||
if (
|
||||
tx.objectClass !== core.class.UserStatus ||
|
||||
![core.class.TxCreateDoc, core.class.TxUpdateDoc].includes(tx._class)
|
||||
) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (tx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = tx as TxUpdateDoc<UserStatus>
|
||||
const val = updateTx.operations.online
|
||||
if (val !== true) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const account = control.modelDb.findAllSync(contact.class.PersonAccount, { email: aiBotAccountEmail })[0]
|
||||
|
||||
if (account !== undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
await processWorkspace(control)
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
OnMessageSend,
|
||||
OnMention,
|
||||
OnMessageNotified,
|
||||
OnUserStatus
|
||||
}
|
||||
})
|
||||
|
||||
export * from './adapter'
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@hcengineering/server-ai-bot",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"types/**/*",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"author": "Hardcore Engineering Inc.",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"format": "format src",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "~20.11.16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/server-core": "^0.6.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Metadata, Plugin, Resource } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { TriggerFunc } from '@hcengineering/server-core'
|
||||
|
||||
export * from './types'
|
||||
export const serverAiBotId = 'server-ai-bot' as Plugin
|
||||
|
||||
export default plugin(serverAiBotId, {
|
||||
metadata: {
|
||||
SupportWorkspaceId: '' as Metadata<string>
|
||||
},
|
||||
trigger: {
|
||||
OnMessageSend: '' as Resource<TriggerFunc>,
|
||||
OnMention: '' as Resource<TriggerFunc>,
|
||||
OnUserStatus: '' as Resource<TriggerFunc>,
|
||||
OnMessageNotified: '' as Resource<TriggerFunc>
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { ServiceAdapter } from '@hcengineering/server-core'
|
||||
import { WorkspaceId } from '@hcengineering/core'
|
||||
|
||||
export interface AIBotServiceAdapter extends ServiceAdapter {
|
||||
processWorkspace: (workspace: WorkspaceId) => Promise<void>
|
||||
}
|
||||
|
||||
export interface WorkspaceInfoRecord {
|
||||
workspace: string
|
||||
productId: string
|
||||
active: boolean
|
||||
avatarPath?: string
|
||||
avatarLastModified?: number
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -172,6 +172,8 @@
|
||||
"@hcengineering/products": "^0.1.0",
|
||||
"@hcengineering/training": "^0.1.0",
|
||||
"@hcengineering/github": "^0.6.0",
|
||||
"@hcengineering/github-assets": "^0.6.0"
|
||||
"@hcengineering/github-assets": "^0.6.0",
|
||||
"@hcengineering/server-ai-bot": "^0.6.0",
|
||||
"@hcengineering/server-ai-bot-resources": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import { serverTimeId } from '@hcengineering/server-time'
|
||||
import { serverTrackerId } from '@hcengineering/server-tracker'
|
||||
import { serverTrainingId } from '@hcengineering/server-training'
|
||||
import { serverViewId } from '@hcengineering/server-view'
|
||||
import { serverAiBotId } from '@hcengineering/server-ai-bot'
|
||||
|
||||
export function registerServerPlugins (): void {
|
||||
addLocation(serverActivityId, () => import('@hcengineering/server-activity-resources'))
|
||||
@@ -57,4 +58,5 @@ export function registerServerPlugins (): void {
|
||||
addLocation(serverDocumentsId, () => import('@hcengineering/server-controlled-documents-resources'))
|
||||
addLocation(serverTrainingId, () => import('@hcengineering/server-training-resources'))
|
||||
addLocation(serverGithubId, () => import('@hcengineering/server-github-resources'))
|
||||
addLocation(serverAiBotId, () => import('@hcengineering/server-ai-bot-resources'))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY bundle/bundle.js ./
|
||||
COPY assets/avatar.png ./
|
||||
|
||||
EXPOSE 4008
|
||||
CMD [ "node", "bundle.js" ]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
|
||||
roots: ["./src"],
|
||||
coverageReporters: ["text-summary", "html"]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "@hcengineering/pod-ai-bot",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"svelte": "src/index.ts",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*",
|
||||
"types/**/*",
|
||||
"tsconfig.json"
|
||||
],
|
||||
"author": "Hardcore Engineering Inc.",
|
||||
"scripts": {
|
||||
"build": "compile",
|
||||
"build:watch": "compile",
|
||||
"test": "jest --passWithNoTests --silent",
|
||||
"_phase:bundle": "rushx bundle",
|
||||
"_phase:docker-build": "rushx docker:build",
|
||||
"_phase:docker-staging": "rushx docker:staging",
|
||||
"bundle": "mkdir -p bundle && esbuild src/index.ts --bundle --platform=node > bundle/bundle.js",
|
||||
"docker:build": "../../../common/scripts/docker_build.sh hardcoreeng/ai-bot",
|
||||
"docker:staging": "../../../common/scripts/docker_tag.sh hardcoreeng/ai-bot staging",
|
||||
"docker:push": "../../../common/scripts/docker_tag.sh hardcoreeng/ai-bot",
|
||||
"docker:tbuild": "rush bundle --to @hcengineering/pod-ai-bot && docker build -t hardcoreeng/ai-bot . --platform=linux/amd64 && ../../../common/scripts/docker_tag_push.sh hardcoreeng/ai-bot",
|
||||
"run-local": "cross-env APP_ID=$(cat ../../../../uberflow_private/appid) PRIVATE_KEY=\"$(cat ../../../../uberflow_private/private-key.pem)\" CLIENT_ID=$(cat ../../../../uberflow_private/client-id) CLIENT_SECRET=$(cat ../../../../uberflow_private/client-secret) SERVER_SECRET=secret ACCOUNTS_URL=http://localhost:3000/ COLLABORATOR_URL=http://localhost:3078 COLLABORATOR_API_URL=http://localhost:3078 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_ENDPOINT=localhost ts-node src/index.ts",
|
||||
"format": "format src",
|
||||
"_phase:build": "compile transpile src",
|
||||
"_phase:test": "jest --passWithNoTests --silent",
|
||||
"_phase:format": "format src",
|
||||
"_phase:validate": "compile validate"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "^0.6.0",
|
||||
"@tsconfig/node16": "^1.0.4",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "^29.5.5",
|
||||
"@types/node": "~20.11.16",
|
||||
"@types/node-fetch": "~2.6.2",
|
||||
"@types/ws": "^8.5.11",
|
||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
||||
"@typescript-eslint/parser": "^6.11.0",
|
||||
"esbuild": "^0.20.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-standard-with-typescript": "^40.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.8.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/account": "^0.6.0",
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/analytics-collector": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
"@hcengineering/client": "^0.6.18",
|
||||
"@hcengineering/client-resources": "^0.6.27",
|
||||
"@hcengineering/contact": "^0.6.24",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/mongo": "^0.6.1",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/server-ai-bot": "^0.6.0",
|
||||
"@hcengineering/server-analytics-collector-resources": "^0.6.0",
|
||||
"@hcengineering/server-client": "^0.6.0",
|
||||
"@hcengineering/server-core": "^0.6.1",
|
||||
"@hcengineering/server-token": "^0.6.11",
|
||||
"@hcengineering/server-ws": "^0.6.11",
|
||||
"@hcengineering/setting": "^0.6.17",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "~16.0.0",
|
||||
"express": "^4.19.2",
|
||||
"fast-equals": "^5.0.1",
|
||||
"form-data": "^4.0.0",
|
||||
"mongodb": "^6.8.0",
|
||||
"node-fetch": "^2.6.6",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { LoginInfo, Workspace, WorkspaceLoginInfo } from '@hcengineering/account'
|
||||
import aiBot, { aiBotAccountEmail } from '@hcengineering/ai-bot'
|
||||
import { AccountRole, WorkspaceId } from '@hcengineering/core'
|
||||
|
||||
import config from './config'
|
||||
|
||||
export async function assignBotToWorkspace (workspaceId: WorkspaceId): Promise<Workspace> {
|
||||
const accountsUrl = config.AccountsURL
|
||||
const workspace = await (
|
||||
await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method: 'assignWorkspace',
|
||||
params: [
|
||||
aiBotAccountEmail,
|
||||
workspaceId.name,
|
||||
AccountRole.User,
|
||||
undefined,
|
||||
false,
|
||||
undefined,
|
||||
aiBot.account.AIBot
|
||||
]
|
||||
})
|
||||
})
|
||||
).json()
|
||||
|
||||
return workspace.result as Workspace
|
||||
}
|
||||
|
||||
export async function createBotAccount (): Promise<Workspace> {
|
||||
const accountsUrl = config.AccountsURL
|
||||
const workspace = await (
|
||||
await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method: 'createAccount',
|
||||
params: [aiBotAccountEmail, config.Password, config.FirstName, config.LastName]
|
||||
})
|
||||
})
|
||||
).json()
|
||||
|
||||
return workspace.result as Workspace
|
||||
}
|
||||
|
||||
export async function loginBot (): Promise<LoginInfo | undefined> {
|
||||
const accountsUrl = config.AccountsURL
|
||||
const workspace = await (
|
||||
await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method: 'login',
|
||||
params: [aiBotAccountEmail, config.Password]
|
||||
})
|
||||
})
|
||||
).json()
|
||||
|
||||
return workspace.result as LoginInfo | undefined
|
||||
}
|
||||
|
||||
export async function getWorkspaceInfo (token: string): Promise<WorkspaceLoginInfo> {
|
||||
const accountsUrl = config.AccountsURL
|
||||
const workspaceInfo = await (
|
||||
await fetch(accountsUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
method: 'getWorkspaceInfo',
|
||||
params: []
|
||||
})
|
||||
})
|
||||
).json()
|
||||
|
||||
return workspaceInfo.result as WorkspaceLoginInfo
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
interface Config {
|
||||
AccountsURL: string
|
||||
ConfigurationDB: string
|
||||
MongoURL: string
|
||||
ProductID: string
|
||||
ServerSecret: string
|
||||
ServiceID: string
|
||||
SupportWorkspace: string
|
||||
FirstName: string
|
||||
LastName: string
|
||||
AvatarPath: string
|
||||
AvatarName: string
|
||||
AvatarContentType: string
|
||||
Password: string
|
||||
}
|
||||
|
||||
const envMap: { [key in keyof Config]: string } = {
|
||||
AccountsURL: 'ACCOUNTS_URL',
|
||||
ConfigurationDB: 'CONFIGURATION_DB',
|
||||
MongoURL: 'MONGO_URL',
|
||||
ProductID: 'PRODUCT_ID',
|
||||
ServerSecret: 'SERVER_SECRET',
|
||||
ServiceID: 'SERVICE_ID',
|
||||
SupportWorkspace: 'SUPPORT_WORKSPACE',
|
||||
FirstName: 'FIRST_NAME',
|
||||
LastName: 'LAST_NAME',
|
||||
AvatarPath: 'AVATAR_PATH',
|
||||
AvatarName: 'AVATAR_NAME',
|
||||
AvatarContentType: 'AVATAR_CONTENT_TYPE',
|
||||
Password: 'PASSWORD'
|
||||
}
|
||||
|
||||
const config: Config = (() => {
|
||||
const params: Partial<Config> = {
|
||||
AccountsURL: process.env[envMap.AccountsURL],
|
||||
ConfigurationDB: process.env[envMap.ConfigurationDB] ?? '%ai-bot',
|
||||
MongoURL: process.env[envMap.MongoURL],
|
||||
ProductID: process.env[envMap.ProductID] ?? '',
|
||||
ServerSecret: process.env[envMap.ServerSecret],
|
||||
ServiceID: process.env[envMap.ServiceID] ?? 'ai-bot-service',
|
||||
SupportWorkspace: process.env[envMap.SupportWorkspace],
|
||||
FirstName: process.env[envMap.FirstName],
|
||||
LastName: process.env[envMap.LastName],
|
||||
AvatarPath: process.env[envMap.AvatarPath] ?? './assets/avatar.png',
|
||||
AvatarName: process.env[envMap.AvatarName] ?? 'huly_ai_bot_avatar',
|
||||
AvatarContentType: process.env[envMap.AvatarContentType] ?? '.png',
|
||||
Password: process.env[envMap.Password] ?? 'password'
|
||||
}
|
||||
|
||||
const missingEnv = (Object.keys(params) as Array<keyof Config>)
|
||||
.filter((key) => params[key] === undefined)
|
||||
.map((key) => envMap[key])
|
||||
|
||||
if (missingEnv.length > 0) {
|
||||
throw Error(`Missing env variables: ${missingEnv.join(', ')}`)
|
||||
}
|
||||
|
||||
return params as Config
|
||||
})()
|
||||
|
||||
export default config
|
||||
@@ -0,0 +1,216 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Db, Collection } from 'mongodb'
|
||||
import { getWorkspaceId, MeasureContext, systemAccountEmail, toWorkspaceString, WorkspaceId } from '@hcengineering/core'
|
||||
import { aiBotAccountEmail, AIBotTransferEvent } from '@hcengineering/ai-bot'
|
||||
import { WorkspaceInfoRecord } from '@hcengineering/server-ai-bot'
|
||||
import { getTransactorEndpoint } from '@hcengineering/server-client'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
import { WorkspaceLoginInfo } from '@hcengineering/account'
|
||||
|
||||
import { WorkspaceClient } from './workspaceClient'
|
||||
import { assignBotToWorkspace, getWorkspaceInfo } from './account'
|
||||
|
||||
const POLLING_INTERVAL_MS = 5 * 1000 // 5 seconds
|
||||
const CLOSE_INTERVAL_MS = 10 * 60 * 1000 // 10 minutes
|
||||
const ASSIGN_WORKSPACE_DELAY_MS = 5 * 1000 // 5 secs
|
||||
const MAX_ASSIGN_ATTEMPTS = 5
|
||||
|
||||
export class AIBotController {
|
||||
private readonly workspaces: Map<string, WorkspaceClient> = new Map<string, WorkspaceClient>()
|
||||
private readonly closeWorkspaceTimeouts: Map<string, NodeJS.Timeout> = new Map<string, NodeJS.Timeout>()
|
||||
|
||||
private readonly db: Db
|
||||
private readonly ctx: MeasureContext
|
||||
private readonly workspacesInfoCollection: Collection<WorkspaceInfoRecord>
|
||||
|
||||
private readonly intervalId: NodeJS.Timeout
|
||||
|
||||
assignTimeout: NodeJS.Timeout | undefined
|
||||
assignAttempts = 0
|
||||
|
||||
constructor (mongoDb: Db, ctx: MeasureContext) {
|
||||
this.db = mongoDb
|
||||
this.ctx = ctx
|
||||
this.workspacesInfoCollection = this.db.collection<WorkspaceInfoRecord>('workspacesInfo')
|
||||
|
||||
this.intervalId = setInterval(() => {
|
||||
void this.updateWorkspaceClients()
|
||||
}, POLLING_INTERVAL_MS)
|
||||
}
|
||||
|
||||
async updateWorkspaceClients (): Promise<void> {
|
||||
const activeRecords = await this.workspacesInfoCollection.find({ active: true }).toArray()
|
||||
|
||||
for (const record of activeRecords) {
|
||||
const id: WorkspaceId = { name: record.workspace, productId: record.productId }
|
||||
|
||||
if (this.workspaces.has(toWorkspaceString(id))) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.initWorkspaceClient(id, record)
|
||||
}
|
||||
}
|
||||
|
||||
async closeWorkspaceClient (workspaceId: WorkspaceId): Promise<void> {
|
||||
const workspace = toWorkspaceString(workspaceId)
|
||||
|
||||
this.ctx.info('Closing workspace client: ', { workspace })
|
||||
|
||||
const timeoutId = this.closeWorkspaceTimeouts.get(workspace)
|
||||
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId)
|
||||
this.closeWorkspaceTimeouts.delete(workspace)
|
||||
}
|
||||
|
||||
await this.workspacesInfoCollection.updateOne(
|
||||
{ workspace: workspaceId.name, productId: workspaceId.productId },
|
||||
{ $set: { active: false } }
|
||||
)
|
||||
|
||||
const client = this.workspaces.get(workspace)
|
||||
|
||||
if (client !== undefined) {
|
||||
await client.close()
|
||||
this.workspaces.delete(workspace)
|
||||
}
|
||||
}
|
||||
|
||||
private async getWorkspaceInfo (ws: WorkspaceId): Promise<WorkspaceLoginInfo | undefined> {
|
||||
const systemToken = generateToken(systemAccountEmail, ws)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
try {
|
||||
const info = await getWorkspaceInfo(systemToken)
|
||||
|
||||
if (info == null) {
|
||||
this.ctx.warn('Cannot find workspace info', ws)
|
||||
await wait(ASSIGN_WORKSPACE_DELAY_MS)
|
||||
continue
|
||||
}
|
||||
|
||||
return info
|
||||
} catch (e) {
|
||||
this.ctx.error('Error during get workspace info:', { e })
|
||||
await wait(ASSIGN_WORKSPACE_DELAY_MS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async assignToWorkspace (ws: WorkspaceId): Promise<void> {
|
||||
clearTimeout(this.assignTimeout)
|
||||
try {
|
||||
const info = await this.getWorkspaceInfo(ws)
|
||||
|
||||
if (info === undefined) {
|
||||
void this.closeWorkspaceClient(ws)
|
||||
return
|
||||
}
|
||||
|
||||
if (info.creating === true) {
|
||||
this.ctx.info('Workspace is creating -> waiting...', ws)
|
||||
this.assignTimeout = setTimeout(() => {
|
||||
void this.assignToWorkspace(ws)
|
||||
}, ASSIGN_WORKSPACE_DELAY_MS)
|
||||
return
|
||||
}
|
||||
|
||||
const result = await assignBotToWorkspace(ws)
|
||||
this.ctx.info('Assign to workspace result: ', { result, workspace: ws.name })
|
||||
} catch (e) {
|
||||
this.ctx.error('Error during assign workspace:', { e })
|
||||
if (this.assignAttempts < MAX_ASSIGN_ATTEMPTS) {
|
||||
this.assignAttempts++
|
||||
this.assignTimeout = setTimeout(() => {
|
||||
void this.assignToWorkspace(ws)
|
||||
}, ASSIGN_WORKSPACE_DELAY_MS)
|
||||
} else {
|
||||
void this.closeWorkspaceClient(ws)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async initWorkspaceClient (workspaceId: WorkspaceId, info: WorkspaceInfoRecord): Promise<void> {
|
||||
const workspace = toWorkspaceString(workspaceId)
|
||||
|
||||
if (!this.workspaces.has(workspace)) {
|
||||
this.ctx.info('Listen workspace: ', { workspace })
|
||||
await this.assignToWorkspace(workspaceId)
|
||||
const token = generateToken(aiBotAccountEmail, workspaceId)
|
||||
const endpoint = await getTransactorEndpoint(token)
|
||||
this.workspaces.set(
|
||||
workspace,
|
||||
new WorkspaceClient(endpoint, token, workspaceId, this, this.ctx.newChild(workspace, {}), info)
|
||||
)
|
||||
}
|
||||
|
||||
const timeoutId = this.closeWorkspaceTimeouts.get(workspace)
|
||||
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
|
||||
const newTimeoutId = setTimeout(() => {
|
||||
void this.closeWorkspaceClient(workspaceId)
|
||||
}, CLOSE_INTERVAL_MS)
|
||||
|
||||
this.closeWorkspaceTimeouts.set(workspace, newTimeoutId)
|
||||
}
|
||||
|
||||
async transfer (event: AIBotTransferEvent): Promise<void> {
|
||||
const workspaceId = getWorkspaceId(event.toWorkspace)
|
||||
const info = await this.workspacesInfoCollection
|
||||
.find({ workspace: workspaceId.name, productId: workspaceId.productId })
|
||||
.toArray()
|
||||
|
||||
await this.initWorkspaceClient(workspaceId, info[0])
|
||||
|
||||
const wsClient = this.workspaces.get(event.toWorkspace)
|
||||
|
||||
if (wsClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
await wsClient.transfer(event)
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
clearInterval(this.intervalId)
|
||||
for (const workspace of this.workspaces.values()) {
|
||||
await workspace.close()
|
||||
}
|
||||
for (const timeoutId of this.closeWorkspaceTimeouts.values()) {
|
||||
clearTimeout(timeoutId)
|
||||
}
|
||||
this.workspaces.clear()
|
||||
}
|
||||
|
||||
async updateAvatarInfo (workspace: WorkspaceId, path: string, lastModified: number): Promise<void> {
|
||||
await this.workspacesInfoCollection.updateOne(
|
||||
{ workspace: workspace.name, productId: workspace.productId },
|
||||
{ $set: { avatarPath: path, avatarLastModified: lastModified } }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function wait (delay: number): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, delay)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
import { config } from 'dotenv'
|
||||
|
||||
import { start } from './start'
|
||||
|
||||
config()
|
||||
void start()
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Client } from '@hcengineering/core'
|
||||
import { createClient, getTransactorEndpoint } from '@hcengineering/server-client'
|
||||
|
||||
export async function connectPlatform (token: string): Promise<Client> {
|
||||
const endpoint = await getTransactorEndpoint(token)
|
||||
return await createClient(endpoint, token)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { setMetadata } from '@hcengineering/platform'
|
||||
import serverToken from '@hcengineering/server-token'
|
||||
import serverAiBot from '@hcengineering/server-ai-bot'
|
||||
import { MeasureMetricsContext } from '@hcengineering/core'
|
||||
import serverClient from '@hcengineering/server-client'
|
||||
|
||||
import config from './config'
|
||||
import { closeDB, getDB } from './storage'
|
||||
import { AIBotController } from './controller'
|
||||
import { createBotAccount } from './account'
|
||||
|
||||
export const start = async (): Promise<void> => {
|
||||
setMetadata(serverToken.metadata.Secret, config.ServerSecret)
|
||||
setMetadata(serverAiBot.metadata.SupportWorkspaceId, config.SupportWorkspace)
|
||||
setMetadata(serverClient.metadata.UserAgent, config.ServiceID)
|
||||
setMetadata(serverClient.metadata.Endpoint, config.AccountsURL)
|
||||
|
||||
const ctx = new MeasureMetricsContext('ai-bot-service', {})
|
||||
|
||||
ctx.info('AI Bot Service started', { firstName: config.FirstName, lastName: config.LastName })
|
||||
|
||||
const db = await getDB(config.MongoURL, config.ConfigurationDB)
|
||||
for (let i = 0; i < 5; i++) {
|
||||
ctx.info('Creating bot account', { attempt: i })
|
||||
try {
|
||||
await createBotAccount()
|
||||
break
|
||||
} catch (e) {
|
||||
ctx.error('Error during account creation', { error: e })
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
}
|
||||
const aiController = new AIBotController(db, ctx)
|
||||
|
||||
const onClose = (): void => {
|
||||
void aiController.close()
|
||||
void closeDB()
|
||||
}
|
||||
|
||||
process.on('SIGINT', onClose)
|
||||
process.on('SIGTERM', onClose)
|
||||
process.on('uncaughtException', (e: Error) => {
|
||||
console.error(e)
|
||||
})
|
||||
process.on('unhandledRejection', (e: Error) => {
|
||||
console.error(e)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Db, MongoClient } from 'mongodb'
|
||||
|
||||
let client: MongoClient | undefined
|
||||
|
||||
export const getDB = async (mongoUrl: string, db: string): Promise<Db> => {
|
||||
client = new MongoClient(mongoUrl)
|
||||
await client.connect()
|
||||
|
||||
return client.db(db)
|
||||
}
|
||||
|
||||
export const closeDB: () => Promise<void> = async () => {
|
||||
if (client !== undefined) {
|
||||
await client.close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
//
|
||||
// Copyright © 2024 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, {
|
||||
Account,
|
||||
Class,
|
||||
Client,
|
||||
Doc,
|
||||
MeasureContext,
|
||||
Ref,
|
||||
Space,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxOperations,
|
||||
TxProcessor,
|
||||
WorkspaceId,
|
||||
Blob
|
||||
} from '@hcengineering/core'
|
||||
import aiBot, { AIBotEvent, aiBotAccountEmail, AIBotResponseEvent, AIBotTransferEvent } from '@hcengineering/ai-bot'
|
||||
import chunter, { ChatMessage, DirectMessage, ThreadMessage } from '@hcengineering/chunter'
|
||||
import contact, { AvatarType, combineName, getFirstName, getLastName, PersonAccount } from '@hcengineering/contact'
|
||||
import { generateToken } from '@hcengineering/server-token'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { getOrCreateAnalyticsChannel } from '@hcengineering/server-analytics-collector-resources'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { BlobClient } from '@hcengineering/server-client'
|
||||
import fs from 'fs'
|
||||
import { WorkspaceInfoRecord } from '@hcengineering/server-ai-bot'
|
||||
|
||||
import config from './config'
|
||||
import { loginBot } from './account'
|
||||
import { AIBotController } from './controller'
|
||||
import { connectPlatform } from './platform'
|
||||
|
||||
const MAX_LOGIN_DELAY_MS = 15 * 1000 // 15 ses
|
||||
|
||||
export class WorkspaceClient {
|
||||
client: Client | undefined
|
||||
opClient: TxOperations | undefined
|
||||
|
||||
blobClient: BlobClient
|
||||
|
||||
loginTimeout: NodeJS.Timeout | undefined
|
||||
loginDelayMs = 2 * 1000
|
||||
|
||||
initializePromise: Promise<void> | undefined = undefined
|
||||
|
||||
aiAccount: PersonAccount | undefined
|
||||
|
||||
constructor (
|
||||
readonly transactorUrl: string,
|
||||
readonly token: string,
|
||||
readonly workspace: WorkspaceId,
|
||||
readonly controller: AIBotController,
|
||||
readonly ctx: MeasureContext,
|
||||
readonly info: WorkspaceInfoRecord | undefined
|
||||
) {
|
||||
this.blobClient = new BlobClient(transactorUrl, token, this.workspace)
|
||||
this.initializePromise = this.initClient().then(() => {
|
||||
this.initializePromise = undefined
|
||||
})
|
||||
}
|
||||
|
||||
private async login (): Promise<string | undefined> {
|
||||
this.ctx.info('Logging in: ', this.workspace)
|
||||
const token = (await loginBot())?.token
|
||||
|
||||
if (token !== undefined) {
|
||||
return token
|
||||
} else {
|
||||
return (await loginBot())?.token
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadAvatarFile (client: TxOperations): Promise<void> {
|
||||
this.ctx.info('Upload avatar file', { workspace: this.workspace.name })
|
||||
|
||||
try {
|
||||
await this.checkPersonData(client)
|
||||
|
||||
const stat = fs.statSync(config.AvatarPath)
|
||||
const lastModified = stat.mtime.getTime()
|
||||
|
||||
if (
|
||||
this.info !== undefined &&
|
||||
this.info.avatarPath === config.AvatarPath &&
|
||||
this.info.avatarLastModified === lastModified
|
||||
) {
|
||||
this.ctx.info('Avatar file already uploaded', { workspace: this.workspace.name, path: config.AvatarPath })
|
||||
return
|
||||
}
|
||||
const data = fs.readFileSync(config.AvatarPath)
|
||||
|
||||
await this.blobClient.upload(this.ctx, config.AvatarName, data.length, config.AvatarContentType, data)
|
||||
await this.controller.updateAvatarInfo(this.workspace, config.AvatarPath, lastModified)
|
||||
this.ctx.info('Uploaded avatar file', { workspace: this.workspace.name, path: config.AvatarPath })
|
||||
} catch (e) {
|
||||
this.ctx.error('Failed to upload avatar file', { e })
|
||||
}
|
||||
}
|
||||
|
||||
private async tryLogin (): Promise<void> {
|
||||
const token = await this.login()
|
||||
|
||||
clearTimeout(this.loginTimeout)
|
||||
|
||||
if (token === undefined) {
|
||||
this.loginTimeout = setTimeout(() => {
|
||||
if (this.loginDelayMs < MAX_LOGIN_DELAY_MS) {
|
||||
this.loginDelayMs += 1000
|
||||
}
|
||||
void this.tryLogin()
|
||||
|
||||
this.ctx.info(`login delay ${this.loginDelayMs} millisecond`)
|
||||
}, this.loginDelayMs)
|
||||
}
|
||||
}
|
||||
|
||||
private async checkPersonData (client: TxOperations): Promise<void> {
|
||||
const account = await client.findOne(contact.class.PersonAccount, { email: aiBotAccountEmail })
|
||||
if (account === undefined) {
|
||||
this.ctx.error('Cannot find AI PersonAccount', { email: aiBotAccountEmail })
|
||||
return
|
||||
}
|
||||
this.aiAccount = account
|
||||
const person = await client.findOne(contact.class.Person, { _id: account.person })
|
||||
|
||||
if (person === undefined) {
|
||||
this.ctx.error('Cannot find AI Person ', { _id: account.person })
|
||||
return
|
||||
}
|
||||
|
||||
const firstName = getFirstName(person.name)
|
||||
const lastName = getLastName(person.name)
|
||||
|
||||
if (lastName !== config.LastName || firstName !== config.FirstName) {
|
||||
await client.update(person, {
|
||||
name: combineName(config.FirstName, config.LastName)
|
||||
})
|
||||
}
|
||||
|
||||
if (person.avatar === config.AvatarName) {
|
||||
return
|
||||
}
|
||||
|
||||
const exist = await this.blobClient.checkFile(this.ctx, config.AvatarName)
|
||||
|
||||
if (!exist) {
|
||||
this.ctx.error('Cannot find file', { file: config.AvatarName, workspace: this.workspace.name })
|
||||
return
|
||||
}
|
||||
|
||||
await client.diffUpdate(person, { avatar: config.AvatarName as Ref<Blob>, avatarType: AvatarType.IMAGE })
|
||||
}
|
||||
|
||||
private async initClient (): Promise<void> {
|
||||
await this.tryLogin()
|
||||
|
||||
const token = generateToken(aiBotAccountEmail, this.workspace)
|
||||
this.client = await connectPlatform(token)
|
||||
|
||||
if (this.client === undefined) {
|
||||
this.ctx.error('Cannot connect to platform', this.workspace)
|
||||
return
|
||||
}
|
||||
this.opClient = new TxOperations(this.client, aiBot.account.AIBot)
|
||||
void this.opClient.findAll(aiBot.class.AIBotEvent, {}).then((res) => {
|
||||
void this.processEvents(res)
|
||||
})
|
||||
|
||||
this.client.notify = (...txes: Tx[]) => {
|
||||
void this.txHandler(txes)
|
||||
}
|
||||
|
||||
await this.uploadAvatarFile(this.opClient)
|
||||
this.ctx.info('Initialized workspace', this.workspace)
|
||||
}
|
||||
|
||||
async getThreadParent (
|
||||
client: TxOperations,
|
||||
event: AIBotTransferEvent,
|
||||
_id: Ref<Doc>,
|
||||
_class: Ref<Class<Doc>>
|
||||
): Promise<ChatMessage | undefined> {
|
||||
const parent = await client.findOne(chunter.class.ChatMessage, {
|
||||
attachedTo: _id,
|
||||
attachedToClass: _class,
|
||||
[aiBot.mixin.TransferredMessage]: {
|
||||
messageId: event.parentMessageId,
|
||||
parentMessageId: undefined
|
||||
}
|
||||
})
|
||||
|
||||
if (parent !== undefined) {
|
||||
return parent
|
||||
}
|
||||
|
||||
return await client.findOne(chunter.class.ChatMessage, {
|
||||
_id: event.parentMessageId
|
||||
})
|
||||
}
|
||||
|
||||
async createTransferMessage (
|
||||
client: TxOperations,
|
||||
event: AIBotTransferEvent,
|
||||
_id: Ref<Doc>,
|
||||
_class: Ref<Class<Doc>>,
|
||||
space: Ref<Space>,
|
||||
message: string
|
||||
): Promise<void> {
|
||||
if (event.messageClass === chunter.class.ChatMessage) {
|
||||
const ref = await client.addCollection<Doc, ChatMessage>(
|
||||
chunter.class.ChatMessage,
|
||||
space,
|
||||
_id,
|
||||
_class,
|
||||
event.collection,
|
||||
{ message }
|
||||
)
|
||||
await client.createMixin(ref, chunter.class.ChatMessage, space, aiBot.mixin.TransferredMessage, {
|
||||
messageId: event.messageId,
|
||||
parentMessageId: event.parentMessageId
|
||||
})
|
||||
} else if (event.messageClass === chunter.class.ThreadMessage && event.parentMessageId !== undefined) {
|
||||
const parent = await this.getThreadParent(client, event, _id, _class)
|
||||
|
||||
if (parent !== undefined) {
|
||||
const ref = await client.addCollection<Doc, ThreadMessage>(
|
||||
chunter.class.ThreadMessage,
|
||||
parent.space,
|
||||
parent._id,
|
||||
parent._class,
|
||||
event.collection,
|
||||
{ message, objectId: parent.attachedTo, objectClass: parent.attachedToClass }
|
||||
)
|
||||
await client.createMixin(
|
||||
ref,
|
||||
chunter.class.ThreadMessage as Ref<Class<ChatMessage>>,
|
||||
space,
|
||||
aiBot.mixin.TransferredMessage,
|
||||
{
|
||||
messageId: event.messageId,
|
||||
parentMessageId: event.parentMessageId
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async processResponseEvent (event: AIBotResponseEvent): Promise<void> {
|
||||
if (this.opClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.messageClass === chunter.class.ChatMessage) {
|
||||
await this.opClient.addCollection<Doc, ChatMessage>(
|
||||
chunter.class.ChatMessage,
|
||||
event.objectSpace,
|
||||
event.objectId,
|
||||
event.objectClass,
|
||||
event.collection,
|
||||
{ message: 'You said: ' + event.message }
|
||||
)
|
||||
} else if (event.messageClass === chunter.class.ThreadMessage) {
|
||||
const parent = await this.opClient.findOne<ChatMessage>(chunter.class.ChatMessage, {
|
||||
_id: event.objectId as Ref<ChatMessage>
|
||||
})
|
||||
|
||||
if (parent !== undefined) {
|
||||
await this.opClient.addCollection<Doc, ThreadMessage>(
|
||||
chunter.class.ThreadMessage,
|
||||
event.objectSpace,
|
||||
event.objectId,
|
||||
event.objectClass,
|
||||
event.collection,
|
||||
{ message: 'You said: ' + event.message, objectId: parent.attachedTo, objectClass: parent.attachedToClass }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await this.opClient.remove(event)
|
||||
}
|
||||
|
||||
async processTransferEvent (event: AIBotTransferEvent): Promise<void> {
|
||||
if (this.opClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.controller.transfer(event)
|
||||
await this.opClient.remove(event)
|
||||
}
|
||||
|
||||
async getAccount (email: string): Promise<Account | undefined> {
|
||||
if (this.opClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
return await this.opClient.findOne(contact.class.PersonAccount, { email })
|
||||
}
|
||||
|
||||
async getDirect (_id: Ref<Account>): Promise<Ref<DirectMessage> | undefined> {
|
||||
if (this.opClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const personAccount = await this.opClient.findOne(contact.class.PersonAccount, { _id: _id as Ref<PersonAccount> })
|
||||
|
||||
if (personAccount === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const allAccounts = await this.opClient.findAll(contact.class.PersonAccount, { person: personAccount.person })
|
||||
const accIds: Ref<Account>[] = [aiBot.account.AIBot, ...allAccounts.map(({ _id }) => _id)].sort()
|
||||
const existingDms = await this.opClient.findAll(chunter.class.DirectMessage, {})
|
||||
|
||||
for (const dm of existingDms) {
|
||||
if (deepEqual(dm.members.sort(), accIds)) {
|
||||
return dm._id
|
||||
}
|
||||
}
|
||||
|
||||
const id = await this.opClient.createDoc<DirectMessage>(chunter.class.DirectMessage, core.space.Space, {
|
||||
name: '',
|
||||
description: '',
|
||||
private: true,
|
||||
archived: false,
|
||||
members: accIds
|
||||
})
|
||||
|
||||
if (this.aiAccount === undefined) return id
|
||||
const space = await this.opClient.findOne(contact.class.PersonSpace, { person: this.aiAccount.person })
|
||||
if (space === undefined) return id
|
||||
await this.opClient.createDoc(notification.class.DocNotifyContext, space._id, {
|
||||
user: aiBot.account.AIBot,
|
||||
objectId: id,
|
||||
objectClass: chunter.class.DirectMessage,
|
||||
objectSpace: core.space.Space,
|
||||
isPinned: false
|
||||
})
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
async transferToSupport (event: AIBotTransferEvent): Promise<void> {
|
||||
if (this.opClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const channel = await getOrCreateAnalyticsChannel(this.opClient, event.toEmail, event.fromWorkspace)
|
||||
|
||||
if (channel === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.createTransferMessage(this.opClient, event, channel, chunter.class.Channel, channel, event.message)
|
||||
}
|
||||
|
||||
async transferToUserDirect (event: AIBotTransferEvent): Promise<void> {
|
||||
if (this.opClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const account = await this.getAccount(event.toEmail)
|
||||
|
||||
if (account === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const direct = await this.getDirect(account._id)
|
||||
|
||||
if (direct === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.createTransferMessage(this.opClient, event, direct, chunter.class.DirectMessage, direct, event.message)
|
||||
}
|
||||
|
||||
async transfer (event: AIBotTransferEvent): Promise<void> {
|
||||
if (this.initializePromise instanceof Promise) {
|
||||
await this.initializePromise
|
||||
}
|
||||
|
||||
if (event.toWorkspace === config.SupportWorkspace) {
|
||||
await this.transferToSupport(event)
|
||||
} else {
|
||||
await this.transferToUserDirect(event)
|
||||
}
|
||||
}
|
||||
|
||||
async processEvents (events: AIBotEvent[]): Promise<void> {
|
||||
if (events.length === 0 || this.opClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const event of events) {
|
||||
try {
|
||||
if (event._class === aiBot.class.AIBotResponseEvent) {
|
||||
void this.processResponseEvent(event as AIBotResponseEvent)
|
||||
} else if (event._class === aiBot.class.AIBotTransferEvent) {
|
||||
void this.processTransferEvent(event as AIBotTransferEvent)
|
||||
}
|
||||
} catch (e) {
|
||||
this.ctx.error('Error processing event: ', { e })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
clearTimeout(this.loginTimeout)
|
||||
await this.client?.close()
|
||||
await this.opClient?.close()
|
||||
|
||||
this.ctx.info('Closed workspace client: ', this.workspace)
|
||||
}
|
||||
|
||||
private async txHandler (txes: Tx[]): Promise<void> {
|
||||
if (this.opClient === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const hierarchy = this.opClient.getHierarchy()
|
||||
|
||||
const resultTxes = txes
|
||||
.map((a) => TxProcessor.extractTx(a) as TxCreateDoc<AIBotEvent>)
|
||||
.filter(
|
||||
(tx) => tx._class === core.class.TxCreateDoc && hierarchy.isDerived(tx.objectClass, aiBot.class.AIBotEvent)
|
||||
)
|
||||
.map((tx) => TxProcessor.createDoc2Doc(tx))
|
||||
|
||||
await this.processEvents(resultTxes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types",
|
||||
"tsBuildInfoFile": ".build/build.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user