Meeting attach record (#7196)

This commit is contained in:
Kristina 2024-11-19 15:12:41 +04:00 committed by GitHub
parent a374f22acc
commit 13c03e3d24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 89 additions and 19 deletions

View File

@ -108,6 +108,7 @@ export class TRoom extends TDoc implements Room {
language!: RoomLanguage language!: RoomLanguage
startWithTranscription!: boolean startWithTranscription!: boolean
startWithRecording!: boolean
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files }) @Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number attachments?: number

View File

@ -89,6 +89,7 @@ async function createReception (client: MigrationUpgradeClient): Promise<void> {
y: 0, y: 0,
language: 'en', language: 'en',
startWithTranscription: false, startWithTranscription: false,
startWithRecording: false,
description: makeCollaborativeDoc(love.ids.Reception, 'description') description: makeCollaborativeDoc(love.ids.Reception, 'description')
}, },
love.ids.Reception love.ids.Reception
@ -105,7 +106,7 @@ export const loveOperation: MigrateOperation = {
} }
}, },
{ {
state: 'setup-defaults-settings', state: 'setup-defaults-settings-v2',
func: async (client: MigrationClient) => { func: async (client: MigrationClient) => {
await client.update( await client.update(
DOMAIN_LOVE, DOMAIN_LOVE,
@ -132,6 +133,21 @@ export const loveOperation: MigrateOperation = {
{ _class: love.class.Office, startWithTranscription: { $exists: false } }, { _class: love.class.Office, startWithTranscription: { $exists: false } },
{ startWithTranscription: false } { startWithTranscription: false }
) )
await client.update(
DOMAIN_LOVE,
{ _class: love.class.Room, type: RoomType.Video, startWithRecording: { $exists: false } },
{ startWithRecording: true }
)
await client.update(
DOMAIN_LOVE,
{ _class: love.class.Room, startWithRecording: { $exists: false } },
{ startWithRecording: false }
)
await client.update(
DOMAIN_LOVE,
{ _class: love.class.Office, startWithRecording: { $exists: false } },
{ startWithRecording: false }
)
} }
}, },
{ {

View File

@ -77,6 +77,7 @@
"MeetingEnd": "Meeting end", "MeetingEnd": "Meeting end",
"Status": "Status", "Status": "Status",
"Active": "Active", "Active": "Active",
"Finished": "Finished" "Finished": "Finished",
"StartWithRecording": "Start with recording"
} }
} }

View File

@ -77,6 +77,7 @@
"MeetingEnd": "Fin de la reunión", "MeetingEnd": "Fin de la reunión",
"Status": "Estado", "Status": "Estado",
"Active": "Activo", "Active": "Activo",
"Finished": "Terminado" "Finished": "Terminado",
"StartWithRecording": "Iniciar con grabación"
} }
} }

View File

@ -77,6 +77,7 @@
"MeetingEnd": "Fin de la réunion", "MeetingEnd": "Fin de la réunion",
"Status": "Statut", "Status": "Statut",
"Active": "Actif", "Active": "Actif",
"Finished": "Terminé" "Finished": "Terminé",
"StartWithRecording": "Démarrer avec l'enregistrement"
} }
} }

View File

@ -77,6 +77,7 @@
"MeetingEnd": "Fine riunione", "MeetingEnd": "Fine riunione",
"Status": "Stato", "Status": "Stato",
"Active": "Attivo", "Active": "Attivo",
"Finished": "Finito" "Finished": "Finito",
"StartWithRecording": "Inizia con la registrazione"
} }
} }

View File

@ -77,6 +77,7 @@
"MeetingEnd": "Fim da reunião", "MeetingEnd": "Fim da reunião",
"Status": "Estado", "Status": "Estado",
"Active": "Ativo", "Active": "Ativo",
"Finished": "Finalizado" "Finished": "Finalizado",
"StartWithRecording": "Começar com gravação"
} }
} }

View File

@ -77,6 +77,7 @@
"MeetingEnd": "Конец встречи", "MeetingEnd": "Конец встречи",
"Status": "Статус", "Status": "Статус",
"Active": "Активно", "Active": "Активно",
"Finished": "Завершено" "Finished": "Завершено",
"StartWithRecording": "Начинать с записью"
} }
} }

View File

@ -77,6 +77,7 @@
"MeetingEnd": "会议结束", "MeetingEnd": "会议结束",
"Status": "状态", "Status": "状态",
"Active": "活动", "Active": "活动",
"Finished": "已完成" "Finished": "已完成",
"StartWithRecording": "开始录制"
} }
} }

View File

@ -63,6 +63,7 @@
access: val.access, access: val.access,
language: 'en', language: 'en',
startWithTranscription: val._class !== love.class.Office && val.type === RoomType.Video, startWithTranscription: val._class !== love.class.Office && val.type === RoomType.Video,
startWithRecording: val._class !== love.class.Office && val.type === RoomType.Video,
description: makeCollaborativeDoc(_id, 'description') description: makeCollaborativeDoc(_id, 'description')
} }
if (val._class === love.class.Office) { if (val._class === love.class.Office) {

View File

@ -26,6 +26,10 @@
async function toggleTranscribing (): Promise<void> { async function toggleTranscribing (): Promise<void> {
await client.diffUpdate(room, { startWithTranscription: !room.startWithTranscription }) await client.diffUpdate(room, { startWithTranscription: !room.startWithTranscription })
} }
async function toggleRecording (): Promise<void> {
await client.diffUpdate(room, { startWithRecording: !room.startWithRecording })
}
</script> </script>
<div class="antiGrid"> <div class="antiGrid">
@ -41,4 +45,10 @@
</div> </div>
<ModernToggle size="small" checked={room.startWithTranscription} on:change={toggleTranscribing} /> <ModernToggle size="small" checked={room.startWithTranscription} on:change={toggleTranscribing} />
</div> </div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={love.string.StartWithRecording} />
</div>
<ModernToggle size="small" checked={room.startWithRecording} on:change={toggleRecording} />
</div>
</div> </div>

View File

@ -420,6 +420,10 @@ function initRoomMetadata (metadata: string | undefined): void {
) { ) {
void startTranscription(room) void startTranscription(room)
} }
if (get(isRecordingAvailable) && data.recording == null && room?.startWithRecording === true) {
void record(room)
}
} }
export async function connect (name: string, room: Room, _id: string): Promise<void> { export async function connect (name: string, room: Room, _id: string): Promise<void> {
const wsURL = getMetadata(love.metadata.WebSocketURL) const wsURL = getMetadata(love.metadata.WebSocketURL)
@ -869,7 +873,7 @@ export async function record (room: Room): Promise<void> {
Authorization: 'Bearer ' + token, Authorization: 'Bearer ' + token,
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ roomName, room: room.name }) body: JSON.stringify({ roomName, room: room.name, meetingMinutes: get(currentMeetingMinutes)?._id })
}) })
} }
} catch (err: any) { } catch (err: any) {

View File

@ -102,6 +102,7 @@ export interface Room extends Doc {
y: number y: number
language: RoomLanguage language: RoomLanguage
startWithTranscription: boolean startWithTranscription: boolean
startWithRecording: boolean
description: CollaborativeDoc description: CollaborativeDoc
attachments?: number attachments?: number
meetings?: number meetings?: number
@ -225,7 +226,8 @@ const love = plugin(loveId, {
MeetingEnd: '' as IntlString, MeetingEnd: '' as IntlString,
Status: '' as IntlString, Status: '' as IntlString,
Active: '' as IntlString, Active: '' as IntlString,
Finished: '' as IntlString Finished: '' as IntlString,
StartWithRecording: '' as IntlString
}, },
ids: { ids: {
MainFloor: '' as Ref<Floor>, MainFloor: '' as Ref<Floor>,

View File

@ -33,6 +33,7 @@ export function createDefaultRooms (employees: Ref<Employee>[]): (Data<Room | Of
person: employees[index] ?? null, person: employees[index] ?? null,
language: 'en', language: 'en',
startWithTranscription: false, startWithTranscription: false,
startWithRecording: false,
description: makeCollaborativeDoc(_id, 'description') description: makeCollaborativeDoc(_id, 'description')
} }
res.push(office) res.push(office)
@ -51,6 +52,7 @@ export function createDefaultRooms (employees: Ref<Employee>[]): (Data<Room | Of
y: 0, y: 0,
language: 'en', language: 'en',
startWithTranscription: true, startWithTranscription: true,
startWithRecording: true,
description: makeCollaborativeDoc(allHands, 'description') description: makeCollaborativeDoc(allHands, 'description')
}) })
@ -67,6 +69,7 @@ export function createDefaultRooms (employees: Ref<Employee>[]): (Data<Room | Of
y: 4, y: 4,
language: 'en', language: 'en',
startWithTranscription: true, startWithTranscription: true,
startWithRecording: true,
description: makeCollaborativeDoc(meetingRoom1, 'description') description: makeCollaborativeDoc(meetingRoom1, 'description')
}) })
const meetingRoom2 = generateId<Room>() const meetingRoom2 = generateId<Room>()
@ -82,6 +85,7 @@ export function createDefaultRooms (employees: Ref<Employee>[]): (Data<Room | Of
y: 4, y: 4,
language: 'en', language: 'en',
startWithTranscription: true, startWithTranscription: true,
startWithRecording: true,
description: makeCollaborativeDoc(meetingRoom2, 'description') description: makeCollaborativeDoc(meetingRoom2, 'description')
}) })
const voiceRoom1 = generateId<Room>() const voiceRoom1 = generateId<Room>()
@ -97,6 +101,7 @@ export function createDefaultRooms (employees: Ref<Employee>[]): (Data<Room | Of
y: 8, y: 8,
language: 'en', language: 'en',
startWithTranscription: false, startWithTranscription: false,
startWithRecording: false,
description: makeCollaborativeDoc(voiceRoom1, 'description') description: makeCollaborativeDoc(voiceRoom1, 'description')
}) })
const voiceRoom2 = generateId<Room>() const voiceRoom2 = generateId<Room>()
@ -112,6 +117,7 @@ export function createDefaultRooms (employees: Ref<Employee>[]): (Data<Room | Of
y: 8, y: 8,
language: 'en', language: 'en',
startWithTranscription: false, startWithTranscription: false,
startWithRecording: false,
description: makeCollaborativeDoc(voiceRoom2, 'description') description: makeCollaborativeDoc(voiceRoom2, 'description')
}) })
return res return res

View File

@ -54,6 +54,7 @@
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"dependencies": { "dependencies": {
"@hcengineering/attachment": "^0.6.14",
"@hcengineering/love": "^0.6.0", "@hcengineering/love": "^0.6.0",
"@hcengineering/drive": "^0.6.0", "@hcengineering/drive": "^0.6.0",
"@hcengineering/core": "^0.6.32", "@hcengineering/core": "^0.6.32",

View File

@ -13,13 +13,13 @@
// limitations under the License. // limitations under the License.
// //
import { toWorkspaceString, WorkspaceId } from '@hcengineering/core' import { Ref, toWorkspaceString, WorkspaceId } from '@hcengineering/core'
import { setMetadata } from '@hcengineering/platform' import { setMetadata } from '@hcengineering/platform'
import serverClient from '@hcengineering/server-client' import serverClient from '@hcengineering/server-client'
import { initStatisticsContext, StorageConfig, StorageConfiguration } from '@hcengineering/server-core' import { initStatisticsContext, StorageConfig, StorageConfiguration } from '@hcengineering/server-core'
import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage' import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage'
import serverToken, { decodeToken } from '@hcengineering/server-token' import serverToken, { decodeToken } from '@hcengineering/server-token'
import { RoomMetadata, TranscriptionStatus } from '@hcengineering/love' import { RoomMetadata, TranscriptionStatus, MeetingMinutes } from '@hcengineering/love'
import cors from 'cors' import cors from 'cors'
import express from 'express' import express from 'express'
import { IncomingHttpHeaders } from 'http' import { IncomingHttpHeaders } from 'http'
@ -70,6 +70,7 @@ export const main = async (): Promise<void> => {
name: string name: string
workspace: string workspace: string
workspaceId: WorkspaceId workspaceId: WorkspaceId
meetingMinutes?: Ref<MeetingMinutes>
} }
>() >()
@ -86,7 +87,7 @@ export const main = async (): Promise<void> => {
const storedBlob = await storageAdapter.stat(ctx, data.workspaceId, filename) const storedBlob = await storageAdapter.stat(ctx, data.workspaceId, filename)
if (storedBlob !== undefined) { if (storedBlob !== undefined) {
const client = await WorkspaceClient.create(data.workspace) const client = await WorkspaceClient.create(data.workspace)
await client.saveFile(filename, data.name, storedBlob) await client.saveFile(filename, data.name, storedBlob, data.meetingMinutes)
await client.close() await client.close()
} }
dataByUUID.delete(res.filename) dataByUUID.delete(res.filename)
@ -128,13 +129,14 @@ export const main = async (): Promise<void> => {
const roomName = req.body.roomName const roomName = req.body.roomName
const room = req.body.room const room = req.body.room
const meetingMinutes = req.body.meetingMinutes
const { workspace } = decodeToken(token) const { workspace } = decodeToken(token)
try { try {
const dateStr = new Date().toISOString().replace('T', '_').slice(0, 19) const dateStr = new Date().toISOString().replace('T', '_').slice(0, 19)
const name = `${room}_${dateStr}.mp4` const name = `${room}_${dateStr}.mp4`
const id = await startRecord(storageConfig, egressClient, roomClient, roomName, workspace) const id = await startRecord(storageConfig, egressClient, roomClient, roomName, workspace)
dataByUUID.set(id, { name, workspace: workspace.name, workspaceId: workspace }) dataByUUID.set(id, { name, workspace: workspace.name, workspaceId: workspace, meetingMinutes })
res.send() res.send()
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import core, { Client, Ref, TxOperations, type Blob } from '@hcengineering/core' import core, { Client, Ref, TxOperations, type Blob, Data } from '@hcengineering/core'
import drive, { createFile } from '@hcengineering/drive' import drive, { createFile } from '@hcengineering/drive'
import love from '@hcengineering/love' import love, { MeetingMinutes } from '@hcengineering/love'
import { generateToken } from '@hcengineering/server-token' import { generateToken } from '@hcengineering/server-token'
import attachment, { Attachment } from '@hcengineering/attachment'
import { getClient } from './client' import { getClient } from './client'
import config from './config' import config from './config'
@ -41,7 +42,7 @@ export class WorkspaceClient {
return this.client return this.client
} }
async saveFile (uuid: string, name: string, blob: Blob): Promise<void> { async saveFile (uuid: string, name: string, blob: Blob, meetingMinutes?: Ref<MeetingMinutes>): Promise<void> {
const current = await this.client.findOne(drive.class.Drive, { _id: love.space.Drive }) const current = await this.client.findOne(drive.class.Drive, { _id: love.space.Drive })
if (current === undefined) { if (current === undefined) {
await this.client.createDoc( await this.client.createDoc(
@ -61,7 +62,6 @@ export class WorkspaceClient {
} }
const data = { const data = {
file: uuid as Ref<Blob>, file: uuid as Ref<Blob>,
title: name,
size: blob.size, size: blob.size,
type: blob.contentType, type: blob.contentType,
lastModified: blob.modifiedOn, lastModified: blob.modifiedOn,
@ -72,6 +72,26 @@ export class WorkspaceClient {
originalWidth: 1280 originalWidth: 1280
} }
} }
await createFile(this.client, love.space.Drive, drive.ids.Root, data) await createFile(this.client, love.space.Drive, drive.ids.Root, { ...data, title: name })
await this.attachToMeetingMinutes({ ...data, name }, meetingMinutes)
}
async attachToMeetingMinutes (
data: Omit<Data<Attachment>, 'attachedToClass' | 'attachedTo' | 'collection'>,
ref?: Ref<MeetingMinutes>
): Promise<void> {
if (ref === undefined) return
const meeting = await this.client.findOne(love.class.MeetingMinutes, { _id: ref })
if (meeting === undefined) return
await this.client.addCollection(
attachment.class.Attachment,
meeting.space,
meeting._id,
meeting._class,
'attachments',
data
)
} }
} }