mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Add translate message action (#6609)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Vendored
+1
-1
@@ -458,7 +458,7 @@
|
||||
"env": {
|
||||
"ACCOUNTS_URL": "http://localhost:3000",
|
||||
"MONGO_URL": "mongodb://localhost:27017",
|
||||
"PORT": "4008",
|
||||
"PORT": "4010",
|
||||
"SERVER_SECRET": "secret",
|
||||
"SUPPORT_WORKSPACE": "support",
|
||||
"FIRST_NAME": "Jolie",
|
||||
|
||||
Generated
+67
-13
@@ -74,6 +74,9 @@ dependencies:
|
||||
'@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/ai-bot-resources':
|
||||
specifier: file:./projects/ai-bot-resources.tgz
|
||||
version: file:projects/ai-bot-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/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)
|
||||
@@ -1820,9 +1823,6 @@ dependencies:
|
||||
telegram:
|
||||
specifier: 2.22.2
|
||||
version: 2.22.2
|
||||
tiktoken:
|
||||
specifier: ^1.0.16
|
||||
version: 1.0.16
|
||||
toposort:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
@@ -17077,6 +17077,12 @@ packages:
|
||||
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
|
||||
dev: false
|
||||
|
||||
/js-tiktoken@1.0.14:
|
||||
resolution: {integrity: sha512-Pk3l3WOgM9joguZY2k52+jH82RtABRgB5RdGFZNUGbOKGMVlNmafcPA3b0ITcCZPu1L9UclP1tne6aw7ZI4Myg==}
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
dev: false
|
||||
|
||||
/js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
dev: false
|
||||
@@ -24527,6 +24533,51 @@ packages:
|
||||
- ts-node
|
||||
dev: false
|
||||
|
||||
file:projects/ai-bot-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-kUT09zDslxlN/ni38NdRvzE4WtOBOtKbqKqTNvTDP78hcjh7rT3CnWpMUQGnDNI5rzQWiVAaWoHKf2LAHFolRQ==, tarball: file:projects/ai-bot-resources.tgz}
|
||||
id: file:projects/ai-bot-resources.tgz
|
||||
name: '@rush-temp/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)
|
||||
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.12)(ts-node@10.9.2)
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
prettier: 3.2.5
|
||||
prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.12)
|
||||
sass: 1.71.1
|
||||
svelte: 4.2.12
|
||||
svelte-check: 3.6.9(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.12)
|
||||
svelte-eslint-parser: 0.33.1(svelte@4.2.12)
|
||||
svelte-loader: 3.2.0(svelte@4.2.12)
|
||||
svelte-preprocess: 5.1.3(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.12)(typescript@5.3.3)
|
||||
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
|
||||
- coffeescript
|
||||
- esbuild
|
||||
- less
|
||||
- node-notifier
|
||||
- postcss
|
||||
- postcss-load-config
|
||||
- pug
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- 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
|
||||
@@ -25328,7 +25379,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/chunter-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-RWD09VQ42IkJmhjvrQ6cqQ3etpChxFKhZICAQ6cuSbjetHSz0LKXSzrsE5xjpnsZE6Wr22dSDiglk9+82SVC8w==, tarball: file:projects/chunter-resources.tgz}
|
||||
resolution: {integrity: sha512-qC9oDROK1wvmwya82IbB9HAj4bYw3oBPa23nMIW8phhnkSp+6fCVboz3D8G1EIw5eZmDbPCSgtnZZh+mH/VqLw==, tarball: file:projects/chunter-resources.tgz}
|
||||
id: file:projects/chunter-resources.tgz
|
||||
name: '@rush-temp/chunter-resources'
|
||||
version: 0.0.0
|
||||
@@ -25376,7 +25427,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/chunter.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-7CCSMrDbX8Mb8drPtpIVbQbvx1fsXW6YAqceu/rhsE7b9+i3LRaNGlyggJ1V1Kg32FTEHK2Q4jkKrn34aLL+3A==, tarball: file:projects/chunter.tgz}
|
||||
resolution: {integrity: sha512-30IiBYbNohYn7c6fiy/QTHMy6qkpRv3ZvghD595O/is4lFkDfaBLP18Zd90fgyQNg/G/2HtGMGXunlWEr/OAoQ==, tarball: file:projects/chunter.tgz}
|
||||
id: file:projects/chunter.tgz
|
||||
name: '@rush-temp/chunter'
|
||||
version: 0.0.0
|
||||
@@ -25535,7 +25586,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/collaborator.tgz(@tiptap/pm@2.6.6)(bufferutil@4.0.8)(utf-8-validate@6.0.4)(y-protocols@1.0.6):
|
||||
resolution: {integrity: sha512-ILQCgoJM6kiRT3YNphnywB42nFA5VZJkRXae0PiKrm+RJwm9/Qf0Berkrj+SDy32iC9fw6PI/+yES11MeqFeSA==, tarball: file:projects/collaborator.tgz}
|
||||
resolution: {integrity: sha512-ljGlQulKneGos0pwJDjmKkgFZVizNJ4CMyGUp14orVTc40IKNK4szU09WskcwoZ6MGbBnUJttMVuGf3tb0/0iA==, tarball: file:projects/collaborator.tgz}
|
||||
id: file:projects/collaborator.tgz
|
||||
name: '@rush-temp/collaborator'
|
||||
version: 0.0.0
|
||||
@@ -26016,7 +26067,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/desktop.tgz(bufferutil@4.0.8)(sass@1.71.1)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-W8ImBT7qeJoODsDzoy5ioDFr7A1YJX5S9xa8haLtNxiOce9iZKCyesZ5qK81g5ZgNETfhWXNo1+7u0fCcoG2qw==, tarball: file:projects/desktop.tgz}
|
||||
resolution: {integrity: sha512-y8p1AOK7K4z/Y7hzkEy4MfAc3JNlLrR+54QYdqAmzCzF7NtuM4x9YeB7oQTmQfKYSmZHXJb4++pJ0+AFONIXSQ==, tarball: file:projects/desktop.tgz}
|
||||
id: file:projects/desktop.tgz
|
||||
name: '@rush-temp/desktop'
|
||||
version: 0.0.0
|
||||
@@ -27495,7 +27546,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/love-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-e49VrhVkk6U8nj/ZyKfYVj/gK/3rTSrVCicDM5Luxw/f/8pPEVf9uEzmdAV11uiXuXxmj5t0BmHdG5eeVhiK6A==, tarball: file:projects/love-resources.tgz}
|
||||
resolution: {integrity: sha512-6dbzkDxFSSCZr7b3Cm7AFvESRax8JpNYiIvI/ScUNPN+M7L2a3PKWKlfdcD/ZvWOAKmQW1l0j5GPH/ucHHu8sQ==, tarball: file:projects/love-resources.tgz}
|
||||
id: file:projects/love-resources.tgz
|
||||
name: '@rush-temp/love-resources'
|
||||
version: 0.0.0
|
||||
@@ -27543,7 +27594,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/love.tgz(@types/node@20.11.19)(esbuild@0.20.1)(svelte@4.2.12)(ts-node@10.9.2):
|
||||
resolution: {integrity: sha512-IA1DOwfYE00ACQ47zgXq3yOcNghk1oU1nPcq15bqdjOxJeKaE5sa4w/yJ7WoGn1Dm5F0TmxKc67GGDxcTT6kDQ==, tarball: file:projects/love.tgz}
|
||||
resolution: {integrity: sha512-D9QBf+/PREVaj/zqju1EnET+45ImzmEpqYThK+I2fbGxZ2t128DbfspBi3Lf2pUure8yF2PSTlTJSzKmUtgvpA==, tarball: file:projects/love.tgz}
|
||||
id: file:projects/love.tgz
|
||||
name: '@rush-temp/love'
|
||||
version: 0.0.0
|
||||
@@ -29340,7 +29391,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/openai.tgz(esbuild@0.20.1)(svelte@4.2.12)(ts-node@10.9.2)(zod@3.23.8):
|
||||
resolution: {integrity: sha512-jWsGOjlco80/7CIZ2Ogs7lCWr23lHUDQIB2hkPgYu2rbvM7iwSz5jhPjTPmR2y9gkVTSyyd9ozTo9LuxPpI6Kg==, tarball: file:projects/openai.tgz}
|
||||
resolution: {integrity: sha512-ZFMlTpj1sADO+BtsNC9SGLFtId5nF8RpYNJhDyz1Q/Mt+c/pBk/ayUKOioOCjc4XTP+82rjue+kojBfDeFQuLw==, tarball: file:projects/openai.tgz}
|
||||
id: file:projects/openai.tgz
|
||||
name: '@rush-temp/openai'
|
||||
version: 0.0.0
|
||||
@@ -29355,6 +29406,7 @@ packages:
|
||||
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)
|
||||
js-tiktoken: 1.0.14
|
||||
openai: 4.56.0(zod@3.23.8)
|
||||
prettier: 3.2.5
|
||||
prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.12)
|
||||
@@ -29525,7 +29577,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/pod-ai-bot.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4)(zod@3.23.8):
|
||||
resolution: {integrity: sha512-aMlSGJ89IhINflYpenenAXdbK+Vw6eAHu3R9rO1nsd7pmsOWxRLG6Hp5f+sN1q6S1cmPVQeD/q/tuvp6byFK8g==, tarball: file:projects/pod-ai-bot.tgz}
|
||||
resolution: {integrity: sha512-hQkngAuFcwYMmIcVKDLMD0UnbZf3JuuXVVoWGG1w/7quPWksjcce8LX6hNrShCzN/+5LkpZVzNz8PAEHdX1M8A==, tarball: file:projects/pod-ai-bot.tgz}
|
||||
id: file:projects/pod-ai-bot.tgz
|
||||
name: '@rush-temp/pod-ai-bot'
|
||||
version: 0.0.0
|
||||
@@ -29552,10 +29604,12 @@ packages:
|
||||
fast-equals: 5.0.1
|
||||
form-data: 4.0.0
|
||||
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
|
||||
js-tiktoken: 1.0.14
|
||||
mongodb: 6.9.0
|
||||
node-fetch: 2.7.0
|
||||
openai: 4.56.0(zod@3.23.8)
|
||||
prettier: 3.2.5
|
||||
puppeteer: 22.14.0(bufferutil@4.0.8)(typescript@5.3.3)(utf-8-validate@6.0.4)
|
||||
tiktoken: 1.0.16
|
||||
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)
|
||||
@@ -29813,7 +29867,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/pod-github.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4)(y-prosemirror@1.2.12):
|
||||
resolution: {integrity: sha512-WPkKYn8Uz2hTqWrzT7vjrfyFyOZctsvFVeixkSDH2RPydyXaB6tE2qIm0UFa5VgCnuBu9Rsc7nN62m2Ac7mL1w==, tarball: file:projects/pod-github.tgz}
|
||||
resolution: {integrity: sha512-eyMBtVIlmdGMh+T89SuihlOHLsLA6czYiSY5SiZ06YL+VOb/TrBiluqvbWyMWUThZ1gFQCpUH/jm+0nkDUZg+Q==, tarball: file:projects/pod-github.tgz}
|
||||
id: file:projects/pod-github.tgz
|
||||
name: '@rush-temp/pod-github'
|
||||
version: 0.0.0
|
||||
@@ -30610,7 +30664,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/prod.tgz(bufferutil@4.0.8)(sass@1.71.1)(ts-node@10.9.2)(utf-8-validate@6.0.4):
|
||||
resolution: {integrity: sha512-cvUyK9P5nMldS6XkLY4agfy2Ch2aFf7Qv7rXU/goSeywHNOwEf/jgffVgsXFU6Z0TFqnWfiB/ey7SJ9pM/mU4Q==, tarball: file:projects/prod.tgz}
|
||||
resolution: {integrity: sha512-TnkAF2lFRAD6kQruc0x6uW65fsADXRJyTxBFx2mKJSsaZP57rWfmFDaO00am5SvSzGtUr2GguQvcCh+IeoPYrQ==, tarball: file:projects/prod.tgz}
|
||||
id: file:projects/prod.tgz
|
||||
name: '@rush-temp/prod'
|
||||
version: 0.0.0
|
||||
|
||||
@@ -201,6 +201,8 @@
|
||||
"@hcengineering/analytics-collector": "^0.6.0",
|
||||
"@hcengineering/analytics-collector-assets": "^0.6.0",
|
||||
"@hcengineering/analytics-collector-resources": "^0.6.0",
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/ai-bot-resources": "^0.6.0",
|
||||
"electron-squirrel-startup": "~1.0.0",
|
||||
"dotenv": "~16.0.0",
|
||||
"electron-context-menu": "^4.0.1",
|
||||
|
||||
@@ -52,6 +52,7 @@ import { productsId } from '@hcengineering/products'
|
||||
import { questionsId } from '@hcengineering/questions'
|
||||
import { trainingId } from '@hcengineering/training'
|
||||
import { documentsId } from '@hcengineering/controlled-documents'
|
||||
import aiBot, { aiBotId } from '@hcengineering/ai-bot'
|
||||
|
||||
import '@hcengineering/activity-assets'
|
||||
import '@hcengineering/attachment-assets'
|
||||
@@ -234,6 +235,7 @@ export async function configurePlatform (): Promise<void> {
|
||||
setMetadata(sign.metadata.SignURL, config.SIGN_URL)
|
||||
setMetadata(uiPlugin.metadata.DefaultApplication, login.component.LoginApp)
|
||||
setMetadata(analyticsCollector.metadata.EndpointURL, config.ANALYTICS_COLLECTOR_URL)
|
||||
setMetadata(aiBot.metadata.EndpointURL, config.AI_URL)
|
||||
|
||||
const languages = myBranding.languages !== undefined && myBranding.languages !== '' ? myBranding.languages.split(',').map((l) => l.trim()) : ['en', 'ru', 'es', 'pt']
|
||||
|
||||
@@ -275,6 +277,7 @@ export async function configurePlatform (): Promise<void> {
|
||||
addLocation(tagsId, async () => await import('@hcengineering/tags-resources'))
|
||||
addLocation(calendarId, async () => await import('@hcengineering/calendar-resources'))
|
||||
addLocation(analyticsCollectorId, async () => await import('@hcengineering/analytics-collector-resources'))
|
||||
addLocation(aiBotId, async () => await import('@hcengineering/ai-bot-resources'))
|
||||
|
||||
addLocation(trackerId, async () => await import('@hcengineering/tracker-resources'))
|
||||
addLocation(boardId, async () => await import('@hcengineering/board-resources'))
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface Config {
|
||||
PRINT_URL?: string
|
||||
PUSH_PUBLIC_KEY: string
|
||||
ANALYTICS_COLLECTOR_URL?: string
|
||||
AI_URL?:string
|
||||
BRANDING_URL?: string
|
||||
PREVIEW_CONFIG: string
|
||||
DESKTOP_UPDATES_URL?: string
|
||||
|
||||
@@ -276,6 +276,8 @@ services:
|
||||
memory: 300M
|
||||
aiBot:
|
||||
image: hardcoreeng/ai-bot
|
||||
ports:
|
||||
- 4010:4010
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -10,3 +10,4 @@ PRINT_URL=http://localhost:4005
|
||||
SIGN_URL=http://localhost:4006
|
||||
|
||||
ANALYTICS_COLLECTOR_URL=http://localhost:4007
|
||||
AI_URL=http://localhost:4010
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
"REKONI_URL": "http://localhost:4004",
|
||||
"PRINT_URL": "http://localhost:4005",
|
||||
"SIGN_URL": "http://localhost:4006",
|
||||
"ANALYTICS_COLLECTOR_URL":"http://localhost:4007"
|
||||
"ANALYTICS_COLLECTOR_URL":"http://localhost:4007",
|
||||
"AI_URL": "http://localhost:4010"
|
||||
}
|
||||
@@ -231,6 +231,8 @@
|
||||
"@hcengineering/products": "^0.1.0",
|
||||
"@hcengineering/products-assets": "^0.1.0",
|
||||
"@hcengineering/products-resources": "^0.1.0",
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/ai-bot-resources": "^0.6.0",
|
||||
"@sentry/svelte": "~7.101.0",
|
||||
"posthog-js": "~1.122.0"
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"PRINT_URL": "http://localhost:4005",
|
||||
"SIGN_URL": "http://localhost:4006",
|
||||
"ANALYTICS_COLLECTOR_URL": "http://localhost:4007",
|
||||
"AI_URL": "http://localhost:4010",
|
||||
"BRANDING_URL": "/branding.json",
|
||||
"VERSION": null,
|
||||
"MODEL_VERSION": null
|
||||
|
||||
@@ -59,6 +59,7 @@ import { documentsId } from '@hcengineering/controlled-documents'
|
||||
import textEditor, { textEditorId } from '@hcengineering/text-editor'
|
||||
import analyticsCollector, {analyticsCollectorId} from '@hcengineering/analytics-collector'
|
||||
import { uploaderId } from '@hcengineering/uploader'
|
||||
import aiBot, { aiBotId } from '@hcengineering/ai-bot'
|
||||
|
||||
import { bitrixId } from '@hcengineering/bitrix'
|
||||
|
||||
@@ -144,6 +145,7 @@ export interface Config {
|
||||
ANALYTICS_COLLECTOR_URL?:string
|
||||
BRANDING_URL?: string
|
||||
TELEGRAM_BOT_URL?: string
|
||||
AI_URL?:string
|
||||
|
||||
// Could be defined for dev environment
|
||||
FRONT_URL?: string
|
||||
@@ -307,6 +309,7 @@ export async function configurePlatform() {
|
||||
setMetadata(calendar.metadata.CalendarServiceURL, config.CALENDAR_URL ?? 'http://localhost:8095')
|
||||
setMetadata(notification.metadata.PushPublicKey, config.PUSH_PUBLIC_KEY)
|
||||
setMetadata(analyticsCollector.metadata.EndpointURL, config.ANALYTICS_COLLECTOR_URL)
|
||||
setMetadata(aiBot.metadata.EndpointURL, config.AI_URL)
|
||||
|
||||
setMetadata(github.metadata.GithubApplication, config.GITHUB_APP ?? '')
|
||||
setMetadata(github.metadata.GithubClientID, config.GITHUB_CLIENTID ?? '')
|
||||
@@ -365,6 +368,7 @@ export async function configurePlatform() {
|
||||
addLocation(timeId, () => import(/* webpackChunkName: "time" */ '@hcengineering/time-resources'))
|
||||
addLocation(desktopPreferencesId, () => import(/* webpackChunkName: "desktop-preferences" */ '@hcengineering/desktop-preferences-resources'))
|
||||
addLocation(analyticsCollectorId, async () => await import('@hcengineering/analytics-collector-resources'))
|
||||
addLocation(aiBotId, async () => await import('@hcengineering/ai-bot-resources'))
|
||||
|
||||
addLocation(trackerId, () => import(/* webpackChunkName: "tracker" */ '@hcengineering/tracker-resources'))
|
||||
addLocation(boardId, () => import(/* webpackChunkName: "board" */ '@hcengineering/board-resources'))
|
||||
|
||||
@@ -43,6 +43,11 @@ export function defineActions (builder: Builder): void {
|
||||
chunter.category.Chunter
|
||||
)
|
||||
|
||||
defineMessageActions(builder)
|
||||
defineChannelActions(builder)
|
||||
}
|
||||
|
||||
function defineMessageActions (builder: Builder): void {
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
@@ -84,6 +89,61 @@ export function defineActions (builder: Builder): void {
|
||||
chunter.action.CopyChatMessageLink
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: chunter.actionImpl.DeleteChatMessage,
|
||||
label: view.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
input: 'focus',
|
||||
keyBinding: ['Backspace'],
|
||||
category: chunter.category.Chunter,
|
||||
target: chunter.class.ChatMessage,
|
||||
visibilityTester: chunter.function.CanDeleteMessage,
|
||||
context: { mode: ['context', 'browser'], group: 'remove' }
|
||||
},
|
||||
chunter.action.DeleteChatMessage
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: chunter.actionImpl.TranslateMessage,
|
||||
label: chunter.string.Translate,
|
||||
icon: view.icon.Translate,
|
||||
input: 'focus',
|
||||
category: chunter.category.Chunter,
|
||||
target: chunter.class.ChatMessage,
|
||||
visibilityTester: chunter.function.CanTranslateMessage,
|
||||
inline: true,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
chunter.action.TranslateMessage
|
||||
)
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: chunter.actionImpl.ShowOriginalMessage,
|
||||
label: chunter.string.ShowOriginal,
|
||||
icon: view.icon.Undo,
|
||||
input: 'focus',
|
||||
category: chunter.category.Chunter,
|
||||
target: chunter.class.ChatMessage,
|
||||
visibilityTester: chunter.function.CanTranslateMessage,
|
||||
inline: true,
|
||||
context: {
|
||||
mode: 'context',
|
||||
group: 'edit'
|
||||
}
|
||||
},
|
||||
chunter.action.ShowOriginalMessage
|
||||
)
|
||||
}
|
||||
|
||||
function defineChannelActions (builder: Builder): void {
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
@@ -154,22 +214,6 @@ export function defineActions (builder: Builder): void {
|
||||
}
|
||||
})
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: chunter.actionImpl.DeleteChatMessage,
|
||||
label: view.string.Delete,
|
||||
icon: view.icon.Delete,
|
||||
input: 'focus',
|
||||
keyBinding: ['Backspace'],
|
||||
category: chunter.category.Chunter,
|
||||
target: chunter.class.ChatMessage,
|
||||
visibilityTester: chunter.function.CanDeleteMessage,
|
||||
context: { mode: ['context', 'browser'], group: 'remove' }
|
||||
},
|
||||
chunter.action.DeleteChatMessage
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
|
||||
@@ -52,7 +52,9 @@ export default mergeIds(chunterId, chunter, {
|
||||
DeleteChatMessage: '' as ViewAction,
|
||||
ReplyToThread: '' as ViewAction,
|
||||
OpenInSidebar: '' as ViewAction,
|
||||
OpenInSidebarTab: '' as ViewAction
|
||||
OpenInSidebarTab: '' as ViewAction,
|
||||
TranslateMessage: '' as ViewAction,
|
||||
ShowOriginalMessage: '' as ViewAction
|
||||
},
|
||||
category: {
|
||||
Chunter: '' as Ref<ActionCategory>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import '@hcengineering/platform-rig/profiles/ui/svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { derived, writable } from 'svelte/store'
|
||||
|
||||
export { default as Theme } from './Theme.svelte'
|
||||
|
||||
@@ -78,3 +78,5 @@ export function initThemeStore (): void {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export const languageStore = derived(themeStore, ($theme) => $theme.language)
|
||||
|
||||
@@ -47,7 +47,7 @@ export type {
|
||||
MouseTargetEvent
|
||||
} from './types'
|
||||
|
||||
export { themeStore } from '@hcengineering/theme'
|
||||
export { themeStore, languageStore } from '@hcengineering/theme'
|
||||
// export { applicationShortcutKey } from './utils'
|
||||
export { getCurrentLocation, locationToUrl, navigate, location, setLocationStorageKey } from './location'
|
||||
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import view, { Action as ViewAction } from '@hcengineering/view'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
|
||||
import ActivityMessageAction from './ActivityMessageAction.svelte'
|
||||
import { savedMessagesStore } from '../activity'
|
||||
|
||||
export let message: ActivityMessage | undefined
|
||||
export let actions: Action[] = []
|
||||
export let excludedActions: Ref<ViewAction>[] = []
|
||||
export let withActionMenu = true
|
||||
export let onOpen: () => void
|
||||
export let onClose: () => void
|
||||
@@ -34,7 +36,7 @@
|
||||
let inlineActions: ViewAction[] = []
|
||||
let isActionMenuOpened = false
|
||||
|
||||
$: void updateInlineActions(message)
|
||||
$: void updateInlineActions(message, excludedActions)
|
||||
|
||||
savedMessagesStore.subscribe(() => {
|
||||
void updateInlineActions(message)
|
||||
@@ -51,15 +53,13 @@
|
||||
}
|
||||
|
||||
function showMenu (ev: MouseEvent): void {
|
||||
const excludedActions = inlineActions.map(({ _id }) => _id)
|
||||
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
object: message,
|
||||
actions,
|
||||
baseMenuClass: activity.class.ActivityMessage,
|
||||
excludedActions
|
||||
excludedActions: inlineActions.map(({ _id }) => _id).concat(excludedActions)
|
||||
},
|
||||
ev.target as HTMLElement,
|
||||
handleActionMenuClosed
|
||||
@@ -67,14 +67,14 @@
|
||||
handleActionMenuOpened()
|
||||
}
|
||||
|
||||
async function updateInlineActions (message?: ActivityMessage): Promise<void> {
|
||||
async function updateInlineActions (message?: ActivityMessage, excludedAction: Ref<ViewAction>[] = []): Promise<void> {
|
||||
if (message === undefined) {
|
||||
inlineActions = []
|
||||
return
|
||||
}
|
||||
inlineActions = (await getActions(client, message, activity.class.ActivityMessage)).filter(
|
||||
(action) => action.inline
|
||||
)
|
||||
inlineActions = (await getActions(client, message, activity.class.ActivityMessage))
|
||||
.filter((action) => action.inline)
|
||||
.filter((action) => !excludedAction.includes(action._id))
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+15
-1
@@ -20,11 +20,12 @@
|
||||
} from '@hcengineering/activity'
|
||||
import { Person } from '@hcengineering/contact'
|
||||
import { Avatar, EmployeePresenter, SystemAvatar } from '@hcengineering/contact-resources'
|
||||
import core from '@hcengineering/core'
|
||||
import core, { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Action, Icon, Label } from '@hcengineering/ui'
|
||||
import { getActions, restrictionStore, showMenu } from '@hcengineering/view-resources'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import { Action as ViewAction } from '@hcengineering/view'
|
||||
|
||||
import ReactionsPresenter from '../reactions/ReactionsPresenter.svelte'
|
||||
import ActivityMessagePresenter from './ActivityMessagePresenter.svelte'
|
||||
@@ -33,6 +34,8 @@
|
||||
import { savedMessagesStore } from '../../activity'
|
||||
import MessageTimestamp from '../MessageTimestamp.svelte'
|
||||
import Replies from '../Replies.svelte'
|
||||
import { MessageInlineAction } from '../../types'
|
||||
import InlineAction from './InlineAction.svelte'
|
||||
|
||||
export let message: DisplayActivityMessage
|
||||
export let parentMessage: DisplayActivityMessage | undefined = undefined
|
||||
@@ -55,6 +58,8 @@
|
||||
export let hoverStyles: 'borderedHover' | 'filledHover' = 'borderedHover'
|
||||
export let showDatePreposition = false
|
||||
export let type: ActivityMessageViewType = 'default'
|
||||
export let inlineActions: MessageInlineAction[] = []
|
||||
export let excludedActions: Ref<ViewAction>[] = []
|
||||
export let onClick: (() => void) | undefined = undefined
|
||||
|
||||
export let socialIcon: Asset | undefined = undefined
|
||||
@@ -223,6 +228,14 @@
|
||||
<span class="text-sm lower">
|
||||
<MessageTimestamp date={message.createdOn ?? message.modifiedOn} />
|
||||
</span>
|
||||
|
||||
{#if withActions && inlineActions.length > 0 && !readonly}
|
||||
<div class="flex-presenter flex-gap-2 ml-2">
|
||||
{#each inlineActions as item}
|
||||
<InlineAction {item} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -244,6 +257,7 @@
|
||||
message={isReactionMessage(message) ? parentMessage : message}
|
||||
{actions}
|
||||
{withActionMenu}
|
||||
{excludedActions}
|
||||
onOpen={handleActionsOpened}
|
||||
onClose={handleActionsClosed}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
// 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.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Label } from '@hcengineering/ui'
|
||||
|
||||
import { MessageInlineAction } from '../../types'
|
||||
|
||||
export let item: MessageInlineAction
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="root" class:clickable={item.onClick !== undefined} on:click={item.onClick}>
|
||||
<Label label={item.label} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.root {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--global-secondary-TextColor);
|
||||
cursor: default;
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--global-primary-TextColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
shouldScrollToActivity
|
||||
} from './utils'
|
||||
|
||||
export * from './types'
|
||||
export * from './activity'
|
||||
export * from './utils'
|
||||
export * from './activityMessagesUtils'
|
||||
|
||||
@@ -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 { type IntlString } from '@hcengineering/platform'
|
||||
|
||||
export interface MessageInlineAction {
|
||||
label: IntlString
|
||||
onClick?: () => Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
|
||||
parserOptions: { tsconfigRootDir: __dirname }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"proseWrap": "preserve",
|
||||
"plugins": [
|
||||
"prettier-plugin-svelte"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.svelte",
|
||||
"options": {
|
||||
"parser": "svelte"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "ui"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@hcengineering/ai-bot-resources",
|
||||
"version": "0.6.0",
|
||||
"main": "src/index.ts",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "compile ui",
|
||||
"build:docs": "api-extractor run --local",
|
||||
"format": "format src",
|
||||
"svelte-check": "do-svelte-check",
|
||||
"_phase:svelte-check": "do-svelte-check",
|
||||
"build:watch": "compile ui",
|
||||
"_phase:build": "compile ui",
|
||||
"_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",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-svelte": "^3.2.2",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-check": "^3.6.9",
|
||||
"svelte-eslint-parser": "^0.33.1",
|
||||
"svelte-loader": "^3.2.0",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/presentation": "^0.6.3",
|
||||
"svelte": "^4.2.12"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
||||
@@ -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 { type Resources } from '@hcengineering/platform'
|
||||
|
||||
export * from './utils'
|
||||
|
||||
export default async (): Promise<Resources> => ({})
|
||||
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// Copyright © 2024 Anticrm Platform Contributors.
|
||||
//
|
||||
// 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 { mergeIds } from '@hcengineering/platform'
|
||||
import aiBot, { aiBotId } from '@hcengineering/ai-bot'
|
||||
|
||||
export default mergeIds(aiBotId, aiBot, {})
|
||||
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// 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 { concatLink, type Markup } from '@hcengineering/core'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import presentation from '@hcengineering/presentation'
|
||||
import { type TranslateRequest, type TranslateResponse } from '@hcengineering/ai-bot'
|
||||
|
||||
import aiBot from './plugin'
|
||||
|
||||
export async function translate (text: Markup, lang: string): Promise<TranslateResponse | undefined> {
|
||||
const url = getMetadata(aiBot.metadata.EndpointURL) ?? ''
|
||||
const token = getMetadata(presentation.metadata.Token) ?? ''
|
||||
|
||||
if (url === '' || token === '') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const req: TranslateRequest = { text, lang }
|
||||
const resp = await fetch(concatLink(url, '/translate'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(req)
|
||||
})
|
||||
if (!resp.ok) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return (await resp.json()) as TranslateResponse
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
|
||||
module.exports = {
|
||||
preprocess: sveltePreprocess()
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./types"
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,12 @@
|
||||
//
|
||||
|
||||
import { Account, Class, Doc, type Mixin, Ref, Space } from '@hcengineering/core'
|
||||
import type { Plugin } from '@hcengineering/platform'
|
||||
import type { Metadata, Plugin } from '@hcengineering/platform'
|
||||
import { plugin } from '@hcengineering/platform'
|
||||
import { ChatMessage } from '@hcengineering/chunter'
|
||||
|
||||
export * from './types'
|
||||
|
||||
export const aiBotId = 'ai-bot' as Plugin
|
||||
|
||||
export const aiBotAccountEmail = 'huly.ai.bot@hc.engineering'
|
||||
@@ -51,6 +53,9 @@ export interface TransferredMessage extends ChatMessage {
|
||||
}
|
||||
|
||||
const aiBot = plugin(aiBotId, {
|
||||
metadata: {
|
||||
EndpointURL: '' as Metadata<string>
|
||||
},
|
||||
class: {
|
||||
AIBotEvent: '' as Ref<Class<AIBotEvent>>,
|
||||
AIBotTransferEvent: '' as Ref<Class<AIBotTransferEvent>>,
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// 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 { Markup } from '@hcengineering/core'
|
||||
|
||||
export interface TranslateRequest {
|
||||
text: Markup
|
||||
lang: string
|
||||
}
|
||||
|
||||
export interface TranslateResponse {
|
||||
text: Markup
|
||||
lang: string
|
||||
}
|
||||
@@ -122,6 +122,10 @@
|
||||
"YouJoinedChannel": "You have been joined to channel",
|
||||
"AndMore": "and {count} more",
|
||||
"IsTyping": "{count, plural, =1 {is} other {are}} typing...",
|
||||
"ThreadIn": "thread in {name}"
|
||||
"ThreadIn": "thread in {name}",
|
||||
"TranslateMessage": "Translate message",
|
||||
"Translate": "Translate",
|
||||
"ShowOriginal": "Show original",
|
||||
"Translating": "Translating..."
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,10 @@
|
||||
"YouJoinedChannel": "Te has unido al canal",
|
||||
"AndMore": "y {count} más",
|
||||
"IsTyping": "está escribiendo...",
|
||||
"ThreadIn": "hilo en {name}"
|
||||
"ThreadIn": "hilo en {name}",
|
||||
"TranslateMessage": "Traducir mensaje",
|
||||
"Translate": "Traducir",
|
||||
"ShowOriginal": "Mostrar original",
|
||||
"Translating": "Traduciendo..."
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,10 @@
|
||||
"YouJoinedChannel": "Vous avez rejoint le canal",
|
||||
"AndMore": "et {count} de plus",
|
||||
"IsTyping": "est en train d'écrire...",
|
||||
"ThreadIn": "fil de discussion dans {name}"
|
||||
"ThreadIn": "fil de discussion dans {name}",
|
||||
"TranslateMessage": "Traduire le message",
|
||||
"Translate": "Traduire",
|
||||
"ShowOriginal": "Afficher l'original",
|
||||
"Translating": "Traduction en cours..."
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,10 @@
|
||||
"YouJoinedChannel": "Entrou no canal",
|
||||
"AndMore": "e mais {count}",
|
||||
"IsTyping": "está a escrever...",
|
||||
"ThreadIn": "Conversa em cadeia em {name}"
|
||||
"ThreadIn": "Conversa em cadeia em {name}",
|
||||
"TranslateMessage": "Traduzir mensagem",
|
||||
"Translate": "Traduzir",
|
||||
"ShowOriginal": "Mostrar original",
|
||||
"Translating": "A traduzir..."
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,10 @@
|
||||
"YouJoinedChannel": "Вы присоединились к каналу",
|
||||
"AndMore": "и еще {count}",
|
||||
"IsTyping": "{count, plural, =1 {печатает} other {печатают}}...",
|
||||
"ThreadIn": "Обсуждение в {name}"
|
||||
"ThreadIn": "Обсуждение в {name}",
|
||||
"TranslateMessage": "Перевести сообщение",
|
||||
"Translate": "Перевести",
|
||||
"ShowOriginal": "Показать оригинал",
|
||||
"Translating": "Перевод..."
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,10 @@
|
||||
"YouJoinedChannel": "你已加入频道",
|
||||
"AndMore": "和 {count} 人",
|
||||
"IsTyping": "正在输入...",
|
||||
"ThreadIn": "线程在 {name}"
|
||||
"ThreadIn": "线程在 {name}",
|
||||
"TranslateMessage": "翻译消息",
|
||||
"Translate": "翻译",
|
||||
"ShowOriginal": "显示原文",
|
||||
"Translating": "翻译中..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,11 @@
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/activity": "^0.6.0",
|
||||
"@hcengineering/activity-resources": "^0.6.1",
|
||||
"@hcengineering/ai-bot": "^0.6.0",
|
||||
"@hcengineering/ai-bot-resources": "^0.6.0",
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/attachment": "^0.6.14",
|
||||
"@hcengineering/attachment-resources": "^0.6.0",
|
||||
"@hcengineering/chunter": "^0.6.20",
|
||||
@@ -61,7 +63,6 @@
|
||||
"@hcengineering/workbench": "^0.6.16",
|
||||
"@hcengineering/workbench-resources": "^0.6.1",
|
||||
"fast-equals": "^5.0.1",
|
||||
"svelte": "^4.2.12",
|
||||
"html-to-text": "^9.0.3"
|
||||
"svelte": "^4.2.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,22 @@
|
||||
<script lang="ts">
|
||||
import contact, { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import { personAccountByIdStore, personByIdStore } from '@hcengineering/contact-resources'
|
||||
import { Class, Doc, getCurrentAccount, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import presentation, { createQuery, getClient, MessageViewer } from '@hcengineering/presentation'
|
||||
import { Class, Doc, getCurrentAccount, Markup, Ref, Space, WithLookup } from '@hcengineering/core'
|
||||
import { getClient, MessageViewer } from '@hcengineering/presentation'
|
||||
import { AttachmentDocList, AttachmentImageSize } from '@hcengineering/attachment-resources'
|
||||
import { getDocLinkTitle } from '@hcengineering/view-resources'
|
||||
import { Action, Button, IconEdit, ShowMore } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import activity, { ActivityMessage, ActivityMessageViewType, DisplayActivityMessage } from '@hcengineering/activity'
|
||||
import { ActivityDocLink, ActivityMessageTemplate } from '@hcengineering/activity-resources'
|
||||
import { ActivityDocLink, ActivityMessageTemplate, MessageInlineAction } from '@hcengineering/activity-resources'
|
||||
import chunter, { ChatMessage, ChatMessageViewlet, InlineButton } from '@hcengineering/chunter'
|
||||
import { Attachment } from '@hcengineering/attachment'
|
||||
import { getMetadata } from '@hcengineering/platform'
|
||||
import { EmptyMarkup } from '@hcengineering/text'
|
||||
|
||||
import ChatMessageHeader from './ChatMessageHeader.svelte'
|
||||
import ChatMessageInput from './ChatMessageInput.svelte'
|
||||
import InlineButtons from '../InlineButtons.svelte'
|
||||
import { translatedMessagesStore, translatingMessagesStore, shownTranslatedMessagesStore } from '../../stores'
|
||||
|
||||
export let value: WithLookup<ChatMessage> | undefined
|
||||
export let doc: Doc | undefined = undefined
|
||||
@@ -81,6 +82,26 @@
|
||||
$: account = accountId !== undefined ? $personAccountByIdStore.get(accountId as Ref<PersonAccount>) : undefined
|
||||
$: person = account?.person !== undefined ? $personByIdStore.get(account.person) : undefined
|
||||
|
||||
let originalText = value?.message
|
||||
|
||||
$: if (value && originalText && originalText !== value?.message) {
|
||||
originalText = value.message
|
||||
translatedMessagesStore.update((map) => {
|
||||
if (value) {
|
||||
map.delete(value._id)
|
||||
}
|
||||
return map
|
||||
})
|
||||
shownTranslatedMessagesStore.update((map) => {
|
||||
if (value) {
|
||||
map.delete(value._id)
|
||||
}
|
||||
return map
|
||||
})
|
||||
} else {
|
||||
originalText = value?.message
|
||||
}
|
||||
|
||||
$: value !== undefined &&
|
||||
getParentMessage(value.attachedToClass, value.attachedTo, value.space).then((res) => {
|
||||
parentMessage = res as DisplayActivityMessage
|
||||
@@ -147,9 +168,52 @@
|
||||
let inlineButtons: InlineButton[] = []
|
||||
$: inlineButtons = (value?.$lookup?.inlineButtons ?? []) as InlineButton[]
|
||||
|
||||
let inlineActions: MessageInlineAction[] = []
|
||||
|
||||
$: updateInlineActions($translatingMessagesStore, $shownTranslatedMessagesStore)
|
||||
function updateInlineActions (
|
||||
translatingMessages: Set<Ref<ChatMessage>>,
|
||||
shownTranslated: Set<Ref<ChatMessage>>
|
||||
): void {
|
||||
if (value === undefined) {
|
||||
inlineActions = []
|
||||
return
|
||||
}
|
||||
|
||||
const result: MessageInlineAction[] = []
|
||||
|
||||
if (translatingMessages.has(value._id)) {
|
||||
result.push({
|
||||
label: chunter.string.Translating
|
||||
})
|
||||
} else if (shownTranslated.has(value._id)) {
|
||||
result.push({
|
||||
label: chunter.string.ShowOriginal,
|
||||
onClick: async () => {
|
||||
shownTranslatedMessagesStore.update((map) => {
|
||||
if (value) {
|
||||
map.delete(value._id)
|
||||
}
|
||||
return map
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
inlineActions = result
|
||||
}
|
||||
|
||||
$: socialProvider = value?.provider
|
||||
? client.getModel().findAllSync(contact.class.ChannelProvider, { _id: value.provider })[0]
|
||||
: undefined
|
||||
|
||||
let displayText: Markup = value?.message ?? EmptyMarkup
|
||||
|
||||
$: if (value && $shownTranslatedMessagesStore.has(value._id)) {
|
||||
displayText = $translatedMessagesStore.get(value._id) ?? value?.message ?? EmptyMarkup
|
||||
} else {
|
||||
displayText = value?.message ?? EmptyMarkup
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if inline && object}
|
||||
@@ -181,8 +245,12 @@
|
||||
{skipLabel}
|
||||
{pending}
|
||||
{stale}
|
||||
excludedActions={$shownTranslatedMessagesStore.has(value._id)
|
||||
? [chunter.action.TranslateMessage]
|
||||
: [chunter.action.ShowOriginalMessage]}
|
||||
socialIcon={socialProvider?.icon}
|
||||
showDatePreposition={hideLink}
|
||||
{inlineActions}
|
||||
{type}
|
||||
{onClick}
|
||||
>
|
||||
@@ -194,7 +262,7 @@
|
||||
{#if withShowMore}
|
||||
<ShowMore limit={compact ? 80 : undefined}>
|
||||
<div class="clear-mins">
|
||||
<MessageViewer message={value.message} />
|
||||
<MessageViewer message={displayText} />
|
||||
{#if (value.attachments ?? 0) > 0 || (value.inlineButtons ?? 0) > 0}
|
||||
<div class="mt-2" />
|
||||
{/if}
|
||||
@@ -204,7 +272,7 @@
|
||||
</ShowMore>
|
||||
{:else}
|
||||
<div class="clear-mins">
|
||||
<MessageViewer message={value.message} />
|
||||
<MessageViewer message={displayText} />
|
||||
{#if (value.attachments ?? 0) > 0 || (value.inlineButtons ?? 0) > 0}
|
||||
<div class="mt-2" />
|
||||
{/if}
|
||||
|
||||
@@ -19,10 +19,9 @@
|
||||
import attachment, { Attachment } from '@hcengineering/attachment'
|
||||
import { AttachmentsTooltip } from '@hcengineering/attachment-resources'
|
||||
import { ChatMessage } from '@hcengineering/chunter'
|
||||
import core from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { Action, Icon, Label, tooltip } from '@hcengineering/ui'
|
||||
import { convert } from 'html-to-text'
|
||||
import { isEmptyMarkup } from '@hcengineering/text'
|
||||
|
||||
export let value: ChatMessage
|
||||
export let readonly = false
|
||||
@@ -46,22 +45,15 @@
|
||||
} else {
|
||||
attachmentsQuery.unsubscribe()
|
||||
}
|
||||
|
||||
$: text = value.message
|
||||
? convert(value.message, {
|
||||
preserveNewlines: false,
|
||||
selectors: [{ selector: 'img', format: 'skip' }]
|
||||
})
|
||||
: undefined
|
||||
</script>
|
||||
|
||||
<BaseMessagePreview text={text ? value.message : undefined} message={value} {type} {readonly} {actions} on:click>
|
||||
{#if value.attachments && type === 'full' && text}
|
||||
<BaseMessagePreview text={value.message} message={value} {type} {readonly} {actions} on:click>
|
||||
{#if value.attachments && type === 'full' && !isEmptyMarkup(value.message)}
|
||||
<div class="attachments" use:tooltip={{ component: AttachmentsTooltip, props: { attachments } }}>
|
||||
{value.attachments}
|
||||
<Icon icon={attachment.icon.Attachment} size="small" />
|
||||
</div>
|
||||
{:else if attachments.length > 0 && !text}
|
||||
{:else if attachments.length > 0 && isEmptyMarkup(value.message)}
|
||||
<span class="font-normal">
|
||||
<Label label={attachment.string.Attachments} />:
|
||||
<span class="ml-1">
|
||||
|
||||
@@ -75,7 +75,10 @@ import {
|
||||
getTitle,
|
||||
getUnreadThreadsCount,
|
||||
leaveChannelAction,
|
||||
removeChannelAction
|
||||
removeChannelAction,
|
||||
translateMessage,
|
||||
showOriginalMessage,
|
||||
canTranslateMessage
|
||||
} from './utils'
|
||||
|
||||
export { default as ChatMessageInput } from './components/chat-message/ChatMessageInput.svelte'
|
||||
@@ -196,7 +199,8 @@ export default async (): Promise<Resources> => ({
|
||||
CanReplyToThread: canReplyToThread,
|
||||
GetMessageLink: getMessageLocation,
|
||||
CloseChatWidgetTab: closeChatWidgetTab,
|
||||
OpenChannelInSidebar: openChannelInSidebar
|
||||
OpenChannelInSidebar: openChannelInSidebar,
|
||||
CanTranslateMessage: canTranslateMessage
|
||||
},
|
||||
actionImpl: {
|
||||
ArchiveChannel,
|
||||
@@ -207,6 +211,8 @@ export default async (): Promise<Resources> => ({
|
||||
RemoveChannel: removeChannelAction,
|
||||
ReplyToThread: replyToThread,
|
||||
OpenInSidebar: openChannelInSidebarAction,
|
||||
OpenInSidebarTab: openChannelInSidebarTabAction
|
||||
OpenInSidebarTab: openChannelInSidebarTabAction,
|
||||
TranslateMessage: translateMessage,
|
||||
ShowOriginalMessage: showOriginalMessage
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// 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 { writable } from 'svelte/store'
|
||||
import { type ChatMessage } from '@hcengineering/chunter'
|
||||
import { type Markup, type Ref } from '@hcengineering/core'
|
||||
import { languageStore } from '@hcengineering/ui'
|
||||
|
||||
export const translatingMessagesStore = writable<Set<Ref<ChatMessage>>>(new Set())
|
||||
export const translatedMessagesStore = writable<Map<Ref<ChatMessage>, Markup>>(new Map())
|
||||
export const shownTranslatedMessagesStore = writable<Set<Ref<ChatMessage>>>(new Set())
|
||||
|
||||
languageStore.subscribe(() => {
|
||||
translatedMessagesStore.set(new Map())
|
||||
shownTranslatedMessagesStore.set(new Set())
|
||||
})
|
||||
@@ -41,16 +41,19 @@ import {
|
||||
isActivityNotification,
|
||||
isMentionNotification
|
||||
} from '@hcengineering/notification-resources'
|
||||
import { translate, type Asset } from '@hcengineering/platform'
|
||||
import { translate, type Asset, getMetadata } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { type AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { type AnySvelteComponent, languageStore } from '@hcengineering/ui'
|
||||
import { classIcon, getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources'
|
||||
import { get, writable, type Unsubscriber } from 'svelte/store'
|
||||
import aiBot from '@hcengineering/ai-bot'
|
||||
import { translate as aiTranslate } from '@hcengineering/ai-bot-resources'
|
||||
|
||||
import ChannelIcon from './components/ChannelIcon.svelte'
|
||||
import DirectIcon from './components/DirectIcon.svelte'
|
||||
import { resetChunterLocIfEqual } from './navigation'
|
||||
import chunter from './plugin'
|
||||
import { shownTranslatedMessagesStore, translatedMessagesStore, translatingMessagesStore } from './stores'
|
||||
|
||||
export async function getDmName (client: Client, space?: Space): Promise<string> {
|
||||
if (space === undefined) {
|
||||
@@ -528,3 +531,39 @@ export function isThreadMessage (message: ActivityMessage): message is ThreadMes
|
||||
export function getChannelSpace (_class: Ref<Class<Doc>>, _id: Ref<Doc>, space: Ref<Space>): Ref<Space> {
|
||||
return getClient().getHierarchy().isDerived(_class, core.class.Space) ? (_id as Ref<Space>) : space
|
||||
}
|
||||
|
||||
export async function translateMessage (message: ChatMessage): Promise<void> {
|
||||
if (get(translatingMessagesStore).has(message._id)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (get(translatedMessagesStore).has(message._id)) {
|
||||
shownTranslatedMessagesStore.update((store) => store.add(message._id))
|
||||
return
|
||||
}
|
||||
|
||||
translatingMessagesStore.update((store) => store.add(message._id))
|
||||
const response = await aiTranslate(message.message, get(languageStore))
|
||||
|
||||
if (response !== undefined) {
|
||||
translatedMessagesStore.update((store) => store.set(message._id, response.text))
|
||||
shownTranslatedMessagesStore.update((store) => store.add(message._id))
|
||||
}
|
||||
|
||||
translatingMessagesStore.update((store) => {
|
||||
store.delete(message._id)
|
||||
return store
|
||||
})
|
||||
}
|
||||
|
||||
export async function showOriginalMessage (message: ChatMessage): Promise<void> {
|
||||
shownTranslatedMessagesStore.update((store) => {
|
||||
store.delete(message._id)
|
||||
return store
|
||||
})
|
||||
}
|
||||
|
||||
export async function canTranslateMessage (): Promise<boolean> {
|
||||
const url = getMetadata(aiBot.metadata.EndpointURL) ?? ''
|
||||
return url !== ''
|
||||
}
|
||||
|
||||
@@ -205,7 +205,11 @@ export default plugin(chunterId, {
|
||||
YouJoinedChannel: '' as IntlString,
|
||||
AndMore: '' as IntlString,
|
||||
IsTyping: '' as IntlString,
|
||||
ThreadIn: '' as IntlString
|
||||
ThreadIn: '' as IntlString,
|
||||
TranslateMessage: '' as IntlString,
|
||||
Translate: '' as IntlString,
|
||||
ShowOriginal: '' as IntlString,
|
||||
Translating: '' as IntlString
|
||||
},
|
||||
ids: {
|
||||
DMNotification: '' as Ref<NotificationType>,
|
||||
@@ -222,9 +226,12 @@ export default plugin(chunterId, {
|
||||
DeleteChatMessage: '' as Ref<Action>,
|
||||
LeaveChannel: '' as Ref<Action>,
|
||||
RemoveChannel: '' as Ref<Action>,
|
||||
TranslateMessage: '' as Ref<Action>,
|
||||
ShowOriginalMessage: '' as Ref<Action>,
|
||||
CloseConversation: '' as Ref<Action>
|
||||
},
|
||||
function: {
|
||||
CanTranslateMessage: '' as Resource<(doc?: Doc | Doc[]) => Promise<boolean>>,
|
||||
OpenChannelInSidebar: '' as Resource<
|
||||
(_id: Ref<Doc>, _class: Ref<Doc>, doc?: Doc, thread?: Ref<ActivityMessage>) => Promise<void>
|
||||
>
|
||||
|
||||
@@ -42,6 +42,6 @@
|
||||
"@hcengineering/core": "^0.6.32",
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"openai": "^4.56.0",
|
||||
"tiktoken": "^1.0.16"
|
||||
"js-tiktoken": "^1.0.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import OpenAI from 'openai'
|
||||
import { Tiktoken } from 'tiktoken'
|
||||
import { Tiktoken } from 'js-tiktoken'
|
||||
|
||||
// Based on https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
|
||||
export function countTokens (messages: OpenAI.ChatCompletionMessageParam[], encoding: Tiktoken): number {
|
||||
|
||||
@@ -183,4 +183,17 @@
|
||||
<symbol id="details-filled" viewBox="0 0 32 32">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 8.00049C2 5.79135 3.79086 4.00049 6 4.00049H26C28.2091 4.00049 30 5.79135 30 8.00049V24.0005C30 26.2096 28.2091 28.0005 26 28.0005H6C3.79086 28.0005 2 26.2096 2 24.0005V8.00049ZM20 6.00049H6C4.89543 6.00049 4 6.89592 4 8.00049V24.0005C4 25.1051 4.89543 26.0005 6 26.0005H20V6.00049Z"/>
|
||||
</symbol>
|
||||
<symbol id="translate" viewBox="0 0 32 32">
|
||||
<path d="M17,8V3a2,2,0,0,0-2-2H3A2,2,0,0,0,1,3V14a2,2,0,0,0,2,2H4v2.667L8,16h3" fill="none" stroke="currentColor"
|
||||
stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path d="M15,25V14a2,2,0,0,1,2-2H29a2,2,0,0,1,2,2V25a2,2,0,0,1-2,2H28v2.667L24,27H17A2,2,0,0,1,15,25Z"
|
||||
fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path d="M24.825,24,24.4,22.4H21.62L21.187,24H18.644l2.794-8.6h3.088l2.83,8.6Zm-.9-3.5-.369-1.406q-.129-.469-.314-1.213T23,16.811q-.053.3-.208.984T22.1,20.5Z"
|
||||
fill="currentColor" stroke="none"/>
|
||||
<path d="M13.685,6.453c-.44-.04-.83-.06-1.419-.06a7.85,7.85,0,0,1-.68,1.929,7.4,7.4,0,0,1-1.109,1.619,9.6,9.6,0,0,0,3.428,1.209,6.426,6.426,0,0,0-.99,1.839A12.35,12.35,0,0,1,9.107,11.17a12.776,12.776,0,0,1-3.978,1.839A5.793,5.793,0,0,0,4.17,11.24,10.869,10.869,0,0,0,7.718,9.931,8.7,8.7,0,0,1,6.429,8.1a8.77,8.77,0,0,1-.69-1.709c-.59,0-.929.02-1.429.06V4.644a12.321,12.321,0,0,0,1.449.07H7.968v-.6a3.017,3.017,0,0,0-.06-.619H9.917a3.068,3.068,0,0,0-.06.609v.61h2.378a12.226,12.226,0,0,0,1.45-.07Zm-6.027-.06a5.7,5.7,0,0,0,1.4,2.419,5.645,5.645,0,0,0,1.25-2.419Z"
|
||||
fill="currentColor" stroke="none"/>
|
||||
</symbol>
|
||||
<symbol id="undo" viewBox="0 0 32 32">
|
||||
<path d="M20 10H7.8149L11.4023 6.4141L10 5L4 11L10 17L11.4023 15.5854L7.8179 12H20C21.5913 12 23.1174 12.6321 24.2426 13.7574C25.3679 14.8826 26 16.4087 26 18C26 19.5913 25.3679 21.1174 24.2426 22.2426C23.1174 23.3679 21.5913 24 20 24H13C12.4477 24 12 24.4477 12 25C12 25.5523 12.4477 26 13 26H20C22.1217 26 24.1566 25.1571 25.6569 23.6569C27.1571 22.1566 28 20.1217 28 18C28 15.8783 27.1571 13.8434 25.6569 12.3431C24.1566 10.8429 22.1217 10 20 10Z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
@@ -56,5 +56,7 @@ loadMetadata(view.icon, {
|
||||
Join: `${icons}#join`,
|
||||
Leave: `${icons}#leave`,
|
||||
Copy: `${icons}#copy`,
|
||||
DetailsFilled: `${icons}#details-filled`
|
||||
DetailsFilled: `${icons}#details-filled`,
|
||||
Translate: `${icons}#translate`,
|
||||
Undo: `${icons}#undo`
|
||||
})
|
||||
|
||||
@@ -252,7 +252,9 @@ const view = plugin(viewId, {
|
||||
Leave: '' as Asset,
|
||||
Copy: '' as Asset,
|
||||
TodoList: '' as Asset,
|
||||
DetailsFilled: '' as Asset
|
||||
DetailsFilled: '' as Asset,
|
||||
Translate: '' as Asset,
|
||||
Undo: '' as Asset
|
||||
},
|
||||
category: {
|
||||
General: '' as Ref<ActionCategory>,
|
||||
|
||||
@@ -39,5 +39,6 @@ startFront(metricsContext, {
|
||||
DESKTOP_UPDATES_URL: process.env.DESKTOP_UPDATES_URL,
|
||||
DESKTOP_UPDATES_CHANNEL: process.env.DESKTOP_UPDATES_CHANNEL,
|
||||
ANALYTICS_COLLECTOR_URL: process.env.ANALYTICS_COLLECTOR_URL,
|
||||
AI_URL: process.env.AI_URL,
|
||||
TELEGRAM_BOT_URL: process.env.TELEGRAM_BOT_URL
|
||||
})
|
||||
|
||||
@@ -2081,6 +2081,11 @@
|
||||
"projectFolder": "plugins/ai-bot",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/ai-bot-resources",
|
||||
"projectFolder": "plugins/ai-bot-resources",
|
||||
"shouldPublish": false
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/model-server-ai-bot",
|
||||
"projectFolder": "models/server-ai-bot",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
FROM node:20-alpine
|
||||
FROM node:20
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
RUN npm install --ignore-scripts=false --verbose bufferutil utf-8-validate @mongodb-js/zstd snappy --unsafe-perm
|
||||
|
||||
COPY bundle/bundle.js ./
|
||||
COPY assets/avatar.png ./
|
||||
|
||||
EXPOSE 4008
|
||||
EXPOSE 4010
|
||||
CMD [ "node", "bundle.js" ]
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"mongodb": "^6.9.0",
|
||||
"node-fetch": "^2.6.6",
|
||||
"openai": "^4.56.0",
|
||||
"tiktoken": "^1.0.16",
|
||||
"js-tiktoken": "^1.0.14",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,54 +30,37 @@ interface Config {
|
||||
Password: string
|
||||
OpenAIKey: string
|
||||
OpenAIModel: OpenAI.ChatModel
|
||||
OpenAITranslateModel: OpenAI.ChatModel
|
||||
MaxContentTokens: number
|
||||
MaxHistoryRecords: number
|
||||
}
|
||||
|
||||
const envMap: { [key in keyof Config]: string } = {
|
||||
AccountsURL: 'ACCOUNTS_URL',
|
||||
ConfigurationDB: 'CONFIGURATION_DB',
|
||||
MongoURL: 'MONGO_URL',
|
||||
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',
|
||||
OpenAIKey: 'OPENAI_API_KEY',
|
||||
OpenAIModel: 'OPENAI_MODEL',
|
||||
MaxContentTokens: 'MAX_CONTENT_TOKENS',
|
||||
MaxHistoryRecords: 'MAX_HISTORY_RECORDS'
|
||||
Port: number
|
||||
}
|
||||
|
||||
const parseNumber = (str: string | undefined): number | undefined => (str !== undefined ? Number(str) : undefined)
|
||||
|
||||
const config: Config = (() => {
|
||||
const params: Partial<Config> = {
|
||||
AccountsURL: process.env[envMap.AccountsURL],
|
||||
ConfigurationDB: process.env[envMap.ConfigurationDB] ?? '%ai-bot',
|
||||
MongoURL: process.env[envMap.MongoURL],
|
||||
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',
|
||||
OpenAIKey: process.env[envMap.OpenAIKey],
|
||||
OpenAIModel: (process.env[envMap.OpenAIModel] ?? 'gpt-4o-mini') as OpenAI.ChatModel,
|
||||
MaxContentTokens: parseNumber(process.env[envMap.MaxContentTokens]) ?? 128 * 100,
|
||||
MaxHistoryRecords: parseNumber(process.env[envMap.MaxHistoryRecords]) ?? 500
|
||||
AccountsURL: process.env.ACCOUNTS_URL,
|
||||
ConfigurationDB: process.env.CONFIGURATION_DB ?? '%ai-bot',
|
||||
MongoURL: process.env.MONGO_URL,
|
||||
ServerSecret: process.env.SERVER_SECRET,
|
||||
ServiceID: process.env.SERVICE_ID ?? 'ai-bot-service',
|
||||
SupportWorkspace: process.env.SUPPORT_WORKSPACE,
|
||||
FirstName: process.env.FIRST_NAME,
|
||||
LastName: process.env.LAST_NAME,
|
||||
AvatarPath: process.env.AVATAR_PATH ?? './assets/avatar.png',
|
||||
AvatarName: process.env.AVATAR_NAME ?? 'huly_ai_bot_avatar',
|
||||
AvatarContentType: process.env.AVATAR_CONTENT_TYPE ?? '.png',
|
||||
Password: process.env.PASSWORD ?? 'password',
|
||||
OpenAIKey: process.env.OPENAI_API_KEY ?? '',
|
||||
OpenAIModel: (process.env.OPENAI_MODEL ?? 'gpt-4o-mini') as OpenAI.ChatModel,
|
||||
OpenAITranslateModel: (process.env.OPENAI_TRANSLATE_MODEL ?? 'gpt-4o-mini') as OpenAI.ChatModel,
|
||||
MaxContentTokens: parseNumber(process.env.MAX_CONTENT_TOKENS) ?? 128 * 100,
|
||||
MaxHistoryRecords: parseNumber(process.env.MAX_HISTORY_RECORDS) ?? 500,
|
||||
Port: parseNumber(process.env.PORT) ?? 4010
|
||||
}
|
||||
|
||||
const missingEnv = (Object.keys(params) as Array<keyof Config>)
|
||||
.filter((key) => params[key] === undefined)
|
||||
.map((key) => envMap[key])
|
||||
const missingEnv = (Object.keys(params) as Array<keyof Config>).filter((key) => params[key] === undefined)
|
||||
|
||||
if (missingEnv.length > 0) {
|
||||
throw Error(`Missing env variables: ${missingEnv.join(', ')}`)
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
//
|
||||
|
||||
import { MeasureContext, systemAccountEmail, isWorkspaceCreating } from '@hcengineering/core'
|
||||
import { aiBotAccountEmail, AIBotTransferEvent } from '@hcengineering/ai-bot'
|
||||
import { aiBotAccountEmail, AIBotTransferEvent, TranslateResponse, TranslateRequest } 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 OpenAI from 'openai'
|
||||
import { encoding_for_model } from 'tiktoken'
|
||||
import { encodingForModel } from 'js-tiktoken'
|
||||
import { htmlToMarkup, markupToHTML } from '@hcengineering/text'
|
||||
|
||||
import { WorkspaceClient } from './workspaceClient'
|
||||
import { assignBotToWorkspace, getWorkspaceInfo } from './account'
|
||||
@@ -39,8 +40,8 @@ export class AIBotController {
|
||||
|
||||
private readonly intervalId: NodeJS.Timeout
|
||||
|
||||
readonly aiClient: OpenAI
|
||||
readonly encoding = encoding_for_model(config.OpenAIModel)
|
||||
readonly aiClient?: OpenAI
|
||||
readonly encoding = encodingForModel(config.OpenAIModel)
|
||||
|
||||
assignTimeout: NodeJS.Timeout | undefined
|
||||
assignAttempts = 0
|
||||
@@ -49,7 +50,7 @@ export class AIBotController {
|
||||
readonly storage: DbStorage,
|
||||
private readonly ctx: MeasureContext
|
||||
) {
|
||||
this.aiClient = new OpenAI({ apiKey: config.OpenAIKey })
|
||||
this.aiClient = config.OpenAIKey === '' ? undefined : new OpenAI({ apiKey: config.OpenAIKey })
|
||||
|
||||
this.intervalId = setInterval(() => {
|
||||
void this.updateWorkspaceClients()
|
||||
@@ -199,8 +200,6 @@ export class AIBotController {
|
||||
async close (): Promise<void> {
|
||||
clearInterval(this.intervalId)
|
||||
|
||||
this.encoding.free()
|
||||
|
||||
for (const workspace of this.workspaces.values()) {
|
||||
await workspace.close()
|
||||
}
|
||||
@@ -213,6 +212,35 @@ export class AIBotController {
|
||||
async updateAvatarInfo (workspace: string, path: string, lastModified: number): Promise<void> {
|
||||
await this.storage.updateWorkspace(workspace, { $set: { avatarPath: path, avatarLastModified: lastModified } })
|
||||
}
|
||||
|
||||
async translate (req: TranslateRequest): Promise<TranslateResponse | undefined> {
|
||||
if (this.aiClient === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const html = markupToHTML(req.text)
|
||||
const start = Date.now()
|
||||
const response = await this.aiClient.chat.completions.create({
|
||||
model: config.OpenAITranslateModel,
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `Your task is to translate the text into ${req.lang} while preserving the html structure and metadata`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: html
|
||||
}
|
||||
]
|
||||
})
|
||||
const end = Date.now()
|
||||
this.ctx.info('Translation time: ', { time: end - start })
|
||||
const result = response.choices[0].message.content
|
||||
const text = result !== null ? htmlToMarkup(result) : req.text
|
||||
return {
|
||||
text,
|
||||
lang: req.lang
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function wait (delay: number): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
const generateMessage = (code: number): string => {
|
||||
if (code === 401) {
|
||||
return 'Unauthorized'
|
||||
}
|
||||
|
||||
if (code === 404) {
|
||||
return 'Not Found'
|
||||
}
|
||||
|
||||
if (code === 400) {
|
||||
return 'Bad Request'
|
||||
}
|
||||
|
||||
return 'Error'
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
readonly code: number
|
||||
|
||||
constructor (code: number, message?: string) {
|
||||
super(message ?? generateMessage(code))
|
||||
this.code = code
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// 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 { Token, decodeToken } from '@hcengineering/server-token'
|
||||
import cors from 'cors'
|
||||
import express, { type Express, type NextFunction, type Request, type Response } from 'express'
|
||||
import { IncomingHttpHeaders, type Server } from 'http'
|
||||
import { TranslateRequest } from '@hcengineering/ai-bot'
|
||||
|
||||
import { ApiError } from './error'
|
||||
import { AIBotController } from './controller'
|
||||
|
||||
const extractCookieToken = (cookie?: string): Token | null => {
|
||||
if (cookie === undefined || cookie === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const cookies = cookie.split(';')
|
||||
const tokenCookie = cookies.find((cookie) => cookie.toLocaleLowerCase().includes('token'))
|
||||
if (tokenCookie === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
const encodedToken = tokenCookie.split('=')[1]
|
||||
if (encodedToken === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return decodeToken(encodedToken)
|
||||
}
|
||||
|
||||
const extractAuthorizationToken = (authorization?: string): Token | null => {
|
||||
if (authorization === undefined || authorization === null) {
|
||||
return null
|
||||
}
|
||||
const encodedToken = authorization.split(' ')[1]
|
||||
|
||||
if (encodedToken === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return decodeToken(encodedToken)
|
||||
}
|
||||
|
||||
const extractToken = (headers: IncomingHttpHeaders): Token => {
|
||||
try {
|
||||
const token = extractCookieToken(headers.cookie) ?? extractAuthorizationToken(headers.authorization)
|
||||
|
||||
if (token === null) {
|
||||
throw new ApiError(401)
|
||||
}
|
||||
|
||||
return token
|
||||
} catch {
|
||||
throw new ApiError(401)
|
||||
}
|
||||
}
|
||||
|
||||
type AsyncRequestHandler = (req: Request, res: Response, token: Token, next: NextFunction) => Promise<void>
|
||||
|
||||
const handleRequest = async (
|
||||
fn: AsyncRequestHandler,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> => {
|
||||
try {
|
||||
const token = extractToken(req.headers)
|
||||
await fn(req, res, token, next)
|
||||
} catch (err: unknown) {
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
const wrapRequest = (fn: AsyncRequestHandler) => (req: Request, res: Response, next: NextFunction) => {
|
||||
void handleRequest(fn, req, res, next)
|
||||
}
|
||||
|
||||
export function createServer (controller: AIBotController): Express {
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
app.post(
|
||||
'/translate',
|
||||
wrapRequest(async (req, res) => {
|
||||
if (req.body == null || Array.isArray(req.body) || typeof req.body !== 'object') {
|
||||
throw new ApiError(400)
|
||||
}
|
||||
const response = await controller.translate(req.body as TranslateRequest)
|
||||
if (response === undefined) {
|
||||
throw new ApiError(500)
|
||||
}
|
||||
|
||||
res.status(200)
|
||||
res.json(response)
|
||||
})
|
||||
)
|
||||
|
||||
app.use((err: any, _req: any, res: any, _next: any) => {
|
||||
console.log(err)
|
||||
if (err instanceof ApiError) {
|
||||
res.status(err.code).send({ code: err.code, message: err.message })
|
||||
return
|
||||
}
|
||||
|
||||
res.status(500).send(err.message?.length > 0 ? { message: err.message } : err)
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export function listen (e: Express, port: number, host?: string): Server {
|
||||
const cb = (): void => {
|
||||
console.log(`AI service has been started at ${host ?? '*'}:${port}`)
|
||||
}
|
||||
|
||||
return host !== undefined ? e.listen(port, host, cb) : e.listen(port, cb)
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import { closeDB, DbStorage, getDB } from './storage'
|
||||
import { AIBotController } from './controller'
|
||||
import { createBotAccount } from './account'
|
||||
import { registerLoaders } from './loaders'
|
||||
import { createServer, listen } from './server'
|
||||
|
||||
export const start = async (): Promise<void> => {
|
||||
setMetadata(serverToken.metadata.Secret, config.ServerSecret)
|
||||
@@ -49,10 +50,13 @@ export const start = async (): Promise<void> => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
}
|
||||
const aiController = new AIBotController(storage, ctx)
|
||||
const app = createServer(aiController)
|
||||
const server = listen(app, config.Port)
|
||||
|
||||
const onClose = (): void => {
|
||||
void aiController.close()
|
||||
void closeDB()
|
||||
server.close(() => process.exit())
|
||||
}
|
||||
|
||||
process.on('SIGINT', onClose)
|
||||
|
||||
@@ -22,7 +22,7 @@ import { deepEqual } from 'fast-equals'
|
||||
import notification from '@hcengineering/notification'
|
||||
import OpenAI from 'openai'
|
||||
import { countTokens } from '@hcengineering/openai'
|
||||
import { Tiktoken } from 'tiktoken'
|
||||
import { Tiktoken } from 'js-tiktoken'
|
||||
|
||||
import { HistoryRecord } from './types'
|
||||
import config from './config'
|
||||
|
||||
@@ -366,6 +366,7 @@ export class WorkspaceClient {
|
||||
objectId: Ref<Doc>,
|
||||
objectClass: Ref<Class<Doc>>
|
||||
): Promise<void> {
|
||||
if (this.controller.aiClient === undefined) return
|
||||
if (this.summarizing.has(objectId)) {
|
||||
return
|
||||
}
|
||||
@@ -422,6 +423,7 @@ export class WorkspaceClient {
|
||||
}
|
||||
|
||||
async processResponseEvent (event: AIBotResponseEvent): Promise<void> {
|
||||
if (this.controller.aiClient === undefined) return
|
||||
const client = await this.opClient
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user