From 975577555d321a136017ee52d68db9926c2ae38d Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Mon, 2 Jun 2025 03:56:04 +0530 Subject: [PATCH 01/11] WIP: allow setting macro in new conversations along with attachments - new composable useFileUpload.js --- frontend/src/App.vue | 2 +- frontend/src/composables/useFileUpload.js | 142 +++++ .../src/features/admin/macros/MacroForm.vue | 78 ++- .../src/features/admin/macros/formSchema.js | 1 + frontend/src/features/command/CommandBox.vue | 81 +-- .../features/conversation/Conversation.vue | 5 +- .../conversation/CreateConversation.vue | 565 +++++++++++------- .../src/features/conversation/ReplyBox.vue | 133 +---- .../features/conversation/ReplyBoxContent.vue | 26 +- .../features/conversation/ReplyBoxMenuBar.vue | 3 +- frontend/src/stores/conversation.js | 37 +- i18n/en.json | 3 +- internal/migrations/v0.6.0.go | 25 + schema.sql | 2 + 14 files changed, 686 insertions(+), 417 deletions(-) create mode 100644 frontend/src/composables/useFileUpload.js diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 81f21940..b75fa2da 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -106,7 +106,7 @@ - + diff --git a/frontend/src/features/conversation/ReplyBox.vue b/frontend/src/features/conversation/ReplyBox.vue index c867e01e..64672115 100644 --- a/frontend/src/features/conversation/ReplyBox.vue +++ b/frontend/src/features/conversation/ReplyBox.vue @@ -70,7 +70,7 @@ @send="processSend" @fileUpload="handleFileUpload" @inlineImageUpload="handleInlineImageUpload" - @fileDelete="handleOnFileDelete" + @fileDelete="handleFileDelete" @aiPromptSelected="handleAiPromptSelected" class="h-full flex-grow" /> @@ -90,6 +90,7 @@ :uploadingFiles="uploadingFiles" :clearEditorContent="clearEditorContent" :contentToSet="contentToSet" + :uploadedFiles="mediaFiles" v-model:htmlContent="htmlContent" v-model:textContent="textContent" v-model:selectedText="selectedText" @@ -105,8 +106,7 @@ @toggleFullscreen="isEditorFullscreen = !isEditorFullscreen" @send="processSend" @fileUpload="handleFileUpload" - @inlineImageUpload="handleInlineImageUpload" - @fileDelete="handleOnFileDelete" + @fileDelete="handleFileDelete" @aiPromptSelected="handleAiPromptSelected" /> @@ -115,7 +115,6 @@ diff --git a/frontend/src/components/combobox/SelectCombobox.vue b/frontend/src/components/combobox/SelectCombobox.vue new file mode 100644 index 00000000..c524afd5 --- /dev/null +++ b/frontend/src/components/combobox/SelectCombobox.vue @@ -0,0 +1,58 @@ + + + diff --git a/frontend/src/components/filter/FilterBuilder.vue b/frontend/src/components/filter/FilterBuilder.vue index 06575e24..6e95665d 100644 --- a/frontend/src/components/filter/FilterBuilder.vue +++ b/frontend/src/components/filter/FilterBuilder.vue @@ -44,79 +44,47 @@
- - +
@@ -146,12 +114,12 @@ import { SelectTrigger, SelectValue } from '@/components/ui/select' -import { Plus, X } from 'lucide-vue-next' +import { Plus } from 'lucide-vue-next' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' -import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar' import { useI18n } from 'vue-i18n' -import ComboBox from '@/components/ui/combobox/ComboBox.vue' +import CloseButton from '@/components/button/CloseButton.vue' +import SelectComboBox from '@/components/combobox/SelectCombobox.vue' const props = defineProps({ fields: { diff --git a/frontend/src/features/admin/automation/ActionBox.vue b/frontend/src/features/admin/automation/ActionBox.vue index 2e660d38..053c3d7d 100644 --- a/frontend/src/features/admin/automation/ActionBox.vue +++ b/frontend/src/features/admin/automation/ActionBox.vue @@ -48,63 +48,17 @@ class="w-48" v-if="action.type && conversationActions[action.type]?.type === 'select'" > - - - - - + :type="action.type === 'assign_team' ? 'team' : 'user'" + />
-
- -
+
import { toRefs } from 'vue' import { Button } from '@/components/ui/button' -import { X } from 'lucide-vue-next' +import CloseButton from '@/components/button/CloseButton.vue' import { useTagStore } from '@/stores/tag' import { Select, @@ -143,13 +97,12 @@ import { SelectTrigger, SelectValue } from '@/components/ui/select' -import ComboBox from '@/components/ui/combobox/ComboBox.vue' -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { SelectTag } from '@/components/ui/select' import { useConversationFilters } from '@/composables/useConversationFilters' import { getTextFromHTML } from '@/utils/strings.js' import { useI18n } from 'vue-i18n' import Editor from '@/features/conversation/ConversationTextEditor.vue' +import SelectComboBox from '@/components/combobox/SelectCombobox.vue' const props = defineProps({ actions: { diff --git a/frontend/src/features/admin/automation/RuleBox.vue b/frontend/src/features/admin/automation/RuleBox.vue index 9f5a9361..8060772e 100644 --- a/frontend/src/features/admin/automation/RuleBox.vue +++ b/frontend/src/features/admin/automation/RuleBox.vue @@ -102,59 +102,12 @@
- - - - - + :type="rule.field === 'assigned_user' ? 'user' : 'team'" + />
@@ -209,9 +162,7 @@
-
- -
+
@@ -242,6 +193,7 @@ import { toRefs, computed, watch } from 'vue' import { Checkbox } from '@/components/ui/checkbox' import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' import { Button } from '@/components/ui/button' +import CloseButton from '@/components/button/CloseButton.vue' import { Select, SelectContent, @@ -258,13 +210,11 @@ import { TagsInputItemDelete, TagsInputItemText } from '@/components/ui/tags-input' -import { X } from 'lucide-vue-next' import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' -import ComboBox from '@/components/ui/combobox/ComboBox.vue' -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { useI18n } from 'vue-i18n' import { useConversationFilters } from '@/composables/useConversationFilters' +import SelectComboBox from '@/components/combobox/SelectCombobox.vue' const props = defineProps({ ruleGroup: { diff --git a/frontend/src/features/admin/automation/RuleTab.vue b/frontend/src/features/admin/automation/RuleTab.vue index ea8114f6..1c178a7b 100644 --- a/frontend/src/features/admin/automation/RuleTab.vue +++ b/frontend/src/features/admin/automation/RuleTab.vue @@ -23,6 +23,21 @@
+
+
+

+ {{ + $t('globals.messages.noResults', { + name: $t('globals.terms.rule', 2).toLowerCase() + }) + }} +

+
+
+
diff --git a/frontend/src/features/admin/macros/ActionBuilder.vue b/frontend/src/features/admin/macros/ActionBuilder.vue index ecaf3484..c079583e 100644 --- a/frontend/src/features/admin/macros/ActionBuilder.vue +++ b/frontend/src/features/admin/macros/ActionBuilder.vue @@ -1,125 +1,132 @@ diff --git a/frontend/src/features/conversation/ReplyBoxMenuBar.vue b/frontend/src/features/conversation/ReplyBoxMenuBar.vue index f59bdb16..893fb982 100644 --- a/frontend/src/features/conversation/ReplyBoxMenuBar.vue +++ b/frontend/src/features/conversation/ReplyBoxMenuBar.vue @@ -13,13 +13,13 @@
- + /> --> diff --git a/frontend/src/features/conversation/sidebar/ConversationSideBar.vue b/frontend/src/features/conversation/sidebar/ConversationSideBar.vue index 034f708a..6baea76e 100644 --- a/frontend/src/features/conversation/sidebar/ConversationSideBar.vue +++ b/frontend/src/features/conversation/sidebar/ConversationSideBar.vue @@ -10,107 +10,31 @@ - - - - - + type="user" + /> - - - - - + type="team" + /> - - - - - + type="priority" + /> String(conversationStore.current?.assigned_user_id)) -const assignedTeamID = computed(() => String(conversationStore.current?.assigned_team_id)) -const priorityID = computed(() => String(conversationStore.current?.priority_id)) const priorityOptions = computed(() => conversationStore.priorityOptions) const fetchTags = async () => { @@ -288,7 +207,6 @@ const selectAgent = (agent) => { handleRemoveAssignee('user') return } - if (conversationStore.current.assigned_user_id == agent.value) return conversationStore.current.assigned_user_id = agent.value handleAssignedUserChange(agent.value) } @@ -298,31 +216,15 @@ const selectTeam = (team) => { handleRemoveAssignee('team') return } - if (conversationStore.current.assigned_team_id == team.value) return - conversationStore.current.assigned_team_id = team.value handleAssignedTeamChange(team.value) } const selectPriority = (priority) => { - if (conversationStore.current.priority_id == priority.value) return conversationStore.current.priority = priority.label conversationStore.current.priority_id = priority.value handlePriorityChange(priority.label) } -const getPriorityIcon = (value) => { - switch (value) { - case '1': - return SignalLow - case '2': - return SignalMedium - case '3': - return SignalHigh - default: - return CircleAlert - } -} - const updateContactCustomAttributes = async (attributes) => { let previousAttributes = conversationStore.current.contact.custom_attributes try { diff --git a/frontend/src/features/conversation/sidebar/CustomAttributes.vue b/frontend/src/features/conversation/sidebar/CustomAttributes.vue index d90df525..79864d34 100644 --- a/frontend/src/features/conversation/sidebar/CustomAttributes.vue +++ b/frontend/src/features/conversation/sidebar/CustomAttributes.vue @@ -196,7 +196,7 @@ const getValidationSchema = (attribute) => { z .number({ invalid_type_error: t('globals.messages.invalid', { - name: t('form.field.value').toLowerCase() + name: t('globals.terms.value').toLowerCase() }) }) .nullable() @@ -209,7 +209,7 @@ const getValidationSchema = (attribute) => { .refine( (val) => !isNaN(Date.parse(val)), t('globals.messages.invalid', { - name: t('form.field.value').toLowerCase() + name: t('globals.terms.value').toLowerCase() }) ) .nullable() @@ -227,7 +227,7 @@ const getValidationSchema = (attribute) => { .string() .refine((val) => attribute.values.includes(val), { message: t('globals.messages.invalid', { - name: t('form.field.value').toLowerCase() + name: t('globals.terms.value').toLowerCase() }) }) .nullable() diff --git a/frontend/src/stores/conversation.js b/frontend/src/stores/conversation.js index 97147d13..2bfa46d3 100644 --- a/frontend/src/stores/conversation.js +++ b/frontend/src/stores/conversation.js @@ -1,5 +1,5 @@ import { defineStore } from 'pinia' -import { computed, reactive, ref, nextTick, watchEffect } from 'vue' +import { computed, reactive, ref, watchEffect } from 'vue' import { CONVERSATION_LIST_TYPE, CONVERSATION_DEFAULT_STATUSES } from '@/constants/conversation' import { handleHTTPError } from '@/utils/http' import { computeRecipientsFromMessage } from '@/utils/email-recipients' @@ -100,7 +100,6 @@ export const useConversationStore = defineStore('conversation', () => { const conversation = reactive({ data: null, participants: {}, - mediaFiles: [], loading: false, errorMessage: '' }) @@ -118,10 +117,6 @@ export const useConversationStore = defineStore('conversation', () => { const incrementMessageVersion = () => setTimeout(() => messages.version++, 0) - function resetMediaFiles () { - conversation.mediaFiles = [] - } - function setListStatus (status, fetch = true) { conversations.status = status if (fetch) { @@ -631,7 +626,6 @@ export const useConversationStore = defineStore('conversation', () => { Object.assign(conversation, { data: null, participants: {}, - mediaFiles: [], macro: {}, loading: false, errorMessage: '' @@ -645,9 +639,8 @@ export const useConversationStore = defineStore('conversation', () => { } - /** Macros **/ + /** Macros for new conversation or open conversation **/ async function setMacro (macro, context) { - console.debug('Setting macro for context:', context, macro) macros.value[context] = macro } @@ -706,7 +699,6 @@ export const useConversationStore = defineStore('conversation', () => { getMacro, setMacro, resetMacro, - resetMediaFiles, removeAssignee, getListSortField, getListStatus, diff --git a/frontend/src/stores/macro.js b/frontend/src/stores/macro.js index f19d6250..a8962326 100644 --- a/frontend/src/stores/macro.js +++ b/frontend/src/stores/macro.js @@ -7,11 +7,11 @@ import { useUserStore } from './user' import api from '@/api' import { permissions as perms } from '@/constants/permissions.js' - export const useMacroStore = defineStore('macroStore', () => { const macroList = ref([]) const emitter = useEmitter() const userStore = useUserStore() + const currentView = ref('') // actionPermissions is a map of action names to their corresponding permissions that a user must have to perform the action. const actionPermissions = { @@ -34,6 +34,14 @@ export const useMacroStore = defineStore('macroStore', () => { userTeams.includes(macro.team_id) || String(macro.user_id) === String(userStore.userID) ) + + // Filter by visible_when if currentView is set. + if (currentView.value) { + filtered = filtered.filter(macro => + !macro.visible_when?.length || macro.visible_when.includes(currentView.value) + ) + } + // Filter macros based on permissions. filtered.forEach(macro => { macro.actions = macro.actions.filter(action => { @@ -42,14 +50,17 @@ export const useMacroStore = defineStore('macroStore', () => { return userStore.can(permission) }) }) + // Skip macros that do not have any actions left AND the macro field `message_content` is empty. filtered = filtered.filter(macro => !(macro.actions.length === 0 && macro.message_content === "")) + return filtered.map(macro => ({ ...macro, label: macro.name, value: String(macro.id), })) }) + const loadMacros = async () => { if (macroList.value.length) return try { @@ -62,9 +73,15 @@ export const useMacroStore = defineStore('macroStore', () => { }) } } + + const setCurrentView = (view) => { + currentView.value = view + } + return { macroList, macroOptions, loadMacros, + setCurrentView } }) \ No newline at end of file diff --git a/i18n/en.json b/i18n/en.json index f7d4b33d..2faaa897 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -93,9 +93,14 @@ "globals.terms.security": "Security | Security", "globals.terms.myInbox": "My Inbox | My Inboxes", "globals.terms.teamInbox": "Team Inbox | Team Inboxes", - "globals.terms.optional": "Optional", + "globals.terms.optional": "Optional | Optionals", + "globals.terms.visibility": "Visibility | Visibilities", + "globals.terms.privateNote": "Private note | Private notes", + "globals.terms.automationRule": "Automation Rule | Automation Rules", + "globals.messages.replying": "Replying", "globals.messages.golangDurationHoursMinutes": "Duration in hours or minutes. Example: 1h, 30m, 1h30m", "globals.messages.badRequest": "Bad request", + "globals.messages.visibleWhen": "Visible when", "globals.messages.adjustFilters": "Try adjusting filters", "globals.messages.errorUpdating": "Error updating {name}", "globals.messages.errorCreating": "Error creating {name}", @@ -133,21 +138,21 @@ "globals.messages.create": "Create {name}", "globals.messages.new": "New {name}", "globals.messages.add": "Add {name}", + "globals.messages.adding": "Adding {name}", + "globals.messages.starting": "Starting {name}", "globals.messages.all": "All {name}", "globals.messages.denied": "{name} denied", "globals.messages.noResults": "No {name} found", "globals.messages.enter": "Enter {name}", "globals.messages.yes": "Yes", - "globals.messages.no": "No", + "globals.messages.no": "No {name}", + "globals.messages.type": "{name} type", "globals.messages.typeOf": "Type of {name}", "globals.messages.invalidEmailAddress": "Invalid email address", "globals.messages.pleaseSelectAtLeastOne": "Please select at least one {name}", "globals.messages.strongPassword": "Password must be between {min} and {max} characters long, should contain at least one uppercase letter, one lowercase letter, one number, and one special character.", - "globals.messages.couldNotReload": "Could not reload {name}. Please restart the app", - "globals.messages.invalid": "Invalid {name}", - "globals.messages.disabled": "{name} is disabled", - "globals.messages.fieldRequired": "{name} required", - "globals.messages.required": "Required", + "globals.messages.couldNotReload": "Could not reload {name}", + "globals.messages.required": "{name} Required", "globals.messages.invalidPortNumber": "Invalid port number", "globals.messages.mustBeNumber": "Must be a number", "globals.messages.fileTypeisNotAnImage": "File type is not an image", @@ -171,6 +176,7 @@ "globals.messages.snooze": "Snooze", "globals.messages.resolve": "Resolve", "globals.messages.applyMacro": "Apply macro", + "globals.messages.deletionConfirmation": "This action cannot be undone. This will permanently delete this {name}.", "globals.messages.atleastOneRecipient": "At least one recipient is required", "globals.messages.startTypingToSearch": "Start typing to search...", "globals.messages.goHourMinuteDuration": "Invalid duration format. Should be a number followed by h (hours), m (minutes).", @@ -318,7 +324,6 @@ "form.field.setValue": "Set value", "form.field.selectEvents": "Select events", "form.field.selectOperator": "Select operator", - "form.field.value": "Value", "form.error.min": "Must be at least {min} characters", "form.error.max": "Must be at most {max} characters", "form.error.minmax": "Must be between {min} and {max} characters", @@ -386,13 +391,10 @@ "admin.conversationTags.deleteConfirmation": "This action cannot be undone. This will permanently delete this tag, and remove it from all conversations.", "admin.macro.messageContent": "Response to be sent when macro is used (optional)", "admin.macro.actions": "Actions (optional)", - "admin.macro.visibility": "Visibility", - "admin.macro.visibility.all": "All users", "admin.macro.messageOrActionRequired": "Either message content or actions are required", "admin.macro.actionTypeRequired": "Action type is required", "admin.macro.actionValueRequired": "Action value is required", - "admin.macro.teamOrUserRequired": "team is required when visibility is `team` & a user is required when visibility is `user`", - "admin.macro.deleteConfirmation": "This action cannot be undone. This will permanently delete this macro.", + "admin.macro.teamOrUserRequired": "Team or user is required", "admin.conversationStatus.name.description": "Set status name. Click save when you're done.", "admin.conversationStatus.deleteConfirmation": "This action cannot be undone. This will permanently delete this status.", "admin.inbox.name.description": "Name for your inbox.", @@ -594,7 +596,6 @@ "ai.enterOpenAIAPIKey": "Enter OpenAI API Key", "ai.apiKey.description": "{provider} API Key is not set or invalid. Please enter a valid API key to use AI features.", "replyBox.reply": "Reply", - "replyBox.privateNote": "Private note", "replyBox.emailAddresess": "Email addresses separated by comma", "replyBox.editor.placeholder": "Shift + Enter to add a new line. Cmd + Enter to send. Cmd + K to open command bar.", "replyBox.invalidEmailsIn": "Invalid email(s) in", diff --git a/internal/macro/macro.go b/internal/macro/macro.go index 035944f3..6ad925e8 100644 --- a/internal/macro/macro.go +++ b/internal/macro/macro.go @@ -11,6 +11,7 @@ import ( "github.com/abhinavxd/libredesk/internal/macro/models" "github.com/jmoiron/sqlx" "github.com/knadh/go-i18n" + "github.com/lib/pq" "github.com/zerodha/logf" ) @@ -67,8 +68,8 @@ func (m *Manager) Get(id int) (models.Macro, error) { } // Create adds a new macro. -func (m *Manager) Create(name, messageContent string, userID, teamID *int, visibility string, actions json.RawMessage) error { - _, err := m.q.Create.Exec(name, messageContent, userID, teamID, visibility, actions) +func (m *Manager) Create(name, messageContent string, userID, teamID *int, visibility string, visibleWhen []string, actions json.RawMessage) error { + _, err := m.q.Create.Exec(name, messageContent, userID, teamID, visibility, pq.StringArray(visibleWhen), actions) if err != nil { m.lo.Error("error creating macro", "error", err) return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.macro}"), nil) @@ -77,8 +78,8 @@ func (m *Manager) Create(name, messageContent string, userID, teamID *int, visib } // Update modifies an existing macro. -func (m *Manager) Update(id int, name, messageContent string, userID, teamID *int, visibility string, actions json.RawMessage) error { - result, err := m.q.Update.Exec(id, name, messageContent, userID, teamID, visibility, actions) +func (m *Manager) Update(id int, name, messageContent string, userID, teamID *int, visibility string, visibleWhen []string, actions json.RawMessage) error { + result, err := m.q.Update.Exec(id, name, messageContent, userID, teamID, visibility, pq.StringArray(visibleWhen), actions) if err != nil { m.lo.Error("error updating macro", "error", err) return envelope.NewError(envelope.GeneralError, m.i18n.Ts("globals.messages.errorUpdating", "name", "{globals.terms.macro}"), nil) diff --git a/internal/macro/models/models.go b/internal/macro/models/models.go index 1ed30aad..3cfa8401 100644 --- a/internal/macro/models/models.go +++ b/internal/macro/models/models.go @@ -3,6 +3,8 @@ package models import ( "encoding/json" "time" + + "github.com/lib/pq" ) type Macro struct { @@ -11,7 +13,8 @@ type Macro struct { UpdatedAt time.Time `db:"updated_at" json:"updated_at"` Name string `db:"name" json:"name"` MessageContent string `db:"message_content" json:"message_content"` - Visibility string `db:"visibility" json:"visibility"` + VisibleWhen pq.StringArray `db:"visible_when" json:"visible_when"` + Visibility string `db:"visibility" json:"visibility"` UserID *int `db:"user_id" json:"user_id,string"` TeamID *int `db:"team_id" json:"team_id,string"` UsageCount int `db:"usage_count" json:"usage_count"` diff --git a/internal/macro/queries.sql b/internal/macro/queries.sql index 9f297fea..289b7798 100644 --- a/internal/macro/queries.sql +++ b/internal/macro/queries.sql @@ -9,6 +9,7 @@ SELECT user_id, team_id, actions, + visible_when, usage_count FROM macros @@ -26,6 +27,7 @@ SELECT user_id, team_id, actions, + visible_when, usage_count FROM macros @@ -34,9 +36,9 @@ ORDER BY -- name: create INSERT INTO - macros (name, message_content, user_id, team_id, visibility, actions) + macros (name, message_content, user_id, team_id, visibility, visible_when, actions) VALUES - ($1, $2, $3, $4, $5, $6); + ($1, $2, $3, $4, $5, $6, $7); -- name: update UPDATE @@ -47,7 +49,8 @@ SET user_id = $4, team_id = $5, visibility = $6, - actions = $7, + visible_when = $7, + actions = $8, updated_at = NOW() WHERE id = $1; @@ -62,6 +65,7 @@ WHERE UPDATE macros SET - usage_count = usage_count + 1 + usage_count = usage_count + 1, + updated_at = NOW() WHERE id = $1; \ No newline at end of file diff --git a/internal/migrations/v0.6.0.go b/internal/migrations/v0.6.0.go index f1dc014f..806b3c89 100644 --- a/internal/migrations/v0.6.0.go +++ b/internal/migrations/v0.6.0.go @@ -294,7 +294,7 @@ func V0_6_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = 'macro_visible_when' ) THEN - CREATE TYPE macro_visible_when AS ENUM ('replying', 'starting_conversation', 'adding_note'); + CREATE TYPE macro_visible_when AS ENUM ('replying', 'starting_conversation', 'adding_private_note'); END IF; END $$; @@ -303,14 +303,15 @@ func V0_6_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { return err } - // Add macro_visible_when column to macros table if it doesn't exist + // Add visible_when column to macros table if it doesn't exist _, err = db.Exec(` ALTER TABLE macros - ADD COLUMN IF NOT EXISTS macro_visible_when macro_visible_when[] NOT NULL DEFAULT ARRAY['replying']; + ADD COLUMN IF NOT EXISTS visible_when macro_visible_when[] NOT NULL DEFAULT ARRAY[]::macro_visible_when[]; `) if err != nil { return err } + return nil } diff --git a/schema.sql b/schema.sql index f269a860..ffb5be20 100644 --- a/schema.sql +++ b/schema.sql @@ -19,7 +19,7 @@ DROP TYPE IF EXISTS "sla_event_status" CASCADE; CREATE TYPE "sla_event_status" A DROP TYPE IF EXISTS "sla_metric" CASCADE; CREATE TYPE "sla_metric" AS ENUM ('first_response', 'resolution', 'next_response'); DROP TYPE IF EXISTS "sla_notification_type" CASCADE; CREATE TYPE "sla_notification_type" AS ENUM ('warning', 'breach'); DROP TYPE IF EXISTS "activity_log_type" CASCADE; CREATE TYPE "activity_log_type" AS ENUM ('agent_login', 'agent_logout', 'agent_away', 'agent_away_reassigned', 'agent_online'); -DROP TYPE IF EXISTS "macro_visible_when" CASCADE; CREATE TYPE "visible_when" AS ENUM ('replying', 'starting_conversation', 'adding_private_note'); +DROP TYPE IF EXISTS "macro_visible_when" CASCADE; CREATE TYPE "macro_visible_when" AS ENUM ('replying', 'starting_conversation', 'adding_private_note'); -- Sequence to generate reference number for conversations. DROP SEQUENCE IF EXISTS conversation_reference_number_sequence; CREATE SEQUENCE conversation_reference_number_sequence START 100; @@ -292,7 +292,7 @@ CREATE TABLE macros ( name TEXT NOT NULL, actions JSONB DEFAULT '{}'::jsonb NOT NULL, visibility macro_visibility NOT NULL, - visible_when macro_visible_when[] NOT NULL DEFAULT ARRAY['replying'], + visible_when macro_visible_when[] NOT NULL DEFAULT ARRAY[]::macro_visible_when[], message_content TEXT NOT NULL, -- Cascade deletes when user is deleted. user_id BIGINT REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE, From 8bc0cce993fa0e17441e3a7dd8bc044e80955abf Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Tue, 3 Jun 2025 04:08:29 +0530 Subject: [PATCH 04/11] fix: update default values for visible_when column in macros table --- internal/migrations/v0.6.0.go | 4 +--- schema.sql | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/migrations/v0.6.0.go b/internal/migrations/v0.6.0.go index 806b3c89..94bc85b2 100644 --- a/internal/migrations/v0.6.0.go +++ b/internal/migrations/v0.6.0.go @@ -306,12 +306,10 @@ func V0_6_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { // Add visible_when column to macros table if it doesn't exist _, err = db.Exec(` ALTER TABLE macros - ADD COLUMN IF NOT EXISTS visible_when macro_visible_when[] NOT NULL DEFAULT ARRAY[]::macro_visible_when[]; + ADD COLUMN IF NOT EXISTS visible_when macro_visible_when[] NOT NULL DEFAULT ARRAY['replying', 'starting_conversation', 'adding_private_note']::macro_visible_when[]; `) if err != nil { return err } - - return nil } diff --git a/schema.sql b/schema.sql index ffb5be20..043179c6 100644 --- a/schema.sql +++ b/schema.sql @@ -292,7 +292,7 @@ CREATE TABLE macros ( name TEXT NOT NULL, actions JSONB DEFAULT '{}'::jsonb NOT NULL, visibility macro_visibility NOT NULL, - visible_when macro_visible_when[] NOT NULL DEFAULT ARRAY[]::macro_visible_when[], + visible_when macro_visible_when[] NOT NULL DEFAULT ARRAY['replying', 'starting_conversation', 'adding_private_note']::macro_visible_when[], message_content TEXT NOT NULL, -- Cascade deletes when user is deleted. user_id BIGINT REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE, From 2dc751e602bec8ffe96ce24b375a0c82bb9d0690 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Tue, 3 Jun 2025 04:29:28 +0530 Subject: [PATCH 05/11] fixes incorrect v-model binding --- .../src/features/admin/automation/RuleBox.vue | 2 +- .../features/admin/macros/ActionBuilder.vue | 31 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/frontend/src/features/admin/automation/RuleBox.vue b/frontend/src/features/admin/automation/RuleBox.vue index 8060772e..51b54f83 100644 --- a/frontend/src/features/admin/automation/RuleBox.vue +++ b/frontend/src/features/admin/automation/RuleBox.vue @@ -103,7 +103,7 @@
{{ $t('globals.messages.no', { name: $t('globals.terms.action', 2).toLowerCase() }) }} - @@ -70,13 +75,20 @@ /> +
@@ -85,7 +97,7 @@ v-if="action.type && config.actions[action.type]?.type === 'tag'" class="max-w-md" > - + - +
@@ -126,7 +130,7 @@ diff --git a/frontend/src/features/conversation/sidebar/ConversationSideBarWrapper.vue b/frontend/src/features/conversation/sidebar/ConversationSideBarWrapper.vue index e0c35623..c8ab89de 100644 --- a/frontend/src/features/conversation/sidebar/ConversationSideBarWrapper.vue +++ b/frontend/src/features/conversation/sidebar/ConversationSideBarWrapper.vue @@ -14,9 +14,9 @@ diff --git a/i18n/en.json b/i18n/en.json index 42656533..5b65c08f 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -589,13 +589,14 @@ "conversation.sidebar.previousConvo": "Previous conversations", "conversation.sidebar.noPreviousConvo": "No previous conversations", "conversation.sidebar.notAvailable": "Not available", - "editor.placeholder": "Shift + Enter to add a new line", + "editor.newLine": "Shift + Enter to add a new line. ", + "editor.send": " Cmd + Enter to send. ", + "editor.cmdK": "Cmd + K to open command bar. ", "ai.apiKeyNotSet": "{provider} API Key is not set. Please ask administrator to set it up", "ai.enterOpenAIAPIKey": "Enter OpenAI API Key", "ai.apiKey.description": "{provider} API Key is not set or invalid. Please enter a valid API key to use AI features.", "replyBox.reply": "Reply", "replyBox.emailAddresess": "Email addresses separated by comma", - "replyBox.editor.placeholder": "Shift + Enter to add a new line. Cmd + Enter to send. Cmd + K to open command bar.", "replyBox.invalidEmailsIn": "Invalid email(s) in", "replyBox.correctEmailErrors": "Please correct the email errors before sending.", "contact.blockConfirm": "Are you sure you want to block this contact? They will no longer be able to interact with you.", From fb2a08ec1acb5ac64bfea64a8ce02a38bd2ae9a9 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Tue, 3 Jun 2025 22:36:49 +0530 Subject: [PATCH 10/11] fix: SelectCombobox adjust item and selected templates for proper emoji rendering --- frontend/src/components/combobox/SelectCombobox.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/combobox/SelectCombobox.vue b/frontend/src/components/combobox/SelectCombobox.vue index c524afd5..aaa0e47a 100644 --- a/frontend/src/components/combobox/SelectCombobox.vue +++ b/frontend/src/components/combobox/SelectCombobox.vue @@ -5,8 +5,9 @@ :items="items" :placeholder="placeholder" > + + +