From 76f129efaf1ea6a21b9e191abf89a7b11ba8605d Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Tue, 24 Feb 2026 20:13:58 +0400 Subject: [PATCH] feat(tiktok): Enable outgoing image attachments (#13620) - Enabled the attachment button for TikTok conversations in the reply box - Auto-split messages when both text and an image are composed together, since the TikTok API rejects mixed text+media in a single message. Fixes https://linear.app/chatwoot/issue/CW-6528/enable-outgoing-image-attachments --- .../widgets/WootWriter/ReplyBottomPanel.vue | 2 +- .../widgets/conversation/ReplyBox.vue | 26 ++++++++++++------- package.json | 2 +- pnpm-lock.yaml | 10 +++---- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue index 241e65505..bc0001238 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue @@ -189,7 +189,7 @@ export default { }, showAudioRecorderButton() { if (this.isEditorDisabled) return false; - if (this.isALineChannel) { + if (this.isALineChannel || this.isATiktokChannel) { return false; } // Disable audio recorder for safari browser as recording is not supported diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index 78a2481e9..e55f13b7c 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -279,7 +279,8 @@ export default { this.isASmsInbox || this.isATelegramChannel || this.isALineChannel || - this.isAnInstagramChannel + this.isAnInstagramChannel || + this.isATiktokChannel ); }, replyButtonLabel() { @@ -751,11 +752,13 @@ export default { this.isATwilioWhatsAppChannel || this.isAWhatsAppCloudChannel || this.is360DialogWhatsAppChannel; - // When users send messages containing both text and attachments on Instagram, Instagram treats them as separate messages. - // Although Chatwoot combines these into a single message, Instagram sends separate echo events for each component. - // This can create duplicate messages in Chatwoot. To prevent this issue, we'll handle text and attachments as separate messages. + // Instagram and TikTok do not support sending text and attachments in the same message. + // For Instagram, combining them causes duplicate messages due to separate echo events per component. + // For TikTok, the API rejects messages that mix text and media. + // To handle both cases, text and attachments are always sent as separate messages. const isOnInstagram = this.isAnInstagramChannel; - if ((isOnWhatsApp || isOnInstagram) && !this.isPrivate) { + const isOnTiktok = this.isATiktokChannel; + if ((isOnWhatsApp || isOnInstagram || isOnTiktok) && !this.isPrivate) { this.sendMessageAsMultipleMessages( this.message, copilotAcceptedMessage @@ -1069,7 +1072,8 @@ export default { const multipleMessagePayload = []; if (this.attachedFiles && this.attachedFiles.length) { - let caption = this.isAnInstagramChannel ? '' : message; + let caption = + this.isAnInstagramChannel || this.isATiktokChannel ? '' : message; this.attachedFiles.forEach(attachment => { const attachedFile = this.globalConfig.directUploadsEnabled ? attachment.blobSignedId @@ -1091,11 +1095,13 @@ export default { const hasNoAttachments = !this.attachedFiles || !this.attachedFiles.length; - // For Instagram, we need a separate text message - // For WhatsApp, we only need a text message if there are no attachments + // For Instagram and TikTok, text must always be sent as a separate message (no captions on attachments). + // For WhatsApp, we only need a text message if there are no attachments. if ( - (this.isAnInstagramChannel && this.message) || - (!this.isAnInstagramChannel && hasNoAttachments) + ((this.isAnInstagramChannel || this.isATiktokChannel) && + this.message) || + (!(this.isAnInstagramChannel || this.isATiktokChannel) && + hasNoAttachments) ) { let messagePayload = { conversationId: this.currentChat.id, diff --git a/package.json b/package.json index 37b1307cd..a850bdf09 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@breezystack/lamejs": "^1.2.7", "@chatwoot/ninja-keys": "1.2.3", "@chatwoot/prosemirror-schema": "1.3.6", - "@chatwoot/utils": "^0.0.51", + "@chatwoot/utils": "^0.0.52", "@formkit/core": "^1.6.7", "@formkit/vue": "^1.6.7", "@hcaptcha/vue3-hcaptcha": "^1.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e880b4aaf..4d5f81119 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,8 +26,8 @@ importers: specifier: 1.3.6 version: 1.3.6 '@chatwoot/utils': - specifier: ^0.0.51 - version: 0.0.51 + specifier: ^0.0.52 + version: 0.0.52 '@formkit/core': specifier: ^1.6.7 version: 1.6.7 @@ -457,8 +457,8 @@ packages: '@chatwoot/prosemirror-schema@1.3.6': resolution: {integrity: sha512-sHRtWqbtiow9mVF1ixim0eGUXfhGK5tuLOdF9Vf53aepjJ+ngEiNVkxQT6FohlEOd886ZsdQxMvmI92IDaUXAQ==} - '@chatwoot/utils@0.0.51': - resolution: {integrity: sha512-WlEmWfOTzR7YZRUWzn5Wpm15/BRudpwqoNckph8TohyDbiim1CP4UZGa+qjajxTbNGLLhtKlm0Xl+X16+5Wceg==} + '@chatwoot/utils@0.0.52': + resolution: {integrity: sha512-e57uVqyVW4tj1gql4YJPNMykqMJPkETn5Y9AmHdhc6Y7oxDXfRXBq27fZrrDadLkZdn5RYVCZjfIhXOumyYv2Q==} engines: {node: '>=10'} '@codemirror/commands@6.7.0': @@ -5010,7 +5010,7 @@ snapshots: prosemirror-utils: 1.2.2(prosemirror-model@1.22.3)(prosemirror-state@1.4.3) prosemirror-view: 1.34.1 - '@chatwoot/utils@0.0.51': + '@chatwoot/utils@0.0.52': dependencies: date-fns: 2.30.0