Skip to content

Commit

Permalink
Updates to meeting transcriptions and regeneration (#106)
Browse files Browse the repository at this point in the history
* Better meeting summaries

* Fixing regeneration.
  • Loading branch information
crspeller authored Jan 15, 2024
1 parent b626aaf commit b9b566b
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 149 deletions.
10 changes: 10 additions & 0 deletions server/ai/subtitles/subtitles.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ func (s *Subtitles) FormatTextOnly() string {
return strings.TrimSpace(result.String())
}

func (s *Subtitles) FormatVTT() string {
var result strings.Builder
s.storage.WriteToWebVTT(&result)
return result.String()
}

func (s *Subtitles) IsEmpty() bool {
return s.storage.IsEmpty()
}

func formatDurationForLLM(dur time.Duration) string {
dur = dur.Round(time.Second)
hours := dur / time.Hour
Expand Down
118 changes: 95 additions & 23 deletions server/api_post.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/render"
"github.com/mattermost/mattermost-plugin-ai/server/ai"
"github.com/mattermost/mattermost-plugin-ai/server/ai/subtitles"
"github.com/mattermost/mattermost/server/public/model"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -86,13 +87,30 @@ func (p *Plugin) handleSummarize(c *gin.Context) {
}

func (p *Plugin) handleTranscribe(c *gin.Context) {
userID := c.GetHeader("Mattermost-User-Id")
post := c.MustGet(ContextPostKey).(*model.Post)
channel := c.MustGet(ContextChannelKey).(*model.Channel)

if err := p.handleCallRecordingPost(post, channel); err != nil {
user, err := p.pluginAPI.User.Get(userID)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}

createdPost, err := p.newCallRecordingThread(user, post, channel)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}

data := struct {
PostID string `json:"postid"`
ChannelID string `json:"channelid"`
}{
PostID: createdPost.Id,
ChannelID: createdPost.ChannelId,
}
c.Render(http.StatusOK, render.JSON{Data: data})
}

func (p *Plugin) handleStop(c *gin.Context) {
Expand All @@ -107,7 +125,7 @@ func (p *Plugin) handleStop(c *gin.Context) {
return
}

if post.GetProp("llm_requester_user_id") != userID {
if post.GetProp(LLMRequesterUserID) != userID {
c.AbortWithError(http.StatusForbidden, errors.New("only the original poster can stop the stream"))
return
}
Expand All @@ -131,40 +149,94 @@ func (p *Plugin) handleRegenerate(c *gin.Context) {
return
}

if post.GetProp("llm_requester_user_id") != userID {
if post.GetProp(LLMRequesterUserID) != userID {
c.AbortWithError(http.StatusForbidden, errors.New("only the original poster can regenerate"))
return
}

user, err := p.pluginAPI.User.Get(userID)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
if post.GetProp(NoRegen) != nil {
c.AbortWithError(http.StatusBadRequest, errors.New("taged no regen"))
return
}

threadData, err := p.getThreadAndMeta(post.RootId)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
threadData.cutoffAtPostID(post.Id)

postToRegenerate := threadData.latestPost()

context := p.MakeConversationContext(user, channel, postToRegenerate)
conversation, err := p.prompts.ChatCompletion(ai.PromptDirectMessageQuestion, context)
user, err := p.pluginAPI.User.Get(userID)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
conversation.AppendConversation(ai.ThreadToBotConversation(p.botid, threadData.Posts))

result, err := p.getLLM().ChatCompletion(conversation)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
summaryPostIDProp := post.GetProp(ThreadIDProp)
refrencedRecordingPostProp := post.GetProp(ReferencedRecordingPostID)
var result *ai.TextStreamResult
switch {
case summaryPostIDProp != nil:
summaryPostID := summaryPostIDProp.(string)
siteURL := p.API.GetConfig().ServiceSettings.SiteURL
post.Message = summaryPostMessage(summaryPostID, *siteURL)

result, err = p.summarizePost(summaryPostID, p.MakeConversationContext(user, channel, nil))
if err != nil {
c.AbortWithError(http.StatusInternalServerError, errors.Wrap(err, "could not summarize post on regen"))
return
}
case refrencedRecordingPostProp != nil:
post.Message = ""
refrencedRecordingPostID := refrencedRecordingPostProp.(string)
referencedRecordingPost, err := p.pluginAPI.Post.GetPost(refrencedRecordingPostID)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, errors.Wrap(err, "could not get transcription post on regen"))
return
}

reader, err := p.pluginAPI.File.Get(post.FileIds[0])
if err != nil {
c.AbortWithError(http.StatusInternalServerError, errors.Wrap(err, "could not get transcription file on regen"))
return
}
transcription, err := subtitles.NewSubtitlesFromVTT(reader)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, errors.Wrap(err, "could not parse transcription file on regen"))
return
}

if transcription.IsEmpty() {
c.AbortWithError(http.StatusInternalServerError, errors.New("transcription is empty on regen"))
return
}

channel, err := p.pluginAPI.Channel.Get(referencedRecordingPost.ChannelId)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, errors.Wrap(err, "could not get channel of original recording on regen"))
return
}

context := p.MakeConversationContext(user, channel, nil)
result, err = p.summarizeTranscription(transcription, context)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, errors.Wrap(err, "could not summarize transcription on regen"))
}
default:
post.Message = ""

threadData, err := p.getThreadAndMeta(post.Id)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
respondingToPostID, ok := post.GetProp(RespondingToProp).(string)
if !ok {
threadData.cutoffBeforePostID(post.Id)
} else {
threadData.cutoffAtPostID(respondingToPostID)
}
postToRegenerate := threadData.latestPost()
context := p.MakeConversationContext(user, channel, postToRegenerate)

if result, err = p.continueConversation(threadData, context); err != nil {
c.AbortWithError(http.StatusInternalServerError, errors.Wrap(err, "could not continue conversation on regen"))
return
}
}

post.Message = ""

p.streamResultToPost(result, post)
}
4 changes: 2 additions & 2 deletions server/conversation_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ func (p *Plugin) MakeConversationContext(user *model.User, channel *model.Channe
context.CompanyName = license.Customer.Company
}

if channel != nil {
if channel != nil && (channel.Type != model.ChannelTypeDirect && channel.Type != model.ChannelTypeGroup) {
team, err := p.pluginAPI.Team.Get(channel.TeamId)
if err != nil {
p.pluginAPI.Log.Error("Unable to get team for context", "error", err.Error())
p.pluginAPI.Log.Error("Unable to get team for context", "error", err.Error(), "team_id", channel.TeamId)
} else {
context.Team = team
}
Expand Down
Loading

0 comments on commit b9b566b

Please sign in to comment.