mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-24 02:25:00 +02:00
Add translate message action (#6609)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
@@ -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 !== ''
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user