more commits.

This commit is contained in:
Abhinav Raut
2024-07-02 03:10:00 +05:30
parent d7fb9be211
commit d0326cfedd
17 changed files with 421 additions and 142 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ const navLinks = [
title: 'Account',
component: 'account',
label: '',
icon: 'lucide:circle-user-round',
icon: 'lucide:settings',
},
]
const userStore = useUserStore()
+1 -1
View File
@@ -9,7 +9,7 @@
}
.tab-container-default {
padding: 15px 15px;
padding: 2rem 2rem;
}
.bg-success {
@@ -0,0 +1,46 @@
<script setup>
import { computed } from 'vue'
import SidebarNav from '@/components/account/SidebarNav.vue'
import { Separator } from '@/components/ui/separator'
const props = defineProps({
page: {
type: String,
required: true
}
})
const currentPage = computed(() => {
return props.page
})
</script>
<template>
<div class="md:hidden">
<VPImage alt="Forms" width="1280" height="1214" class="block" :image="{
dark: '/examples/forms-dark.png',
light: '/examples/forms-light.png',
}" />
</div>
<div class="hidden space-y-6 md:block tab-container-default">
<div class="space-y-0.5">
<h2 class="text-2xl font-bold tracking-tight">
Profile settings
</h2>
<p class="text-muted-foreground">
Manage your account settings and set e-mail preferences.
</p>
</div>
<Separator class="my-6" />
<div class="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
<aside class="-mx-4 lg:w-1/5">
<SidebarNav :current-page="currentPage" />
</aside>
<div class="flex-1 lg:max-w-2xl">
<div class="space-y-6">
<slot :page="currentPage" />
</div>
</div>
</div>
</div>
</template>
@@ -0,0 +1,51 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
defineProps({
currentPage: {
type: String,
required: true
}
})
const sidebarNavItems = [
{
title: 'Profile',
href: '/account/profile',
},
{
title: 'Account',
href: '/account/account',
},
{
title: 'Appearance',
href: '/account/appearance',
},
{
title: 'Notifications',
href: '/account/notifications',
},
{
title: 'Display',
href: '/account/display',
},
]
</script>
<template>
<nav class="flex space-x-2 lg:flex-col lg:space-x-0 lg:space-y-1">
<router-link v-for="item in sidebarNavItems" :key="item.title" :to="item.href" custom>
<template v-slot="{ navigate, isActive }">
<Button as="a" :href="item.href" variant="ghost" :class="cn(
'w-full text-left justify-start',
isActive && 'bg-muted hover:bg-muted'
)" @click="navigate">
{{ item.title }}
</Button>
</template>
</router-link>
</nav>
</template>
@@ -5,33 +5,6 @@
<h3 class="scroll-m-20 text-2xl font-medium flex gap-x-2">
Conversations
</h3>
<div class="w-[8rem]">
<Select @update:modelValue="handleFilterChange" v-model="predefinedFilter">
<SelectTrigger>
<SelectValue placeholder="Select a filter" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<!-- <SelectLabel>Status</SelectLabel> -->
<SelectItem value="status_all">
All
</SelectItem>
<SelectItem value="status_open">
Open
</SelectItem>
<SelectItem value="status_processing">
Processing
</SelectItem>
<SelectItem value="status_spam">
Spam
</SelectItem>
<SelectItem value="status_resolved">
Resolved
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
<!-- Search -->
@@ -58,16 +31,33 @@
</TabsList>
</Tabs>
<div class="space-x-2">
<Popover>
<PopoverTrigger>
<button>
<ListFilter :size="30" class="p-2 bg-slate-100 rounded-sm" :stroke-width="1.9" />
</button>
</PopoverTrigger>
<PopoverContent class="flex flex-col gap-3 w-full">
Work in progress.
</PopoverContent>
</Popover>
<div class="w-[8rem]">
<Select @update:modelValue="handleFilterChange" v-model="predefinedFilter">
<SelectTrigger>
<SelectValue placeholder="Select a filter" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<!-- <SelectLabel>Status</SelectLabel> -->
<SelectItem value="status_all">
All
</SelectItem>
<SelectItem value="status_open">
Open
</SelectItem>
<SelectItem value="status_processing">
Processing
</SelectItem>
<SelectItem value="status_spam">
Spam
</SelectItem>
<SelectItem value="status_resolved">
Resolved
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
</div>
</div>
</div>
@@ -88,13 +78,16 @@
</div>
</div>
<div class="flex justify-center items-center mt-6 relative"
v-if="conversationStore.conversations.hasMore && !hasErrored && hasConversations">
<Button variant="link" @click="loadNextPage">
<Spinner v-if="conversationStore.conversations.loading" />
<p v-else>Load more...</p>
</Button>
<div class="flex justify-center items-center mt-5 relative">
<div v-if="conversationStore.conversations.hasMore && !hasErrored && hasConversations">
<Button variant="link" @click="loadNextPage">
<Spinner v-if="conversationStore.conversations.loading" />
<p v-else>Load more...</p>
</Button>
</div>
<div v-else-if="everythingLoaded">
All conversations loaded 😎
</div>
</div>
</div>
</div>
@@ -108,18 +101,11 @@ import { CONVERSATION_LIST_TYPE, CONVERSATION_PRE_DEFINED_FILTERS } from '@/cons
import { Error } from '@/components/ui/error'
import { Skeleton } from '@/components/ui/skeleton'
import { Input } from '@/components/ui/input'
import { Search, ListFilter } from 'lucide-vue-next'
import {
Tabs,
TabsList,
TabsTrigger,
} from '@/components/ui/tabs'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
import { Button } from '@/components/ui/button'
import {
Select,
@@ -179,6 +165,11 @@ const hasErrored = computed(() => {
return conversationStore.conversations.errorMessage ? true : false
})
const everythingLoaded = computed(() => {
return !conversationStore.conversations.errorMessage && !emptyConversations.value
})
const conversationsLoading = computed(() => {
return conversationStore.conversations.loading
})
+5 -4
View File
@@ -26,10 +26,11 @@ const routes = [
component: UserLoginView
},
{
path: '/account',
name: "account",
component: AccountView
},
path: '/account/:page?',
name: 'account',
component: AccountView,
props: true,
}
]
const router = createRouter({
+18 -5
View File
@@ -1,10 +1,23 @@
import { format } from 'date-fns'
import { format, differenceInMinutes, differenceInHours, differenceInDays } from 'date-fns';
export function formatTime (t) {
export function formatTime(t) {
try {
return format(t, 'h:mm a')
const now = new Date();
const minutesDifference = differenceInMinutes(now, t);
const hoursDifference = differenceInHours(now, t);
const daysDifference = differenceInDays(now, t);
if (minutesDifference < 60) {
return `${minutesDifference} minutes ago`;
} else if (hoursDifference < 24) {
return `${hoursDifference} hours ago`;
} else if (daysDifference < 7) {
return `${daysDifference} days ago`;
} else {
return format(t, 'MMMM d, yyyy h:mm a');
}
} catch (error) {
console.error("error parsing time", error)
console.error("error parsing time", error, "time", t)
return ''
}
}
}
+8 -7
View File
@@ -1,8 +1,9 @@
<template>
<div class="tab-container-default">
<h1>Work in progress.</h1>
</div>
</template>
<script setup>
</script>
import Account from '@/components/account/Account.vue'
</script>
<template>
<Account>
yo
</Account>
</template>
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<!-- Resizable panel last resize value is stored in the localstorage -->
<ResizablePanelGroup direction="horizontal" auto-save-id="conversation.vue.resizable.panel">
<ResizablePanel :min-size="18" :default-size="23" :max-size="23">
<ResizablePanel :min-size="20" :default-size="23" :max-size="23">
<ConversationList></ConversationList>
</ResizablePanel>
<ResizableHandle />
+104 -22
View File
@@ -4,6 +4,8 @@ import (
"context"
"embed"
"encoding/json"
"fmt"
"time"
"github.com/abhinavxd/artemis/internal/automation/models"
cmodels "github.com/abhinavxd/artemis/internal/conversation/models"
@@ -18,12 +20,13 @@ var (
)
type Engine struct {
q queries
lo *logf.Logger
conversationStore ConversationStore
messageStore MessageStore
rules []models.Rule
conversationQ chan cmodels.Conversation
q queries
lo *logf.Logger
conversationStore ConversationStore
messageStore MessageStore
rules []models.Rule
newConversationQ chan string
updateConversationQ chan string
}
type Opts struct {
@@ -32,8 +35,12 @@ type Opts struct {
}
type ConversationStore interface {
Get(uuid string) (cmodels.Conversation, error)
GetRecentConversations(t time.Time) ([]cmodels.Conversation, error)
UpdateTeamAssignee(uuid string, assigneeUUID []byte) error
UpdateUserAssignee(uuid string, assigneeUUID []byte) error
UpdateStatus(uuid string, status []byte) error
UpdatePriority(uuid string, priority []byte) error
}
type MessageStore interface {
@@ -49,20 +56,21 @@ func New(opt Opts) (*Engine, error) {
var (
q queries
e = &Engine{
lo: opt.Lo,
conversationQ: make(chan cmodels.Conversation, 10000),
lo: opt.Lo,
newConversationQ: make(chan string, 5000),
updateConversationQ: make(chan string, 5000),
}
)
if err := dbutil.ScanSQLFile("queries.sql", &q, opt.DB, efs); err != nil {
return nil, err
}
e.q = q
e.rules = e.getRules()
e.rules = e.queryRules()
return e, nil
}
func (e *Engine) ReloadRules() {
e.rules = e.getRules()
e.rules = e.queryRules()
}
func (e *Engine) SetMessageStore(messageStore MessageStore) {
@@ -74,34 +82,98 @@ func (e *Engine) SetConversationStore(conversationStore ConversationStore) {
}
func (e *Engine) Serve(ctx context.Context) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
// Create separate semaphores for each channel
maxWorkers := 10
newConversationSemaphore := make(chan struct{}, maxWorkers)
updateConversationSemaphore := make(chan struct{}, maxWorkers)
timeTriggerSemaphore := make(chan struct{}, maxWorkers)
for {
select {
case <-ctx.Done():
return
case conversation := <-e.conversationQ:
e.processConversations(conversation)
case conversationUUID := <-e.newConversationQ:
newConversationSemaphore <- struct{}{}
go e.handleNewConversation(conversationUUID, newConversationSemaphore)
case conversationUUID := <-e.updateConversationQ:
updateConversationSemaphore <- struct{}{}
go e.handleUpdateConversation(conversationUUID, updateConversationSemaphore)
case <-ticker.C:
e.lo.Info("evaluating time triggers")
timeTriggerSemaphore <- struct{}{}
go e.handleTimeTrigger(timeTriggerSemaphore)
}
}
}
func (e *Engine) EvaluateRules(c cmodels.Conversation) {
select {
case e.conversationQ <- c:
default:
// Queue is full.
e.lo.Warn("EvaluateRules: conversationQ is full, unable to enqueue conversation")
func (e *Engine) handleNewConversation(conversationUUID string, semaphore chan struct{}) {
defer func() { <-semaphore }()
conversation, err := e.conversationStore.Get(conversationUUID)
if err != nil {
e.lo.Error("error could not fetch conversations to evaluate new conversation rules", "conversation_uuid", conversationUUID)
return
}
rules := e.filterRulesByType(models.RuleTypeNewConversation)
e.evalConversationRules(rules, conversation)
}
func (e *Engine) handleUpdateConversation(conversationUUID string, semaphore chan struct{}) {
defer func() { <-semaphore }()
conversation, err := e.conversationStore.Get(conversationUUID)
if err != nil {
e.lo.Error("error could not fetch conversations to evaluate update conversation rules", "conversation_uuid", conversationUUID)
return
}
rules := e.filterRulesByType(models.RuleTypeConversationUpdate)
e.evalConversationRules(rules, conversation)
}
func (e *Engine) handleTimeTrigger(semaphore chan struct{}) {
defer func() { <-semaphore }()
thirtyDaysAgo := time.Now().Add(-30 * 24 * time.Hour)
conversations, err := e.conversationStore.GetRecentConversations(thirtyDaysAgo)
if err != nil {
e.lo.Error("error could not fetch conversations to evaluate time triggers")
return
}
rules := e.filterRulesByType(models.RuleTypeTimeTrigger)
for _, conversation := range conversations {
e.evalConversationRules(rules, conversation)
}
}
func (e *Engine) getRules() []models.Rule {
var rulesJSON []string
func (e *Engine) EvaluateNewConversationRules(conversationUUID string) {
select {
case e.newConversationQ <- conversationUUID:
default:
// Queue is full.
e.lo.Warn("EvaluateNewConversationRules: newConversationQ is full, unable to enqueue conversation")
}
}
func (e *Engine) EvaluateConversationUpdateRules(conversationUUID string) {
select {
case e.updateConversationQ <- conversationUUID:
default:
// Queue is full.
e.lo.Warn("EvaluateConversationUpdateRules: updateConversationQ is full, unable to enqueue conversation")
}
}
func (e *Engine) queryRules() []models.Rule {
var (
rulesJSON []string
rules []models.Rule
)
err := e.q.GetRules.Select(&rulesJSON)
if err != nil {
e.lo.Error("error fetching automation rules", "error", err)
return nil
}
var rules []models.Rule
for _, ruleJSON := range rulesJSON {
var rulesBatch []models.Rule
if err := json.Unmarshal([]byte(ruleJSON), &rulesBatch); err != nil {
@@ -111,6 +183,16 @@ func (e *Engine) getRules() []models.Rule {
rules = append(rules, rulesBatch...)
}
e.lo.Debug("fetched rules", "num", len(rules), "rules", rules)
e.lo.Debug("fetched rules", "num", len(rules), "rules", fmt.Sprintf("%+v", rules))
return rules
}
func (e *Engine) filterRulesByType(ruleType string) []models.Rule {
var filteredRules []models.Rule
for _, rule := range e.rules {
if rule.Type == ruleType {
filteredRules = append(filteredRules, rule)
}
}
return filteredRules
}
@@ -2,6 +2,7 @@ package automation
import (
"fmt"
"strconv"
"strings"
"github.com/abhinavxd/artemis/internal/automation/models"
@@ -9,10 +10,11 @@ import (
"github.com/abhinavxd/artemis/internal/systeminfo"
)
func (e *Engine) processConversations(conversation cmodels.Conversation) {
e.lo.Debug("num rules", "rules", len(e.rules))
for _, rule := range e.rules {
func (e *Engine) evalConversationRules(rules []models.Rule, conversation cmodels.Conversation) {
e.lo.Debug("num rules", "rules", len(rules))
for _, rule := range rules {
e.lo.Debug("eval rule", "groups", len(rule.Groups), "rule", rule)
// At max there can be only 2 groups.
if len(rule.Groups) > 2 {
continue
}
@@ -29,6 +31,8 @@ func (e *Engine) processConversations(conversation cmodels.Conversation) {
for _, action := range rule.Actions {
e.executeActions(conversation, action)
}
} else {
e.lo.Debug("rule evaluation failed, NOT executing actions")
}
}
}
@@ -81,45 +85,57 @@ func (e *Engine) evaluateGroup(rules []models.RuleDetail, operator string, conve
func (e *Engine) evaluateRule(rule models.RuleDetail, conversation cmodels.Conversation) bool {
var (
conversationValue string
conditionMet bool
valueToCompare string
conditionMet bool
)
// Extract the value from the conversation based on the rule's field
switch rule.Field {
case "subject":
conversationValue = conversation.Subject
case "content":
conversationValue = conversation.FirstMessage
case "status":
conversationValue = conversation.Status.String
case "priority":
conversationValue = conversation.Priority.String
case models.ConversationFieldSubject:
valueToCompare = conversation.Subject
case models.ConversationFieldContent:
valueToCompare = conversation.FirstMessage
case models.ConversationFieldStatus:
valueToCompare = conversation.Status.String
case models.ConversationFieldPriority:
valueToCompare = conversation.Priority.String
case models.ConversationFieldAssignedTeamID:
if conversation.AssignedTeamID.Valid {
valueToCompare = strconv.Itoa(conversation.AssignedTeamID.Int)
}
case models.ConversationFieldAssignedUserID:
if conversation.AssignedUserID.Valid {
valueToCompare = strconv.Itoa(conversation.AssignedUserID.Int)
}
default:
e.lo.Error("rule field not recognized", "field", rule.Field)
return false
}
// Lower case the value.
conversationValue = strings.ToLower(conversationValue)
// Compare the conversation value with the rule's value based on the operator
switch rule.Operator {
case "equals":
conditionMet = conversationValue == rule.Value
case "not equal":
conditionMet = conversationValue != rule.Value
case "contains":
conditionMet = strings.Contains(conversationValue, rule.Value)
case "startsWith":
conditionMet = strings.HasPrefix(conversationValue, rule.Value)
case "endsWith":
conditionMet = strings.HasSuffix(conversationValue, rule.Value)
default:
e.lo.Error("logical operator not recognized for evaluating rules", "operator", rule.Operator)
return false
if !rule.CaseSensitiveMatch {
valueToCompare = strings.ToLower(valueToCompare)
rule.Value = strings.ToLower(rule.Value)
}
e.lo.Debug("comparing values", "conversation_value", valueToCompare, "rule_value", rule.Value)
switch rule.Operator {
case models.RuleEquals:
conditionMet = valueToCompare == rule.Value
case models.RuleNotEqual:
conditionMet = valueToCompare != rule.Value
case models.RuleContains:
conditionMet = strings.Contains(valueToCompare, rule.Value)
case models.RuleNotContains:
conditionMet = !strings.Contains(valueToCompare, rule.Value)
case models.RuleSet:
conditionMet = bool(len(valueToCompare) > 0)
case models.RuleNotSet:
conditionMet = !bool(len(valueToCompare) > 0)
default:
e.lo.Error("rule logical operator not recognized", "operator", rule.Operator)
return false
}
return conditionMet
}
@@ -140,12 +156,20 @@ func (e *Engine) applyAction(action models.RuleAction, conversation cmodels.Conv
return err
}
case models.ActionAssignAgent:
if err := e.conversationStore.UpdateStatus(conversation.UUID, []byte(action.Action)); err != nil {
if err := e.conversationStore.UpdateUserAssignee(conversation.UUID, []byte(action.Action)); err != nil {
return err
}
if err := e.messageStore.RecordStatusChange(action.Action, conversation.UUID, systeminfo.SystemUserUUID); err != nil {
return err
}
case models.ActionSetPriority:
if err := e.conversationStore.UpdatePriority(conversation.UUID, []byte(action.Action)); err != nil {
return err
}
case models.ActionSetStatus:
if err := e.conversationStore.UpdateStatus(conversation.UUID, []byte(action.Action)); err != nil {
return err
}
default:
return fmt.Errorf("unrecognized rule action: %s", action.Type)
}
+28 -5
View File
@@ -3,11 +3,33 @@ package models
const (
ActionAssignTeam = "assign_team"
ActionAssignAgent = "assign_agent"
OperatorAnd = "AND"
OperatorOR = "OR"
ActionSetStatus = "set_status"
ActionSetPriority = "set_priority"
OperatorAnd = "AND"
OperatorOR = "OR"
RuleContains = "contains"
RuleNotContains = "not contains"
RuleEquals = "equals"
RuleNotEqual = "not equal"
RuleSet = "set"
RuleNotSet = "not set"
RuleTypeNewConversation = "new_conversation"
RuleTypeConversationUpdate = "conversation_update"
RuleTypeTimeTrigger = "time_trigger"
ConversationFieldSubject = "subject"
ConversationFieldContent = "content"
ConversationFieldStatus = "status"
ConversationFieldPriority = "priority"
ConversationFieldAssignedUserID = "assigned_user_id"
ConversationFieldAssignedTeamID = "assigned_team_id"
)
type Rule struct {
Type string `json:"type" db:"type"`
GroupOperator string `json:"group_operator" db:"group_operator"`
Groups []RuleGroup `json:"groups" db:"groups"`
Actions []RuleAction `json:"actions" db:"actions"`
@@ -19,9 +41,10 @@ type RuleGroup struct {
}
type RuleDetail struct {
Field string `json:"field" db:"field"`
Operator string `json:"operator" db:"operator"`
Value string `json:"value" db:"value"`
Field string `json:"field" db:"field"`
Operator string `json:"operator" db:"operator"`
Value string `json:"value" db:"value"`
CaseSensitiveMatch bool `json:"case_sensitive_match" db:"case_sensitive_match"`
}
type RuleAction struct {
+2 -1
View File
@@ -1,3 +1,4 @@
-- name: get-rules
select rules
select
rules
from automation_rules;
+20 -5
View File
@@ -76,6 +76,7 @@ type queries struct {
GetUUID *sqlx.Stmt `query:"get-uuid"`
GetInboxID *sqlx.Stmt `query:"get-inbox-id"`
GetConversation *sqlx.Stmt `query:"get-conversation"`
GetRecentConversations *sqlx.Stmt `query:"get-recent-conversations"`
GetUnassigned *sqlx.Stmt `query:"get-unassigned"`
GetConversationParticipants *sqlx.Stmt `query:"get-conversation-participants"`
GetConversations string `query:"get-conversations"`
@@ -124,15 +125,29 @@ func (c *Manager) Create(contactID int, inboxID int, meta []byte) (int, string,
}
func (c *Manager) Get(uuid string) (models.Conversation, error) {
var conv models.Conversation
if err := c.q.GetConversation.Get(&conv, uuid); err != nil {
var conversation models.Conversation
if err := c.q.GetConversation.Get(&conversation, uuid); err != nil {
if err == sql.ErrNoRows {
return conv, fmt.Errorf("conversation not found")
c.lo.Error("conversation not found", "uuid", uuid)
return conversation, fmt.Errorf("conversation not found")
}
c.lo.Error("fetching conversation from DB", "error", err)
return conv, fmt.Errorf("error fetching conversation")
return conversation, fmt.Errorf("error fetching conversation")
}
return conv, nil
return conversation, nil
}
func (c *Manager) GetRecentConversations(time time.Time) ([]models.Conversation, error) {
var conversations []models.Conversation
if err := c.q.GetRecentConversations.Select(&conversations, time); err != nil {
if err == sql.ErrNoRows {
c.lo.Error("conversations not found", "created_after", time)
return conversations, fmt.Errorf("conversation not found")
}
c.lo.Error("fetching conversation from DB", "error", err)
return conversations, fmt.Errorf("error fetching conversation")
}
return conversations, nil
}
func (c *Manager) UpdateAssigneeLastSeen(uuid string) error {
+34
View File
@@ -69,6 +69,40 @@ LEFT JOIN users u ON u.id = c.assigned_user_id
LEFT JOIN teams at ON at.id = c.assigned_team_id
WHERE c.uuid = $1;
-- name: get-recent-conversations
SELECT
c.created_at,
c.updated_at,
c.closed_at,
c.resolved_at,
c.priority,
c.status,
c.uuid,
c.reference_number,
c.first_reply_at,
ct.uuid AS contact_uuid,
ct.first_name as first_name,
ct.last_name as last_name,
ct.email as email,
ct.phone_number as phone_number,
ct.avatar_url as avatar_url,
u.uuid AS assigned_user_uuid,
at.uuid AS assigned_team_uuid,
(SELECT COALESCE(
(SELECT json_agg(t.name)
FROM tags t
INNER JOIN conversation_tags ct ON ct.tag_id = t.id
WHERE ct.conversation_id = c.id),
'[]'::json
)) AS tags
FROM conversations c
JOIN contacts ct ON c.contact_id = ct.id
LEFT JOIN users u ON u.id = c.assigned_user_id
LEFT JOIN teams at ON at.id = c.assigned_team_id
WHERE c.created_at > $1 and c.uuid = 'e2f69c9f-17f5-4d09-9aae-12c2a79046a2';
-- name: get-id
SELECT id from conversations where uuid = $1;
+4 -7
View File
@@ -16,7 +16,6 @@ import (
"github.com/abhinavxd/artemis/internal/automation"
"github.com/abhinavxd/artemis/internal/contact"
"github.com/abhinavxd/artemis/internal/conversation"
cmodels "github.com/abhinavxd/artemis/internal/conversation/models"
"github.com/abhinavxd/artemis/internal/dbutil"
"github.com/abhinavxd/artemis/internal/inbox"
"github.com/abhinavxd/artemis/internal/message/models"
@@ -457,13 +456,11 @@ func (m *Manager) processIncomingMessage(in models.IncomingMessage) error {
m.conversationMgr.UpdateLastMessage(in.Message.ConversationID, in.Message.ConversationUUID, content, in.Message.CreatedAt)
}
// Evaluate automation rules for this new conversation.
// Evaluate automation rules for this conversation.
if isNewConversation {
m.automationEngine.EvaluateRules(cmodels.Conversation{
UUID: in.Message.ConversationUUID,
FirstMessage: in.Message.Content,
Subject: in.Message.Subject,
})
m.automationEngine.EvaluateNewConversationRules(in.Message.ConversationUUID)
} else {
m.automationEngine.EvaluateConversationUpdateRules(in.Message.ConversationUUID)
}
return nil