8000 Chore/merge upstream by gabrieljablonski · Pull Request #38 · fazer-ai/chatwoot · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Chore/merge upstream #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
8000
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ GEM
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.19)
net-imap (0.4.20)
date
net-protocol
net-pop (0.1.2)
Expand Down
11 changes: 9 additions & 2 deletions app/controllers/api/v1/accounts/contacts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,16 @@ def contact_custom_attributes
@contact.custom_attributes
end

def contact_additional_attributes
return @contact.additional_attributes.merge(permitted_params[:additional_attributes]) if permitted_params[:additional_attributes]

@contact.additional_attributes
end

def contact_update_params
# we want the merged custom attributes not the original one
permitted_params.except(:custom_attributes, :avatar_url).merge({ custom_attributes: contact_custom_attributes })
permitted_params.except(:custom_attributes, :avatar_url)
.merge({ custom_attributes: contact_custom_attributes })
.merge({ additional_attributes: contact_additional_attributes })
end

def set_include_contact_inboxes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::Conversations::BaseController
before_action :ensure_api_inbox, only: :update

def index
@messages = message_finder.perform
end
Expand All @@ -11,6 +13,11 @@ def create
render_could_not_create_error(e.message)
end

def update
Messages::StatusUpdateService.new(message, permitted_params[:status], permitted_params[:external_error]).perform
@message = message
end

def destroy
ActiveRecord::Base.transaction do
message.update!(content: I18n.t('conversations.messages.deleted'), content_type: :text, content_attributes: { deleted: true })
Expand All @@ -21,7 +28,9 @@ def destroy
def retry
return if message.blank?

message.update!(status: :sent, content_attributes: {})
service = Messages::StatusUpdateService.new(message, 'sent')
service.perform
message.update!(content_attributes: {})
::SendReplyJob.perform_later(message.id)
rescue StandardError => e
render_could_not_create_error(e.message)
Expand Down Expand Up @@ -56,10 +65,16 @@ def message_finder
end

def permitted_params
params.permit(:id, :target_language)
params.permit(:id, :target_language, :status, :external_error)
end

def already_translated_content_available?
message.translations.present? && message.translations[permitted_params 8000 [:target_language]].present?
end

# API inbox check
def ensure_api_inbox
# Only API inboxes can update messages
render json: { error: 'Message status update is only allowed for API inboxes' }, status: :forbidden unless @conversation.inbox.api?
end
end
2 changes: 2 additions & 0 deletions app/controllers/super_admin/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@ def destroy
# rubocop:enable Rails/I18nLocaleTexts
end
end

SuperAdmin::AccountsController.prepend_mod_with('SuperAdmin::AccountsController')
38 changes: 32 additions & 6 deletions app/dashboards/account_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ class AccountDashboard < Administrate::BaseDashboard
# on pages throughout the dashboard.

enterprise_attribute_types = if ChatwootApp.enterprise?
{
limits: Enterprise::AccountLimitsField,
all_features: Enterprise::AccountFeaturesField
attributes = {
limits: AccountLimitsField
}

# Only show manually managed features in Chatwoot Cloud deployment
attributes[:manually_managed_features] = ManuallyManagedFeaturesField if ChatwootApp.chatwoot_cloud?

# Add all_features last so it appears after manually_managed_features
attributes[:all_features] = AccountFeaturesField

attributes
else
{}
end
Expand Down Expand Up @@ -46,7 +53,14 @@ class AccountDashboard < Administrate::BaseDashboard

# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
enterprise_show_page_attributes = ChatwootApp.enterprise? ? %i[custom_attributes limits all_features] : []
enterprise_show_page_attributes = if ChatwootApp.enterprise?
attrs = %i[custom_attributes limits]
attrs << :manually_managed_features if ChatwootApp.chatwoot_cloud?
attrs << :all_features
attrs
else
[]
end
SHOW_PAGE_ATTRIBUTES = (%i[
id
name
Expand All @@ -61,7 +75,14 @@ class AccountDashboard < Administrate::BaseDashboard
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
enterprise_form_attributes = ChatwootApp.enterprise? ? %i[limits all_features] : []
enterprise_form_attributes = if ChatwootApp.enterprise?
attrs = %i[limits]
attrs << :manually_managed_features if ChatwootApp.chatwoot_cloud?
attrs << :all_features
attrs
else
[]
end
FORM_ATTRIBUTES = (%i[
name
locale
Expand Down Expand Up @@ -96,6 +117,11 @@ def display_resource(account)
# to prevent an error from being raised (wrong number of arguments)
# Reference: https://github.com/thoughtbot/administrate/pull/2356/files#diff-4e220b661b88f9a19ac527c50d6f1577ef6ab7b0bed2bfdf048e22e6bfa74a05R204
def permitted_attributes(action)
super + [limits: {}]
attrs = super + [limits: {}]

# Add manually_managed_features to permitted attributes only for Chatwoot Cloud
attrs << { manually_managed_features: [] } if ChatwootApp.chatwoot_cloud?

attrs
end
end
7 changes: 0 additions & 7 deletions app/fields/enterprise/account_features_field.rb

This file was deleted.

2 changes: 1 addition & 1 deletion app/helpers/super_admin/account_features_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def self.feature_display_names
end

def self.filter_internal_features(features)
return features if GlobalConfig.get_value('DEPLOYMENT_ENV') == 'cloud'
return features if ChatwootApp.chatwoot_cloud?

internal_features = account_features.select { |f| f['chatwoot_internal'] }.pluck('name')
features.except(*internal_features)
Expand Down
7 changes: 7 additions & 0 deletions app/javascript/dashboard/api/captain/assistant.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ class CaptainAssistant extends ApiClient {
},
});
}

playground({ assistantId, messageContent, messageHistory }) {
return axios.post(`${this.url}/${assistantId}/playground`, {
message_content: messageContent,
message_history: messageHistory,
});
}
}

export default new CaptainAssistant();
39 changes: 39 additions & 0 deletions app/javascript/dashboard/components-next/Accordion/Accordion.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script setup>
import { ref, watch } from 'vue';

const props = defineProps({
title: { type: String, required: true },
isOpen: { type: Boolean, default: false },
});

const isExpanded = ref(props.isOpen);

const toggleAccordion = () => {
isExpanded.value = !isExpanded.value;
};

watch(
() => props.isOpen,
newValue => {
isExpanded.value = newValue;
}
);
</script>

<template>
<div class="border rounded-lg border-n-slate-4">
<button
class="flex items-center justify-between w-full p-4 text-left"
@click="toggleAccordion"
>
<span class="text-sm font-medium text-n-slate-12">{{ title }}</span>
<span
class="w-5 h-5 transition-transform duration-200 i-lucide-chevron-down"
:class="{ 'rotate-180': isExpanded }"
/>
</button>
<div v-if="isExpanded" class="p-4 pt-0">
<slot />
</div>
</div>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ useKeyboardEvents(keyboardEvents);
<ContactNoteItem
v-for="note in notes"
:key="note.id"
class="mx-6 py-4"
:note="note"
:written-by="getWrittenBy(note)"
allow-delete
@delete="onDelete"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script setup>
import { useTemplateRef, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { dynamicTime } from 'shared/helpers/timeHelper';
import { useToggle } from '@vueuse/core';
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
import Button from 'dashboard/components-next/button/Button.vue';
Expand All @@ -14,39 +16,63 @@ const props = defineProps({
type: String,
required: true,
},
allowDelete: {
type: Boolean,
default: false,
},
collapsible: {
type: Boolean,
default: false,
},
});

const emit = defineEmits(['delete']);

const noteContentRef = useTemplateRef('noteContentRef');
const needsCollapse = ref(false);
const [isExpanded, toggleExpanded] = useToggle();
const { t } = useI18n();
const { formatMessage } = useMessageFormatter();

const handleDelete = () => {
emit('delete', props.note.id);
};

onMounted(() => {
if (props.collapsible) {
// Check if content height exceeds approximately 4 lines
// Assuming line height is ~1.625 and font size is ~14px
const threshold = 14 * 1.625 * 4; // ~84px
needsCollapse.value = noteContentRef.value?.clientHeight > threshold;
}
});
</script>

<template>
<div
class="flex flex-col gap-2 py-2 mx-6 border-b border-n-strong group/note"
>
<div class="flex items-center justify-between">
<div class="flex items-center gap-1.5 py-2.5 min-w-0">
<div class="flex flex-col gap-2 border-b border-n-strong group/note">
<div class="flex items-center justify-between gap-2">
<div class="flex items-center gap-1.5 min-w-0">
<Avatar
:name="note?.user?.name || 'Bot'"
:src="note?.user?.thumbnail || '/assets/images/chatwoot_bot.png'"
:src="
note?.user?.name
? note?.user?.thumbnail
: '/assets/images/chatwoot_bot.png'
"
:size="16"
rounded-full
/>
<div class="min-w-0 truncate">
<span class="inline-flex items-center gap-1 text-sm text-n-slate-11">
<span class="font-medium">{{ writtenBy }}</span>
<span class="font-medium text-n-slate-12">{{ writtenBy }}</span>
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.WROTE') }}
<span class="font-medium">{{ dynamicTime(note.createdAt) }}</span>
<span class="font-medium text-n-slate-12">
{{ dynamicTime(note.createdAt) }}
</span>
</span>
</div>
</div>
<Button
v-if="allowDelete"
variant="faded"
color="ruby"
size="xs"
Expand All @@ -56,8 +82,28 @@ const handleDelete = () => {
/>
</div>
<p
ref="noteContentRef"
v-dompurify-html="formatMessage(note.content || '')"
class="mb-0 prose-sm prose-p:mb-1 prose-p:mt-0 prose-ul:mb-1 prose-ul:mt-0 text-n-slate-12"
class="mb-0 prose-sm prose-p:text-sm prose-p:leading-relaxed prose-p:mb-1 prose-p:mt-0 prose-ul:mb-1 prose-ul:mt-0 text-n-slate-12"
:class="{
'line-clamp-4': collapsible && !isExpanded && needsCollapse,
}"
/>
<p v-if="collapsible && needsCollapse">
<Button
variant="faded"
color="blue"
size="xs"
:icon="isExpanded ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
@click="() => toggleExpanded()"
>
<template v-if="isExpanded">
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.COLLAPSE') }}
</template>
<template v-else>
{{ t('CONTACTS_LAYOUT.SIDEBAR.NOTES.EXPAND') }}
</template>
</Button>
</p>
</div>
</template>
Loading
0