From 8d9cebdd67f4de2858d605c4a4e1f92674a6dd1f Mon Sep 17 00:00:00 2001 From: Kristina Date: Thu, 19 Sep 2024 19:35:18 +0400 Subject: [PATCH] Add translate message action (#6609) Signed-off-by: Kristina Fefelova --- .vscode/launch.json | 2 +- common/config/rush/pnpm-lock.yaml | 80 +++++++++-- desktop/package.json | 2 + desktop/src/ui/platform.ts | 3 + desktop/src/ui/types.ts | 1 + dev/docker-compose.yaml | 2 + dev/prod/.env | 1 + dev/prod/config.json | 3 +- dev/prod/package.json | 2 + dev/prod/public/config.json | 1 + dev/prod/src/platform.ts | 4 + models/chunter/src/actions.ts | 76 +++++++--- models/chunter/src/plugin.ts | 4 +- packages/theme/src/index.ts | 4 +- packages/ui/src/index.ts | 2 +- .../components/ActivityMessageActions.svelte | 16 +-- .../ActivityMessageTemplate.svelte | 16 ++- .../activity-message/InlineAction.svelte | 45 ++++++ plugins/activity-resources/src/index.ts | 1 + plugins/activity-resources/src/types.ts | 20 +++ plugins/ai-bot-resources/.eslintrc.js | 4 + plugins/ai-bot-resources/.prettierrc | 22 +++ plugins/ai-bot-resources/config/rig.json | 5 + plugins/ai-bot-resources/jest.config.js | 5 + plugins/ai-bot-resources/package.json | 47 +++++++ plugins/ai-bot-resources/postcss.config.js | 5 + plugins/ai-bot-resources/src/index.ts | 20 +++ plugins/ai-bot-resources/src/plugin.ts | 19 +++ plugins/ai-bot-resources/src/utils.ts | 49 +++++++ plugins/ai-bot-resources/svelte.config.js | 5 + plugins/ai-bot-resources/tsconfig.json | 9 ++ plugins/ai-bot/src/index.ts | 7 +- plugins/ai-bot/src/types.ts | 26 ++++ plugins/chunter-assets/lang/en.json | 6 +- plugins/chunter-assets/lang/es.json | 6 +- plugins/chunter-assets/lang/fr.json | 6 +- plugins/chunter-assets/lang/pt.json | 6 +- plugins/chunter-assets/lang/ru.json | 6 +- plugins/chunter-assets/lang/zh.json | 6 +- plugins/chunter-resources/package.json | 7 +- .../chat-message/ChatMessagePresenter.svelte | 80 ++++++++++- .../chat-message/ChatMessagePreview.svelte | 16 +-- plugins/chunter-resources/src/index.ts | 12 +- plugins/chunter-resources/src/stores.ts | 28 ++++ plugins/chunter-resources/src/utils.ts | 43 +++++- plugins/chunter/src/index.ts | 9 +- plugins/openai/package.json | 2 +- plugins/openai/src/utils.ts | 2 +- plugins/view-assets/assets/icons.svg | 13 ++ plugins/view-assets/src/index.ts | 4 +- plugins/view/src/index.ts | 4 +- pods/front/src/__start.ts | 1 + rush.json | 5 + services/ai-bot/pod-ai-bot/Dockerfile | 5 +- services/ai-bot/pod-ai-bot/package.json | 2 +- services/ai-bot/pod-ai-bot/src/config.ts | 59 +++----- services/ai-bot/pod-ai-bot/src/controller.ts | 42 +++++- services/ai-bot/pod-ai-bot/src/error.ts | 39 ++++++ services/ai-bot/pod-ai-bot/src/server.ts | 131 ++++++++++++++++++ services/ai-bot/pod-ai-bot/src/start.ts | 4 + services/ai-bot/pod-ai-bot/src/utils.ts | 2 +- .../ai-bot/pod-ai-bot/src/workspaceClient.ts | 2 + 62 files changed, 926 insertions(+), 130 deletions(-) create mode 100644 plugins/activity-resources/src/components/activity-message/InlineAction.svelte create mode 100644 plugins/activity-resources/src/types.ts create mode 100644 plugins/ai-bot-resources/.eslintrc.js create mode 100644 plugins/ai-bot-resources/.prettierrc create mode 100644 plugins/ai-bot-resources/config/rig.json create mode 100644 plugins/ai-bot-resources/jest.config.js create mode 100644 plugins/ai-bot-resources/package.json create mode 100644 plugins/ai-bot-resources/postcss.config.js create mode 100644 plugins/ai-bot-resources/src/index.ts create mode 100644 plugins/ai-bot-resources/src/plugin.ts create mode 100644 plugins/ai-bot-resources/src/utils.ts create mode 100644 plugins/ai-bot-resources/svelte.config.js create mode 100644 plugins/ai-bot-resources/tsconfig.json create mode 100644 plugins/ai-bot/src/types.ts create mode 100644 plugins/chunter-resources/src/stores.ts create mode 100644 services/ai-bot/pod-ai-bot/src/error.ts create mode 100644 services/ai-bot/pod-ai-bot/src/server.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 0e62f4c7eb..8187a32d84 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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", diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 2155209fa8..8707f8ddc5 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -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 diff --git a/desktop/package.json b/desktop/package.json index d9f6818950..1e715af5b2 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -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", diff --git a/desktop/src/ui/platform.ts b/desktop/src/ui/platform.ts index 6a2e061efa..359874b35f 100644 --- a/desktop/src/ui/platform.ts +++ b/desktop/src/ui/platform.ts @@ -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 { 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 { 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')) diff --git a/desktop/src/ui/types.ts b/desktop/src/ui/types.ts index 9dd49655dc..5cce9aa6b7 100644 --- a/desktop/src/ui/types.ts +++ b/desktop/src/ui/types.ts @@ -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 diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index d5c15cd24c..2749b47499 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -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 diff --git a/dev/prod/.env b/dev/prod/.env index cbfc478199..15eafa18bd 100644 --- a/dev/prod/.env +++ b/dev/prod/.env @@ -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 diff --git a/dev/prod/config.json b/dev/prod/config.json index a29f38cbea..f03fb9b333 100644 --- a/dev/prod/config.json +++ b/dev/prod/config.json @@ -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" } \ No newline at end of file diff --git a/dev/prod/package.json b/dev/prod/package.json index 96ac3bcdc5..652bed4f33 100644 --- a/dev/prod/package.json +++ b/dev/prod/package.json @@ -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" } diff --git a/dev/prod/public/config.json b/dev/prod/public/config.json index 9b3e3ce052..c47ee2907a 100644 --- a/dev/prod/public/config.json +++ b/dev/prod/public/config.json @@ -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 diff --git a/dev/prod/src/platform.ts b/dev/prod/src/platform.ts index 6f8c48b418..a43cf316fc 100644 --- a/dev/prod/src/platform.ts +++ b/dev/prod/src/platform.ts @@ -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')) diff --git a/models/chunter/src/actions.ts b/models/chunter/src/actions.ts index 0bdd338601..e2d65dd7d8 100644 --- a/models/chunter/src/actions.ts +++ b/models/chunter/src/actions.ts @@ -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, { diff --git a/models/chunter/src/plugin.ts b/models/chunter/src/plugin.ts index 9f21394ddd..e85892870e 100644 --- a/models/chunter/src/plugin.ts +++ b/models/chunter/src/plugin.ts @@ -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 diff --git a/packages/theme/src/index.ts b/packages/theme/src/index.ts index c99db4542e..da5c196b00 100644 --- a/packages/theme/src/index.ts +++ b/packages/theme/src/index.ts @@ -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) diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 4322db40f7..c17ff91f03 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -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' diff --git a/plugins/activity-resources/src/components/ActivityMessageActions.svelte b/plugins/activity-resources/src/components/ActivityMessageActions.svelte index a0e941d563..62129d1bf8 100644 --- a/plugins/activity-resources/src/components/ActivityMessageActions.svelte +++ b/plugins/activity-resources/src/components/ActivityMessageActions.svelte @@ -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[] = [] 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 { + async function updateInlineActions (message?: ActivityMessage, excludedAction: Ref[] = []): Promise { 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)) } diff --git a/plugins/activity-resources/src/components/activity-message/ActivityMessageTemplate.svelte b/plugins/activity-resources/src/components/activity-message/ActivityMessageTemplate.svelte index 1da1094087..2b84bf79ac 100644 --- a/plugins/activity-resources/src/components/activity-message/ActivityMessageTemplate.svelte +++ b/plugins/activity-resources/src/components/activity-message/ActivityMessageTemplate.svelte @@ -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[] = [] export let onClick: (() => void) | undefined = undefined export let socialIcon: Asset | undefined = undefined @@ -223,6 +228,14 @@ + + {#if withActions && inlineActions.length > 0 && !readonly} +
+ {#each inlineActions as item} + + {/each} +
+ {/if} {/if} @@ -244,6 +257,7 @@ message={isReactionMessage(message) ? parentMessage : message} {actions} {withActionMenu} + {excludedActions} onOpen={handleActionsOpened} onClose={handleActionsClosed} /> diff --git a/plugins/activity-resources/src/components/activity-message/InlineAction.svelte b/plugins/activity-resources/src/components/activity-message/InlineAction.svelte new file mode 100644 index 0000000000..28c9ae8938 --- /dev/null +++ b/plugins/activity-resources/src/components/activity-message/InlineAction.svelte @@ -0,0 +1,45 @@ + + + + + +
+
+ + diff --git a/plugins/activity-resources/src/index.ts b/plugins/activity-resources/src/index.ts index 31885dc2ca..3214143f1f 100644 --- a/plugins/activity-resources/src/index.ts +++ b/plugins/activity-resources/src/index.ts @@ -41,6 +41,7 @@ import { shouldScrollToActivity } from './utils' +export * from './types' export * from './activity' export * from './utils' export * from './activityMessagesUtils' diff --git a/plugins/activity-resources/src/types.ts b/plugins/activity-resources/src/types.ts new file mode 100644 index 0000000000..12e717cc39 --- /dev/null +++ b/plugins/activity-resources/src/types.ts @@ -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 +} diff --git a/plugins/ai-bot-resources/.eslintrc.js b/plugins/ai-bot-resources/.eslintrc.js new file mode 100644 index 0000000000..bb8fd7450d --- /dev/null +++ b/plugins/ai-bot-resources/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'], + parserOptions: { tsconfigRootDir: __dirname } +} diff --git a/plugins/ai-bot-resources/.prettierrc b/plugins/ai-bot-resources/.prettierrc new file mode 100644 index 0000000000..792942803a --- /dev/null +++ b/plugins/ai-bot-resources/.prettierrc @@ -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" + } + } + ] +} \ No newline at end of file diff --git a/plugins/ai-bot-resources/config/rig.json b/plugins/ai-bot-resources/config/rig.json new file mode 100644 index 0000000000..bcad6f7c33 --- /dev/null +++ b/plugins/ai-bot-resources/config/rig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json", + "rigPackageName": "@hcengineering/platform-rig", + "rigProfile": "ui" +} diff --git a/plugins/ai-bot-resources/jest.config.js b/plugins/ai-bot-resources/jest.config.js new file mode 100644 index 0000000000..3656e284d3 --- /dev/null +++ b/plugins/ai-bot-resources/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'] +} diff --git a/plugins/ai-bot-resources/package.json b/plugins/ai-bot-resources/package.json new file mode 100644 index 0000000000..62255ea50c --- /dev/null +++ b/plugins/ai-bot-resources/package.json @@ -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" + } +} diff --git a/plugins/ai-bot-resources/postcss.config.js b/plugins/ai-bot-resources/postcss.config.js new file mode 100644 index 0000000000..88752c6cb0 --- /dev/null +++ b/plugins/ai-bot-resources/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + require('autoprefixer') + ] +} diff --git a/plugins/ai-bot-resources/src/index.ts b/plugins/ai-bot-resources/src/index.ts new file mode 100644 index 0000000000..b77d598b4b --- /dev/null +++ b/plugins/ai-bot-resources/src/index.ts @@ -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 => ({}) diff --git a/plugins/ai-bot-resources/src/plugin.ts b/plugins/ai-bot-resources/src/plugin.ts new file mode 100644 index 0000000000..4a177fdf09 --- /dev/null +++ b/plugins/ai-bot-resources/src/plugin.ts @@ -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, {}) diff --git a/plugins/ai-bot-resources/src/utils.ts b/plugins/ai-bot-resources/src/utils.ts new file mode 100644 index 0000000000..718050bd86 --- /dev/null +++ b/plugins/ai-bot-resources/src/utils.ts @@ -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 { + 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 + } +} diff --git a/plugins/ai-bot-resources/svelte.config.js b/plugins/ai-bot-resources/svelte.config.js new file mode 100644 index 0000000000..944a06f73e --- /dev/null +++ b/plugins/ai-bot-resources/svelte.config.js @@ -0,0 +1,5 @@ +const sveltePreprocess = require('svelte-preprocess') + +module.exports = { + preprocess: sveltePreprocess() +}; \ No newline at end of file diff --git a/plugins/ai-bot-resources/tsconfig.json b/plugins/ai-bot-resources/tsconfig.json new file mode 100644 index 0000000000..fb6ccb2d18 --- /dev/null +++ b/plugins/ai-bot-resources/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json", + + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "declarationDir": "./types" + } +} \ No newline at end of file diff --git a/plugins/ai-bot/src/index.ts b/plugins/ai-bot/src/index.ts index c612e4dd6f..cdcbb332b2 100644 --- a/plugins/ai-bot/src/index.ts +++ b/plugins/ai-bot/src/index.ts @@ -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 + }, class: { AIBotEvent: '' as Ref>, AIBotTransferEvent: '' as Ref>, diff --git a/plugins/ai-bot/src/types.ts b/plugins/ai-bot/src/types.ts new file mode 100644 index 0000000000..9485ce67fe --- /dev/null +++ b/plugins/ai-bot/src/types.ts @@ -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 +} diff --git a/plugins/chunter-assets/lang/en.json b/plugins/chunter-assets/lang/en.json index 93a64935d1..7d61091832 100644 --- a/plugins/chunter-assets/lang/en.json +++ b/plugins/chunter-assets/lang/en.json @@ -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..." } } \ No newline at end of file diff --git a/plugins/chunter-assets/lang/es.json b/plugins/chunter-assets/lang/es.json index b01ab9555b..1fef286c8a 100644 --- a/plugins/chunter-assets/lang/es.json +++ b/plugins/chunter-assets/lang/es.json @@ -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..." } } \ No newline at end of file diff --git a/plugins/chunter-assets/lang/fr.json b/plugins/chunter-assets/lang/fr.json index df3213a5f8..e5415a0cd1 100644 --- a/plugins/chunter-assets/lang/fr.json +++ b/plugins/chunter-assets/lang/fr.json @@ -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..." } } \ No newline at end of file diff --git a/plugins/chunter-assets/lang/pt.json b/plugins/chunter-assets/lang/pt.json index fcc921fd47..0b2c5a6425 100644 --- a/plugins/chunter-assets/lang/pt.json +++ b/plugins/chunter-assets/lang/pt.json @@ -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..." } } \ No newline at end of file diff --git a/plugins/chunter-assets/lang/ru.json b/plugins/chunter-assets/lang/ru.json index 0d192d3808..0d62dcce64 100644 --- a/plugins/chunter-assets/lang/ru.json +++ b/plugins/chunter-assets/lang/ru.json @@ -122,6 +122,10 @@ "YouJoinedChannel": "Вы присоединились к каналу", "AndMore": "и еще {count}", "IsTyping": "{count, plural, =1 {печатает} other {печатают}}...", - "ThreadIn": "Обсуждение в {name}" + "ThreadIn": "Обсуждение в {name}", + "TranslateMessage": "Перевести сообщение", + "Translate": "Перевести", + "ShowOriginal": "Показать оригинал", + "Translating": "Перевод..." } } \ No newline at end of file diff --git a/plugins/chunter-assets/lang/zh.json b/plugins/chunter-assets/lang/zh.json index 72c31ff3df..84b7dbf3f8 100644 --- a/plugins/chunter-assets/lang/zh.json +++ b/plugins/chunter-assets/lang/zh.json @@ -122,6 +122,10 @@ "YouJoinedChannel": "你已加入频道", "AndMore": "和 {count} 人", "IsTyping": "正在输入...", - "ThreadIn": "线程在 {name}" + "ThreadIn": "线程在 {name}", + "TranslateMessage": "翻译消息", + "Translate": "翻译", + "ShowOriginal": "显示原文", + "Translating": "翻译中..." } } diff --git a/plugins/chunter-resources/package.json b/plugins/chunter-resources/package.json index ecef85207d..59d130c8fa 100644 --- a/plugins/chunter-resources/package.json +++ b/plugins/chunter-resources/package.json @@ -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" } } diff --git a/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte b/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte index 04852080f4..dffc590c20 100644 --- a/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte +++ b/plugins/chunter-resources/src/components/chat-message/ChatMessagePresenter.svelte @@ -15,21 +15,22 @@ {#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}
- + {#if (value.attachments ?? 0) > 0 || (value.inlineButtons ?? 0) > 0}
{/if} @@ -204,7 +272,7 @@ {:else}
- + {#if (value.attachments ?? 0) > 0 || (value.inlineButtons ?? 0) > 0}
{/if} diff --git a/plugins/chunter-resources/src/components/chat-message/ChatMessagePreview.svelte b/plugins/chunter-resources/src/components/chat-message/ChatMessagePreview.svelte index dea5359773..71f019cbff 100644 --- a/plugins/chunter-resources/src/components/chat-message/ChatMessagePreview.svelte +++ b/plugins/chunter-resources/src/components/chat-message/ChatMessagePreview.svelte @@ -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 - - {#if value.attachments && type === 'full' && text} + + {#if value.attachments && type === 'full' && !isEmptyMarkup(value.message)}
{value.attachments}
- {:else if attachments.length > 0 && !text} + {:else if attachments.length > 0 && isEmptyMarkup(value.message)}