mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-10-25 22:09:36 +00:00
answer customer messages sent while the AI is replying
This commit is contained in:
@@ -234,14 +234,19 @@ func (m *Manager) handle(ctx context.Context, convID int) {
|
||||
if inbound == nil {
|
||||
return
|
||||
}
|
||||
// Only respond to a fresh inbound customer message, never to the assistant's own or automated
|
||||
// replies. A requeued run sees the previous run's just-posted reply as the last message, so it
|
||||
// instead checks for an inbound message the previous run's history never included.
|
||||
m.mu.Lock()
|
||||
prevSeen, hasPrev := m.lastSeen[convID]
|
||||
m.lastSeen[convID] = msgs[len(msgs)-1].ID
|
||||
m.mu.Unlock()
|
||||
if !lastIsInboundContact(msgs) && (!hasPrev || inbound.ID <= prevSeen) {
|
||||
// A requeued run finds the previous run's reply after the customer messages that arrived while it
|
||||
// was being written. Moving the reply above them keeps the history in the order the assistant saw
|
||||
// it and leaves the newest customer message last, which providers require.
|
||||
if hasPrev {
|
||||
msgs = previousReplyFirst(msgs, prevSeen)
|
||||
}
|
||||
// Only respond when the newest message is the customer's, never to the assistant's own, a human's
|
||||
// or automated replies.
|
||||
if !lastIsInboundContact(msgs) {
|
||||
return
|
||||
}
|
||||
// The reply and any identity-scoped tools act as the primary contact. An email thread can carry
|
||||
@@ -867,3 +872,25 @@ func messageMetaBool(raw json.RawMessage, key string) bool {
|
||||
value, _ := meta[key].(bool)
|
||||
return value
|
||||
}
|
||||
|
||||
// previousReplyFirst moves AI replies posted after prevSeen above the other messages that arrived since.
|
||||
func previousReplyFirst(msgs []cmodels.Message, prevSeen int) []cmodels.Message {
|
||||
out := make([]cmodels.Message, 0, len(msgs))
|
||||
var later []cmodels.Message
|
||||
for _, msg := range msgs {
|
||||
if msg.ID > prevSeen && !isAIReply(msg) {
|
||||
later = append(later, msg)
|
||||
continue
|
||||
}
|
||||
out = append(out, msg)
|
||||
}
|
||||
return append(out, later...)
|
||||
}
|
||||
|
||||
// isAIReply matches replies from any assistant, so a reassignment mid-run still finds the old reply.
|
||||
func isAIReply(msg cmodels.Message) bool {
|
||||
var meta struct {
|
||||
AssistantID int `json:"ai_assistant_id"`
|
||||
}
|
||||
return json.Unmarshal(msg.Meta, &meta) == nil && meta.AssistantID > 0
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package aiagent
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
aimodels "github.com/abhinavxd/libredesk/internal/ai/models"
|
||||
"github.com/abhinavxd/libredesk/internal/aiagent/models"
|
||||
cmodels "github.com/abhinavxd/libredesk/internal/conversation/models"
|
||||
"github.com/abhinavxd/libredesk/internal/stringutil"
|
||||
)
|
||||
|
||||
@@ -109,3 +111,33 @@ func TestArticleCitationHTML(t *testing.T) {
|
||||
t.Fatalf("unexpected preview: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreviousReplyFirst(t *testing.T) {
|
||||
msg := func(id int) cmodels.Message { return cmodels.Message{ID: id} }
|
||||
reply := func(id int) cmodels.Message {
|
||||
return cmodels.Message{ID: id, Meta: json.RawMessage(`{"ai_assistant_id":7}`)}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
msgs []cmodels.Message
|
||||
prevSeen int
|
||||
want []int
|
||||
}{
|
||||
{"messages sent while replying", []cmodels.Message{msg(1), msg(2), msg(3), reply(4)}, 1, []int{1, 4, 2, 3}},
|
||||
{"split reply", []cmodels.Message{msg(1), msg(2), reply(3), reply(4)}, 1, []int{1, 3, 4, 2}},
|
||||
{"no new message", []cmodels.Message{msg(1), reply(2)}, 1, []int{1, 2}},
|
||||
{"no reply posted", []cmodels.Message{msg(1), msg(2)}, 1, []int{1, 2}},
|
||||
{"older replies untouched", []cmodels.Message{msg(1), reply(2), msg(3), msg(4), reply(5)}, 3, []int{1, 2, 3, 5, 4}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var got []int
|
||||
for _, m := range previousReplyFirst(tt.msgs, tt.prevSeen) {
|
||||
got = append(got, m.ID)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("got %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user