diff --git a/server/migrations/versions/082fa608201c_add_room_background_information.py b/server/migrations/versions/082fa608201c_add_room_background_information.py new file mode 100644 index 00000000..34779e39 --- /dev/null +++ b/server/migrations/versions/082fa608201c_add_room_background_information.py @@ -0,0 +1,26 @@ +"""add_room_background_information + +Revision ID: 082fa608201c +Revises: b7df9609542c +Create Date: 2025-07-29 01:41:37.912195 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '082fa608201c' +down_revision: Union[str, None] = 'b7df9609542c' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column('room', sa.Column('background_information', sa.Text(), nullable=True)) + + +def downgrade() -> None: + op.drop_column('room', 'background_information') diff --git a/server/reflector/db/rooms.py b/server/reflector/db/rooms.py index 6e98acff..a06764c9 100644 --- a/server/reflector/db/rooms.py +++ b/server/reflector/db/rooms.py @@ -39,6 +39,7 @@ rooms = sqlalchemy.Table( sqlalchemy.Column( "is_shared", sqlalchemy.Boolean, nullable=False, server_default=false() ), + sqlalchemy.Column("background_information", sqlalchemy.Text), sqlalchemy.Index("idx_room_is_shared", "is_shared"), ) @@ -58,6 +59,7 @@ class Room(BaseModel): "none", "prompt", "automatic", "automatic-2nd-participant" ] = "automatic-2nd-participant" is_shared: bool = False + background_information: str = "" class RoomController: @@ -106,6 +108,7 @@ class RoomController: recording_type: str, recording_trigger: str, is_shared: bool, + background_information: str = "", ): """ Add a new room @@ -121,6 +124,7 @@ class RoomController: recording_type=recording_type, recording_trigger=recording_trigger, is_shared=is_shared, + background_information=background_information, ) query = rooms.insert().values(**room.model_dump()) try: diff --git a/server/reflector/pipelines/main_live_pipeline.py b/server/reflector/pipelines/main_live_pipeline.py index 3a4c36be..1a9a43bd 100644 --- a/server/reflector/pipelines/main_live_pipeline.py +++ b/server/reflector/pipelines/main_live_pipeline.py @@ -454,15 +454,47 @@ class PipelineMainFinalSummaries(PipelineMainFromTopics): Generate summaries from the topics """ + async def get_room(self): + """Get room information for the transcript""" + if not self._transcript.room_id: + return None + return await rooms_controller.get_by_id(self._transcript.room_id) + def get_processors(self) -> list: return [ TranscriptFinalSummaryProcessor.as_threaded( transcript=self._transcript, + room=getattr(self, '_room', None), callback=self.on_long_summary, on_short_summary=self.on_short_summary, ), ] + async def create(self) -> Pipeline: + self.prepare() + + # get transcript + self._transcript = transcript = await self.get_transcript() + + # get room information + self._room = await self.get_room() + + # create pipeline + processors = self.get_processors() + pipeline = Pipeline(*processors) + pipeline.options = self + pipeline.logger.bind(transcript_id=transcript.id) + pipeline.logger.info(f"{self.__class__.__name__} pipeline created") + + # push topics + topics = self.get_transcript_topics(transcript) + for topic in topics: + await self.push(topic) + + await self.flush() + + return pipeline + class PipelineMainWaveform(PipelineMainFromTopics): """ diff --git a/server/reflector/processors/summary/summary_builder.py b/server/reflector/processors/summary/summary_builder.py index 9120f0a7..aa3605c7 100644 --- a/server/reflector/processors/summary/summary_builder.py +++ b/server/reflector/processors/summary/summary_builder.py @@ -135,7 +135,7 @@ class Messages: class SummaryBuilder: - def __init__(self, llm, filename: str | None = None, logger=None): + def __init__(self, llm, filename: str | None = None, logger=None, room=None): self.transcript: str | None = None self.recap: str | None = None self.summaries: list[dict] = [] @@ -147,6 +147,7 @@ class SummaryBuilder: self.llm_instance: LLM = llm self.model_name: str = llm.model_name self.logger = logger or structlog.get_logger() + self.room = room self.m = Messages(model_name=self.model_name, logger=self.logger) if filename: self.read_transcript_from_file(filename) @@ -465,26 +466,31 @@ class SummaryBuilder: self.logger.debug("--- extract main subjects") m = Messages(model_name=self.model_name, logger=self.logger) - m.add_system( - ( - "You are an advanced transcription summarization assistant." - "Your task is to summarize discussions by focusing only on the main ideas contributed by participants." - # Prevent generating another transcription - "Exclude direct quotes and unnecessary details." - # Do not mention others participants just because they didn't contributed - "Only include participant names if they actively contribute to the subject." - # Prevent generation of summary with "no others participants contributed" etc - "Keep summaries concise and focused on main subjects without adding conclusions such as 'no other participant contributed'. " - # Avoid: In the discussion, they talked about... - "Do not include contextual preface. " - # Prevention to have too long summary - "Summary should fit in a single paragraph. " - # Using other pronouns that the participants or the group - 'Mention the participants or the group using "they".' - # Avoid finishing the summary with "No conclusions were added by the summarizer" - "Do not mention conclusion if there is no conclusion" - ) + + system_prompt = ( + "You are an advanced transcription summarization assistant." + "Your task is to summarize discussions by focusing only on the main ideas contributed by participants." + # Prevent generating another transcription + "Exclude direct quotes and unnecessary details." + # Do not mention others participants just because they didn't contributed + "Only include participant names if they actively contribute to the subject." + # Prevent generation of summary with "no others participants contributed" etc + "Keep summaries concise and focused on main subjects without adding conclusions such as 'no other participant contributed'. " + # Avoid: In the discussion, they talked about... + "Do not include contextual preface. " + # Prevention to have too long summary + "Summary should fit in a single paragraph. " + # Using other pronouns that the participants or the group + 'Mention the participants or the group using "they".' + # Avoid finishing the summary with "No conclusions were added by the summarizer" + "Do not mention conclusion if there is no conclusion" ) + + # Add room context if available + if self.room and self.room.background_information: + system_prompt += f"\n\nContext about this meeting room: {self.room.background_information}" + + m.add_system(system_prompt) m.add_user( f"# Transcript\n\n{self.transcript}\n\n" + ( diff --git a/server/reflector/processors/transcript_final_summary.py b/server/reflector/processors/transcript_final_summary.py index daa52e56..1ca46950 100644 --- a/server/reflector/processors/transcript_final_summary.py +++ b/server/reflector/processors/transcript_final_summary.py @@ -12,9 +12,10 @@ class TranscriptFinalSummaryProcessor(Processor): INPUT_TYPE = TitleSummary OUTPUT_TYPE = FinalLongSummary - def __init__(self, transcript=None, **kwargs): + def __init__(self, transcript=None, room=None, **kwargs): super().__init__(**kwargs) self.transcript = transcript + self.room = room self.chunks: list[TitleSummary] = [] self.llm = LLM.get_instance(model_name="NousResearch/Hermes-3-Llama-3.1-8B") self.builder = None @@ -23,7 +24,7 @@ class TranscriptFinalSummaryProcessor(Processor): self.chunks.append(data) async def get_summary_builder(self, text) -> SummaryBuilder: - builder = SummaryBuilder(self.llm) + builder = SummaryBuilder(self.llm, room=self.room) builder.set_transcript(text) await builder.identify_participants() await builder.generate_summary() diff --git a/server/reflector/views/rooms.py b/server/reflector/views/rooms.py index d086040c..50e64edf 100644 --- a/server/reflector/views/rooms.py +++ b/server/reflector/views/rooms.py @@ -33,6 +33,7 @@ class Room(BaseModel): recording_type: str recording_trigger: str is_shared: bool + background_information: str class Meeting(BaseModel): @@ -55,6 +56,7 @@ class CreateRoom(BaseModel): recording_type: str recording_trigger: str is_shared: bool + background_information: str = "" class UpdateRoom(BaseModel): @@ -67,6 +69,7 @@ class UpdateRoom(BaseModel): recording_type: str recording_trigger: str is_shared: bool + background_information: str = "" class DeletionStatus(BaseModel): @@ -108,6 +111,7 @@ async def rooms_create( recording_type=room.recording_type, recording_trigger=room.recording_trigger, is_shared=room.is_shared, + background_information=room.background_information, ) diff --git a/www/app/(app)/rooms/page.tsx b/www/app/(app)/rooms/page.tsx index 03a4858b..1d6c76d8 100644 --- a/www/app/(app)/rooms/page.tsx +++ b/www/app/(app)/rooms/page.tsx @@ -11,13 +11,14 @@ import { Input, Select, Spinner, + Textarea, createListCollection, useDisclosure, } from "@chakra-ui/react"; import { useEffect, useState } from "react"; import useApi from "../../lib/useApi"; import useRoomList from "./useRoomList"; -import { ApiError, Room } from "../../api"; +import { Room } from "../../api"; import { RoomList } from "./_components/RoomList"; interface SelectOption { @@ -54,6 +55,7 @@ const roomInitialState = { recordingType: "cloud", recordingTrigger: "automatic-2nd-participant", isShared: false, + backgroundInformation: "", }; export default function RoomsList() { @@ -170,6 +172,7 @@ export default function RoomsList() { recording_type: room.recordingType, recording_trigger: room.recordingTrigger, is_shared: room.isShared, + background_information: room.backgroundInformation, }; if (isEditing) { @@ -189,11 +192,10 @@ export default function RoomsList() { setNameError(""); refetch(); onClose(); - } catch (err) { + } catch (err: any) { if ( - err instanceof ApiError && err.status === 400 && - (err.body as any).detail == "Room name is not unique" + err.body?.detail === "Room name is not unique" ) { setNameError( "This room name is already taken. Please choose a different name.", @@ -215,6 +217,7 @@ export default function RoomsList() { recordingType: roomData.recording_type, recordingTrigger: roomData.recording_trigger, isShared: roomData.is_shared, + backgroundInformation: roomData.background_information || "", }); setEditRoomId(roomId); setIsEditing(true); @@ -323,6 +326,20 @@ export default function RoomsList() { {nameError && {nameError}} + + Background Information +