Update zulip message

This commit is contained in:
2024-09-06 16:09:44 +02:00
parent db714f6390
commit 6d976044d0
8 changed files with 166 additions and 15 deletions

View File

@@ -0,0 +1,30 @@
"""Add zulip message id
Revision ID: 764ce6db4388
Revises: 62dea3db63a5
Create Date: 2024-09-06 14:02:06.649665
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '764ce6db4388'
down_revision: Union[str, None] = '62dea3db63a5'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('transcript', sa.Column('zulip_message_id', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('transcript', 'zulip_message_id')
# ### end Alembic commands ###

View File

@@ -54,6 +54,7 @@ transcripts = sqlalchemy.Table(
"meeting_id", "meeting_id",
sqlalchemy.String, sqlalchemy.String,
), ),
sqlalchemy.Column("zulip_message_id", sqlalchemy.Integer, nullable=True),
) )
@@ -150,6 +151,7 @@ class Transcript(BaseModel):
audio_location: str = "local" audio_location: str = "local"
reviewed: bool = False reviewed: bool = False
meeting_id: str | None = None meeting_id: str | None = None
zulip_message_id: int | None = None
def add_event(self, event: str, data: BaseModel) -> TranscriptEvent: def add_event(self, event: str, data: BaseModel) -> TranscriptEvent:
ev = TranscriptEvent(event=event, data=data.model_dump()) ev = TranscriptEvent(event=event, data=data.model_dump())

View File

@@ -587,7 +587,10 @@ async def pipeline_post_to_zulip(transcript: Transcript, logger: Logger):
if room.zulip_auto_post: if room.zulip_auto_post:
message = get_zulip_message(transcript=transcript) message = get_zulip_message(transcript=transcript)
send_message_to_zulip(room.zulip_stream, room.zulip_topic, message) response = send_message_to_zulip(room.zulip_stream, room.zulip_topic, message)
await transcripts_controller.update(
transcript, {"zulip_message_id": response["id"]}
)
logger.info("Posted to zulip") logger.info("Posted to zulip")

View File

@@ -16,6 +16,11 @@ from reflector.db.transcripts import (
from reflector.processors.types import Transcript as ProcessorTranscript from reflector.processors.types import Transcript as ProcessorTranscript
from reflector.processors.types import Word from reflector.processors.types import Word
from reflector.settings import settings from reflector.settings import settings
from reflector.zulip import (
get_zulip_message,
send_message_to_zulip,
update_zulip_message,
)
router = APIRouter() router = APIRouter()
@@ -323,3 +328,28 @@ async def transcript_get_topics_with_words_per_speaker(
# convert to GetTranscriptTopicWithWordsPerSpeaker # convert to GetTranscriptTopicWithWordsPerSpeaker
return GetTranscriptTopicWithWordsPerSpeaker.from_transcript_topic(topic) return GetTranscriptTopicWithWordsPerSpeaker.from_transcript_topic(topic)
@router.post("/transcripts/{transcript_id}/zulip")
async def transcript_post_to_zulip(
transcript_id: str,
stream: str,
topic: str,
include_topics: bool,
user: Annotated[Optional[auth.UserInfo], Depends(auth.current_user_optional)],
):
user_id = user["sub"] if user else None
transcript = await transcripts_controller.get_by_id_for_http(
transcript_id, user_id=user_id
)
if not transcript:
raise HTTPException(status_code=404, detail="Transcript not found")
content = get_zulip_message(transcript, include_topics)
if transcript.zulip_message_id:
update_zulip_message(transcript.zulip_message_id, stream, topic, content)
else:
response = send_message_to_zulip(stream, topic, content)
await transcripts_controller.update(
transcript, {"zulip_message_id": response["id"]}
)

View File

@@ -6,10 +6,7 @@ from reflector.db.transcripts import Transcript
from reflector.settings import settings from reflector.settings import settings
def send_message_to_zulip(stream: str, topic: str, message: str): def send_message_to_zulip(stream: str, topic: str, content: str):
if not stream or not topic or not message:
raise ValueError("Missing required parameters")
try: try:
response = requests.post( response = requests.post(
f"https://{settings.ZULIP_REALM}/api/v1/messages", f"https://{settings.ZULIP_REALM}/api/v1/messages",
@@ -17,7 +14,7 @@ def send_message_to_zulip(stream: str, topic: str, message: str):
"type": "stream", "type": "stream",
"to": stream, "to": stream,
"topic": topic, "topic": topic,
"content": message, "content": content,
}, },
auth=(settings.ZULIP_BOT_EMAIL, settings.ZULIP_API_KEY), auth=(settings.ZULIP_BOT_EMAIL, settings.ZULIP_API_KEY),
headers={"Content-Type": "application/x-www-form-urlencoded"}, headers={"Content-Type": "application/x-www-form-urlencoded"},
@@ -30,7 +27,29 @@ def send_message_to_zulip(stream: str, topic: str, message: str):
raise Exception(f"Failed to send message to Zulip: {error}") raise Exception(f"Failed to send message to Zulip: {error}")
def get_zulip_message(transcript: Transcript): def update_zulip_message(message_id: int, stream: str, topic: str, content: str):
try:
response = requests.patch(
f"https://{settings.ZULIP_REALM}/api/v1/messages/{message_id}",
data={
"type": "stream",
"to": stream,
"topic": topic,
"content": content,
},
auth=(settings.ZULIP_BOT_EMAIL, settings.ZULIP_API_KEY),
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
response.raise_for_status()
return response.json()
except requests.RequestException as error:
print(content)
raise Exception(f"Failed to update Zulip message: {error}")
def get_zulip_message(transcript: Transcript, include_topics: bool):
transcript_url = f"{settings.UI_BASE_URL}/transcripts/{transcript.id}" transcript_url = f"{settings.UI_BASE_URL}/transcripts/{transcript.id}"
header_text = f"# Reflector {transcript.title or 'Unnamed recording'}\n\n" header_text = f"# Reflector {transcript.title or 'Unnamed recording'}\n\n"
@@ -40,7 +59,7 @@ def get_zulip_message(transcript: Transcript):
topic_text = "" topic_text = ""
if transcript.topics: if include_topics and transcript.topics:
topic_text = "```spoiler Topics\n" topic_text = "```spoiler Topics\n"
for topic in transcript.topics: for topic in transcript.topics:
topic_text += f"1. [{format_time(topic.timestamp)}] {topic.title}\n" topic_text += f"1. [{format_time(topic.timestamp)}] {topic.title}\n"
@@ -48,7 +67,7 @@ def get_zulip_message(transcript: Transcript):
summary = "```spoiler Summary\n" summary = "```spoiler Summary\n"
summary += transcript.long_summary summary += transcript.long_summary
summary += "```\n\n" summary += "\n```\n\n"
message = header_text + summary + topic_text + "-----\n" message = header_text + summary + topic_text + "-----\n"
return message return message

View File

@@ -1,9 +1,9 @@
import React, { useContext, useState, useEffect } from "react"; import React, { useContext, useState, useEffect, useCallback } from "react";
import SelectSearch from "react-select-search"; import SelectSearch from "react-select-search";
import { getZulipMessage, sendZulipMessage } from "../../../lib/zulip";
import { GetTranscript, GetTranscriptTopic } from "../../../api"; import { GetTranscript, GetTranscriptTopic } from "../../../api";
import "react-select-search/style.css"; import "react-select-search/style.css";
import { DomainContext } from "../../../domainContext"; import { DomainContext } from "../../../domainContext";
import useApi from "../../../lib/useApi";
type ShareModal = { type ShareModal = {
show: boolean; show: boolean;
@@ -30,6 +30,7 @@ const ShareModal = (props: ShareModal) => {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [streams, setStreams] = useState<Stream[]>([]); const [streams, setStreams] = useState<Stream[]>([]);
const { zulip_streams } = useContext(DomainContext); const { zulip_streams } = useContext(DomainContext);
const api = useApi();
useEffect(() => { useEffect(() => {
fetch(zulip_streams + "/streams.json") fetch(zulip_streams + "/streams.json")
@@ -52,12 +53,22 @@ const ShareModal = (props: ShareModal) => {
}); });
}, []); }, []);
const handleSendToZulip = () => { const handleSendToZulip = async () => {
if (!props.transcript) return; if (!props.transcript) return;
const msg = getZulipMessage(props.transcript, props.topics, includeTopics); if (stream && topic) {
if (!api) return;
if (stream && topic) sendZulipMessage(stream, topic, msg); try {
await api.v1TranscriptPostToZulip({
transcriptId: props.transcript.id,
stream,
topic,
includeTopics,
});
} catch (error) {
console.log(error);
}
}
}; };
if (props.show && isLoading) { if (props.show && isLoading) {

View File

@@ -30,6 +30,8 @@ import type {
V1TranscriptGetTopicsWithWordsResponse, V1TranscriptGetTopicsWithWordsResponse,
V1TranscriptGetTopicsWithWordsPerSpeakerData, V1TranscriptGetTopicsWithWordsPerSpeakerData,
V1TranscriptGetTopicsWithWordsPerSpeakerResponse, V1TranscriptGetTopicsWithWordsPerSpeakerResponse,
V1TranscriptPostToZulipData,
V1TranscriptPostToZulipResponse,
V1TranscriptHeadAudioMp3Data, V1TranscriptHeadAudioMp3Data,
V1TranscriptHeadAudioMp3Response, V1TranscriptHeadAudioMp3Response,
V1TranscriptGetAudioMp3Data, V1TranscriptGetAudioMp3Data,
@@ -373,6 +375,36 @@ export class DefaultService {
}); });
} }
/**
* Transcript Post To Zulip
* @param data The data for the request.
* @param data.transcriptId
* @param data.stream
* @param data.topic
* @param data.includeTopics
* @returns unknown Successful Response
* @throws ApiError
*/
public v1TranscriptPostToZulip(
data: V1TranscriptPostToZulipData,
): CancelablePromise<V1TranscriptPostToZulipResponse> {
return this.httpRequest.request({
method: "POST",
url: "/v1/transcripts/{transcript_id}/zulip",
path: {
transcript_id: data.transcriptId,
},
query: {
stream: data.stream,
topic: data.topic,
include_topics: data.includeTopics,
},
errors: {
422: "Validation Error",
},
});
}
/** /**
* Transcript Get Audio Mp3 * Transcript Get Audio Mp3
* @param data The data for the request. * @param data The data for the request.

View File

@@ -319,6 +319,15 @@ export type V1TranscriptGetTopicsWithWordsPerSpeakerData = {
export type V1TranscriptGetTopicsWithWordsPerSpeakerResponse = export type V1TranscriptGetTopicsWithWordsPerSpeakerResponse =
GetTranscriptTopicWithWordsPerSpeaker; GetTranscriptTopicWithWordsPerSpeaker;
export type V1TranscriptPostToZulipData = {
includeTopics: boolean;
stream: string;
topic: string;
transcriptId: string;
};
export type V1TranscriptPostToZulipResponse = unknown;
export type V1TranscriptHeadAudioMp3Data = { export type V1TranscriptHeadAudioMp3Data = {
token?: string | null; token?: string | null;
transcriptId: string; transcriptId: string;
@@ -614,6 +623,21 @@ export type $OpenApiTs = {
}; };
}; };
}; };
"/v1/transcripts/{transcript_id}/zulip": {
post: {
req: V1TranscriptPostToZulipData;
res: {
/**
* Successful Response
*/
200: unknown;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/transcripts/{transcript_id}/audio/mp3": { "/v1/transcripts/{transcript_id}/audio/mp3": {
head: { head: {
req: V1TranscriptHeadAudioMp3Data; req: V1TranscriptHeadAudioMp3Data;