diff --git a/meeting_summarizer/README.md b/meeting_summarizer/README.md new file mode 100644 index 000000000..f6a74158f --- /dev/null +++ b/meeting_summarizer/README.md @@ -0,0 +1,119 @@ +# Meeting Summarizer for Odoo 18 + +[](https://www.odoo.com) +[](https://opensource.org/licenses/MIT) + +## Overview + +The Meeting Summarizer module transcribes Discuss meetings and saves the +transcript along with a summary. + +## Features + +- Features +- π Download the transcription summary file. +- π Access and download the full transcription data. +- βοΈ Automatically send transcription and summary files +to selected users. + + +## Screenshots + +Here are some glimpses of Json Widget: + +### User Interface + +
+
+
+
+
+ Hello,
+
+
+
+ This module helps to generate summaries of meetings by transcribing and processing conversations in real time.
+
+ +
++
++
++
+
+ + First, set the OpenAPI key to enable transcription summary creation.
+
+ + When a new meeting starts with participants and the call end button is clicked, a wizard will automatically open with the transcription and summary files attached. The meeting description will be set as the subject. + These files can be sent to multiple participants by clicking the Send Mail button. +
+
+ When the 'Automatically Send Mail' feature is enabled in General Settings, + the system can automatically send the files based on the selected option. If 'Host' is selected, the files will be sent to the person who created the meeting. If 'All Attendees' is selected, + the files will be sent to all participants who attended the meeting, excluding public users.
+
+ The meeting contents are emailed in the following format.
+
+ + Send transcription and summary files manually or automatically.
++ This feature allows you + to add custom questions + related to the product during + the ordering process in a POS session. +
+Meeting content here...
", + transcriptionId: result.transcriptionId, + summaryId: result.summaryId, + }, + }); + + if (partner_details.length != 0) { + const partners_email = partner_details.map(p => p.email); + + const emailResponse = await rpc('/send/auto_email', { + kwargs: { + partners_email: partners_email, + subject: subject, + email_body: "Meeting content here...
", + transcriptionId: result.transcriptionId, + summaryId: result.summaryId, + }, + }); + + } else { + await this.tryOpeningTranscription(transcriptionId); + } + } catch (error) { + console.error("Error in admin meeting end processing:", error); + } + } + } catch (error) { + console.error("Error in transcription processing timeout:", error); + } + }, 500); + + return res; + } catch (error) { + console.error("Error in onClickToggleAudioCall:", error); + return false; + } + }, + + async tryOpeningTranscription(transcriptionId) { + if (!this.action) { + console.error("Error: this.action is undefined."); + return; + } + + try { + const actionData = { + name: "Send mail Transcription", + type: "ir.actions.act_window", + res_model: "send.mail.transcription", + res_id: transcriptionId, + view_mode: "form", + views: [[false, "form"]], // Ensures proper view format + target: "new", + }; + + await this.action.doAction(actionData); + } catch (error) { + console.error("Error in doAction:", error); + } + }, + + async startRecording() { + try { + if (!this.speechQueue) { + this.speechQueue = new SpeechRecognitionQueue( + this.insertTranscription.bind(this), + this.user + ); + } + + await this.speechQueue.start(); + this.state.isRecording = true; + this.state.currentSpeaker = this.state.userName || (this.user?.name || "Unknown User"); + + if (!this.state.transcriptions[this.state.currentSpeaker]) { + this.state.transcriptions[this.state.currentSpeaker] = []; + } + + } catch (error) { + console.error("Error starting recording:", error); + this.state.isRecording = false; + } + }, + + async insertTranscription(transcriptionData) { + const { text, userId, timestamp } = transcriptionData; + + let speakerName = "Unknown User"; + + // Try multiple methods to get speaker name + if (this.state.userName) { + speakerName = this.state.userName; + } else if (this.user && this.user.name) { + speakerName = this.user.name; + } + + const formattedText = `(${new Date(timestamp).toLocaleString()})\n\t${speakerName} : ${text}\n`; + + try { + if (!this.props.thread || !this.props.thread.id) { + console.error("Cannot send transcription: thread or thread ID is missing"); + return; + } + + const response = await rpc('/get/transcription_data', { + data: formattedText, + id: this.props.thread.id, + userId: userId, + timestamp: timestamp + }); + + } catch (error) { + console.error("Error sending transcription:", error); + } + }, + + async stopRecording() { + + if (this.state.isRecording) { + if (this.speechQueue) { + this.speechQueue.stop(); + } + this.state.isRecording = false; + } + + try { + if (!this.props.thread || !this.props.thread.id) { + console.error("Cannot attach transcription: thread or thread ID is missing"); + return; + } + + await rpc('/attach/transcription_data/summary', { + kwargs: { + channelId: this.props.thread.id, + }, + }); + + } catch (error) { + console.error("Error attaching transcription data:", error); + } + }, +}); \ No newline at end of file diff --git a/meeting_summarizer/static/src/xml/attachment_list.xml b/meeting_summarizer/static/src/xml/attachment_list.xml new file mode 100644 index 000000000..30212b9b2 --- /dev/null +++ b/meeting_summarizer/static/src/xml/attachment_list.xml @@ -0,0 +1,15 @@ + +