Compare commits

..
Author SHA1 Message Date
Shivam Mishra 1f932391ac feat: kickstart groq 2025-02-04 13:40:10 +05:30
33 changed files with 999 additions and 988 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
steps:
- checkout
- node/install:
node-version: '23.7'
node-version: '20.12'
- node/install-pnpm
- node/install-packages:
pkg-manager: pnpm
+12 -12
View File
@@ -5,30 +5,30 @@
version: '3'
services:
base:
base:
build:
context: ..
dockerfile: .devcontainer/Dockerfile.base
args:
VARIANT: 'ubuntu-22.04'
NODE_VERSION: '23.7.0'
RUBY_VERSION: '3.3.3'
VARIANT: "ubuntu-22.04"
NODE_VERSION: "20.9.0"
RUBY_VERSION: "3.3.3"
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
USER_UID: '1000'
USER_GID: '1000'
USER_UID: "1000"
USER_GID: "1000"
image: base:latest
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
args:
VARIANT: 'ubuntu-22.04'
NODE_VERSION: '23.7.0'
RUBY_VERSION: '3.3.3'
VARIANT: "ubuntu-22.04"
NODE_VERSION: "20.9.0"
RUBY_VERSION: "3.3.3"
# On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000.
USER_UID: '1000'
USER_GID: '1000'
USER_UID: "1000"
USER_GID: "1000"
volumes:
- ..:/workspace:cached
+3 -1
View File
@@ -23,10 +23,12 @@ jobs:
bundler-cache: true
- uses: pnpm/action-setup@v4
with:
version: 9.3.0
- uses: actions/setup-node@v4
with:
node-version: 23
node-version: 20
cache: 'pnpm'
- name: Install pnpm dependencies
+2 -1
View File
@@ -38,6 +38,7 @@ jobs:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -47,7 +48,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 23
node-version: 20
cache: 'pnpm'
- name: Install pnpm dependencies
+7 -3
View File
@@ -19,11 +19,13 @@ jobs:
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v2
with:
version: 9.3.0
- uses: actions/setup-node@v4
with:
node-version: 23
node-version: 20
cache: 'pnpm'
- name: pnpm
@@ -37,7 +39,7 @@ jobs:
- name: setup env
run: |
cp .env.example .env
- name: Run asset compile
run: bundle exec rake assets:precompile
env:
@@ -45,3 +47,5 @@ jobs:
- name: Size Check
run: pnpm run size
+3 -3
View File
@@ -186,7 +186,7 @@ GEM
database_cleaner-core (2.0.1)
datadog-ci (0.8.3)
msgpack
date (3.4.1)
date (3.3.4)
ddtrace (1.23.2)
datadog-ci (~> 0.8.1)
debase-ruby_core_source (= 3.3.1)
@@ -487,7 +487,7 @@ GEM
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.19)
net-imap (0.4.17)
date
net-protocol
net-pop (0.1.2)
@@ -782,7 +782,7 @@ GEM
time_diff (0.3.0)
activesupport
i18n
timeout (0.4.3)
timeout (0.4.1)
trailblazer-option (0.1.2)
twilio-ruby (5.77.0)
faraday (>= 0.9, < 3.0)
+1 -1
View File
@@ -1 +1 @@
3.2.0
3.1.0
@@ -2,7 +2,7 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::
def index
@conversations = Current.account.conversations.includes(
:assignee, :contact, :inbox, :taggings
).where(inbox_id: inbox_ids, contact_id: @contact.id).order(last_activity_at: :desc).limit(20)
).where(inbox_id: inbox_ids, contact_id: @contact.id).order(id: :desc).limit(20)
end
private
@@ -77,7 +77,7 @@ const toggleMessageSignature = () => {
setSignature();
};
// Added this watch to dynamically set signature on target inbox change.
// Added this watch to dynamically set signature.
// Only targetInbox has value and is Advance Editor(used by isEmailOrWebWidgetInbox)
// Set the signature only if the inbox based flag is true
watch(
@@ -86,8 +86,7 @@ watch(
nextTick(() => {
if (newValue && props.isEmailOrWebWidgetInbox) setSignature();
});
},
{ immediate: true }
}
);
const onClickInsertEmoji = emoji => {
@@ -15,14 +15,18 @@ import { useCamelCase } from 'dashboard/composables/useTransformKeys';
* @property {Array} messages - Array of all messages [These are not in camelcase]
*/
const props = defineProps({
readMessages: {
type: Array,
default: () => [],
},
unReadMessages: {
type: Array,
default: () => [],
},
currentUserId: {
type: Number,
required: true,
},
firstUnreadId: {
type: Number,
default: null,
},
isAnEmailChannel: {
type: Boolean,
default: false,
@@ -37,8 +41,12 @@ const props = defineProps({
},
});
const allMessages = computed(() => {
return useCamelCase(props.messages, { deep: true });
const unread = computed(() => {
return useCamelCase(props.unReadMessages, { deep: true });
});
const read = computed(() => {
return useCamelCase(props.readMessages, { deep: true });
});
/**
@@ -100,21 +108,29 @@ const getInReplyToMessage = parentMessage => {
<template>
<ul class="px-4 bg-n-background">
<slot name="beforeAll" />
<template v-for="(message, index) in allMessages" :key="message.id">
<slot
v-if="firstUnreadId && message.id === firstUnreadId"
name="unreadBadge"
/>
<template v-for="(message, index) in read" :key="message.id">
<Message
v-bind="message"
:is-email-inbox="isAnEmailChannel"
:in-reply-to="getInReplyToMessage(message)"
:group-with-next="shouldGroupWithNext(index, allMessages)"
:group-with-next="shouldGroupWithNext(index, read)"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
data-clarity-mask="True"
/>
</template>
<slot name="beforeUnread" />
<template v-for="(message, index) in unread" :key="message.id">
<Message
v-bind="message"
:in-reply-to="getInReplyToMessage(message)"
:group-with-next="shouldGroupWithNext(index, unread)"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
:is-email-inbox="isAnEmailChannel"
data-clarity-mask="True"
/>
</template>
<slot name="after" />
</ul>
</template>
@@ -26,18 +26,7 @@ const ccEmail = computed(() => {
});
const senderName = computed(() => {
const fromEmailAddress = fromEmail.value[0] ?? '';
const senderEmail = sender.value.email ?? '';
if (!fromEmailAddress && !senderEmail) return null;
// if the sender of the conversation and the sender of this particular
// email are the same, only then we return the sender name
if (fromEmailAddress === senderEmail) {
return sender.value.name;
}
return null;
return sender.value.name ?? '';
});
const bccEmail = computed(() => {
@@ -70,19 +59,11 @@ const showMeta = computed(() => {
:class="hasError ? 'text-n-ruby-11' : 'text-n-slate-11'"
>
<template v-if="showMeta">
<div
v-if="fromEmail[0]"
:class="hasError ? 'text-n-ruby-11' : 'text-n-slate-12'"
>
<template v-if="senderName">
<span>
{{ senderName }}
</span>
&lt;{{ fromEmail[0] }}&gt;
</template>
<template v-else>
{{ fromEmail[0] }}
</template>
<div v-if="fromEmail[0]">
<span :class="hasError ? 'text-n-ruby-11' : 'text-n-slate-12'">
{{ senderName }}
</span>
&lt;{{ fromEmail[0] }}&gt;
</div>
<div v-if="toEmail.length">
{{ $t('EMAIL_HEADER.TO') }}: {{ toEmail.join(', ') }}
@@ -38,10 +38,9 @@ onMounted(() => {
});
const formatTime = time => {
if (!time || Number.isNaN(time)) return '00:00';
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
};
const toggleMute = () => {
@@ -50,7 +49,7 @@ const toggleMute = () => {
};
const onTimeUpdate = () => {
currentTime.value = audioPlayer.value?.currentTime;
currentTime.value = audioPlayer.value.currentTime;
};
const seek = event => {
@@ -243,15 +243,6 @@ export default {
unreadMessageCount() {
return this.currentChat.unread_count || 0;
},
unreadMessageLabel() {
const count =
this.unreadMessageCount > 9 ? '9+' : this.unreadMessageCount;
const label =
this.unreadMessageCount > 1
? 'CONVERSATION.UNREAD_MESSAGES'
: 'CONVERSATION.UNREAD_MESSAGE';
return `${count} ${this.$t(label)}`;
},
isInstagramDM() {
return this.conversationType === 'instagram_direct_message';
},
@@ -501,11 +492,12 @@ export default {
<NextMessageList
v-if="showNextBubbles"
class="conversation-panel"
:read-messages="readMessages"
:un-read-messages="unReadMessages"
:current-user-id="currentUserId"
:first-unread-id="unReadMessages[0]?.id"
:is-an-email-channel="isAnEmailChannel"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:messages="getMessages"
:messages="currentChat ? currentChat.messages : []"
>
<template #beforeAll>
<transition name="slide-up">
@@ -515,10 +507,15 @@ export default {
</li>
</transition>
</template>
<template #unreadBadge>
<template #beforeUnread>
<li v-show="unreadMessageCount != 0" class="unread--toast">
<span>
{{ unreadMessageLabel }}
{{ unreadMessageCount > 9 ? '9+' : unreadMessageCount }}
{{
unreadMessageCount > 1
? $t('CONVERSATION.UNREAD_MESSAGES')
: $t('CONVERSATION.UNREAD_MESSAGE')
}}
</span>
</li>
</template>
@@ -99,9 +99,6 @@
},
"fallback": {
"CONTENT": "has shared a url"
},
"contact": {
"CONTENT": "Shared contact"
}
},
"CHAT_SORT_BY_FILTER": {
@@ -24,7 +24,6 @@
"READ_MORE": "Read more",
"WROTE": "wrote:",
"FROM": "from",
"EMAIL": "email",
"EMAIL_SUBJECT": "subject"
"EMAIL": "email"
}
}
@@ -35,10 +35,6 @@ const props = defineProps({
type: Number,
default: 0,
},
emailSubject: {
type: String,
default: '',
},
});
const navigateTo = computed(() => {
@@ -53,28 +49,6 @@ const navigateTo = computed(() => {
});
const createdAtTime = dynamicTime(props.createdAt);
const infoItems = computed(() => [
{
label: 'SEARCH.FROM',
value: props.name,
show: !!props.name,
},
{
label: 'SEARCH.EMAIL',
value: props.email,
show: !!props.email,
},
{
label: 'SEARCH.EMAIL_SUBJECT',
value: props.emailSubject,
show: !!props.emailSubject,
},
]);
const visibleInfoItems = computed(() =>
infoItems.value.filter(item => item.show)
);
</script>
<template>
@@ -112,18 +86,26 @@ const visibleInfoItems = computed(() =>
{{ createdAtTime }}
</span>
</div>
<div class="flex flex-wrap gap-x-2 gap-y-1.5">
<div class="flex gap-2">
<h5
v-for="item in visibleInfoItems"
:key="item.label"
class="m-0 text-sm min-w-0 text-n-slate-12 dark:text-n-slate-12 truncate"
v-if="name"
class="m-0 text-sm min-w-0 truncate text-n-slate-12 dark:text-n-slate-12"
>
<span class="text-xs font-norma text-n-slate-11 dark:text-n-slate-11">
{{ $t('SEARCH.FROM') }}:
</span>
{{ name }}
</h5>
<h5
v-if="email"
class="m-0 overflow-hidden text-sm text-n-slate-12 dark:text-n-slate-12 truncate"
>
<span
class="text-xs font-normal text-n-slate-11 dark:text-n-slate-11"
>
{{ $t(item.label) }}:
{{ $t('SEARCH.EMAIL') }}:
</span>
{{ item.value }}
{{ email }}
</h5>
</div>
<slot />
@@ -1,10 +1,9 @@
<script setup>
import { defineProps, computed } from 'vue';
import { useMapGetter } from 'dashboard/composables/store.js';
import SearchResultSection from './SearchResultSection.vue';
import SearchResultConversationItem from './SearchResultConversationItem.vue';
const props = defineProps({
defineProps({
conversations: {
type: Array,
default: () => [],
@@ -24,13 +23,6 @@ const props = defineProps({
});
const accountId = useMapGetter('getCurrentAccountId');
const conversationsWithSubject = computed(() => {
return props.conversations.map(conversation => ({
...conversation,
mail_subject: conversation.additional_attributes?.mail_subject || '',
}));
});
</script>
<template>
@@ -42,10 +34,7 @@ const conversationsWithSubject = computed(() => {
:is-fetching="isFetching"
>
<ul v-if="conversations.length" class="space-y-1.5 list-none">
<li
v-for="conversation in conversationsWithSubject"
:key="conversation.id"
>
<li v-for="conversation in conversations" :key="conversation.id">
<SearchResultConversationItem
:id="conversation.id"
:name="conversation.contact.name"
@@ -53,7 +42,6 @@ const conversationsWithSubject = computed(() => {
:account-id="accountId"
:inbox="conversation.inbox"
:created-at="conversation.created_at"
:email-subject="conversation.mail_subject"
/>
</li>
</ul>
+1 -36
View File
@@ -1,44 +1,9 @@
class ActionCableBroadcastJob < ApplicationJob
queue_as :critical
include Events::Types
CONVERSATION_UPDATE_EVENTS = [
CONVERSATION_READ,
CONVERSATION_UPDATED,
TEAM_CHANGED,
ASSIGNEE_CHANGED,
CONVERSATION_STATUS_CHANGED
].freeze
def perform(members, event_name, data)
return if members.blank?
broadcast_data = prepare_broadcast_data(event_name, data)
broadcast_to_members(members, event_name, broadcast_data)
end
private
# Ensures that only the latest available data is sent to prevent UI issues
# caused by out-of-order events during high-traffic periods. This prevents
# the conversation job from processing outdated data.
def prepare_broadcast_data(event_name, data)
return data unless CONVERSATION_UPDATE_EVENTS.include?(event_name)
account = Account.find(data[:account_id])
conversation = account.conversations.find_by!(display_id: data[:id])
conversation.push_event_data.merge(account_id: data[:account_id])
end
def broadcast_to_members(members, event_name, broadcast_data)
members.each do |member|
ActionCable.server.broadcast(
member,
{
event: event_name,
data: broadcast_data
}
)
ActionCable.server.broadcast(member, { event: event_name, data: data })
end
end
end
@@ -42,8 +42,7 @@ class Conversations::EventDataPresenter < SimpleDelegator
contact_last_seen_at: contact_last_seen_at.to_i,
last_activity_at: last_activity_at.to_i,
timestamp: last_activity_at.to_i,
created_at: created_at.to_i,
updated_at: updated_at.to_f
created_at: created_at.to_i
}
end
end
@@ -43,7 +43,6 @@ class Telegram::IncomingMessageService
def process_message_attachments
attach_location
attach_files
attach_contact
end
def update_contact_avatar
@@ -137,16 +136,6 @@ class Telegram::IncomingMessageService
)
end
def attach_contact
return unless contact_card
@message.attachments.new(
account_id: @message.account_id,
file_type: :contact,
fallback_title: contact_card['phone_number'].to_s
)
end
def file
@file ||= visual_media_params || params[:message][:voice].presence || params[:message][:audio].presence || params[:message][:document].presence
end
@@ -165,10 +154,6 @@ class Telegram::IncomingMessageService
@location ||= params.dig(:message, :location).presence
end
def contact_card
@contact_card ||= params.dig(:message, :contact).presence
end
def visual_media_params
params[:message][:photo].presence&.last || params.dig(:message, :sticker, :thumb).presence || params[:message][:video].presence
end
@@ -16,8 +16,6 @@ json.payload do
json.agent do
json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present?
end
json.additional_attributes conversation.additional_attributes
end
end
end
@@ -44,7 +44,6 @@ json.muted conversation.muted?
json.snoozed_until conversation.snoozed_until
json.status conversation.status
json.created_at conversation.created_at.to_i
json.updated_at conversation.updated_at.to_f
json.timestamp conversation.last_activity_at.to_i
json.first_reply_created_at conversation.first_reply_created_at.to_i
json.unread_count conversation.unread_incoming_messages.count
@@ -70,9 +70,7 @@
Finish Setup
</button>
</div>
<% end %>
</div>
</section>
<% end %>
</main>
</div>
</body>
+5
View File
@@ -138,6 +138,11 @@
display_title: 'OpenAI API Key'
description: 'The API key used to authenticate requests to OpenAI services for Captain AI.'
locked: false
- name: CAPTAIN_OPEN_AI_BASE_URL
display_title: 'API Base URL'
description: 'The base URL for the OpenAI API. Default: https://api.openai.com, this is useful when using other OpenAI compliant endpoints like Azure, Ollama or Groq.'
value: 'https://api.openai.com'
locked: false
- name: CAPTAIN_OPEN_AI_MODEL
display_title: 'OpenAI Model'
description: 'The OpenAI model configured for use in Captain AI. Default: gpt-4o-mini'
+7 -7
View File
@@ -2,7 +2,7 @@
# Description: Install and manage a Chatwoot installation.
# OS: Ubuntu 20.04 LTS, 22.04 LTS, 24.04 LTS
# Script Version: 3.2.0
# Script Version: 3.1.0
# Run this script as root
set -eu -o errexit -o pipefail -o noclobber -o nounset
@@ -19,7 +19,7 @@ fi
# option --output/-o requires 1 argument
LONGOPTS=console,debug,help,install,Install:,logs:,restart,ssl,upgrade,webserver,version
OPTIONS=cdhiI:l:rsuwv
CWCTL_VERSION="3.2.0"
CWCTL_VERSION="3.1.0"
pg_pass=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 15 ; echo '')
CHATWOOT_HUB_URL="https://hub.2.chatwoot.com/events"
@@ -177,7 +177,7 @@ function install_dependencies() {
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=23
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg 16" > /etc/apt/sources.list.d/pgdg.list
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
@@ -779,15 +779,15 @@ function upgrade_node() {
# Parse major version number
major_version=$(echo "$current_version" | cut -d. -f1)
if [ "$major_version" -ge 23 ]; then
echo "Node.js is already version $current_version (>= 23.x). Skipping Node.js upgrade."
if [ "$major_version" -ge 20 ]; then
echo "Node.js is already version $current_version (>= 20.x). Skipping Node.js upgrade."
return
fi
echo "Upgrading Node.js version to v23.x"
echo "Upgrading Node.js version to v20.x"
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=23
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
apt-get update
+12 -31
View File
@@ -1,12 +1,6 @@
# pre-build stage
FROM node:23-alpine as node
FROM ruby:3.3.3-alpine3.19 AS pre-builder
ARG NODE_VERSION="23.7.0"
ARG PNPM_VERSION="10.2.0"
ENV NODE_VERSION=${NODE_VERSION}
ENV PNPM_VERSION=${PNPM_VERSION}
# ARG default to production settings
# For development docker-compose file overrides ARGS
ARG BUNDLE_WITHOUT="development:test"
@@ -31,24 +25,18 @@ RUN apk update && apk add --no-cache \
tzdata \
postgresql-dev \
postgresql-client \
nodejs=20.15.1-r0 \
git \
curl \
xz \
&& mkdir -p /var/app \
&& gem install bundler
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
RUN npm install -g pnpm@${PNPM_VERSION}
RUN echo 'export PNPM_HOME="/root/.local/share/pnpm"' >> /root/.shrc \
&& echo 'export PATH="$PNPM_HOME:$PATH"' >> /root/.shrc \
&& export PNPM_HOME="/root/.local/share/pnpm" \
&& export PATH="$PNPM_HOME:$PATH" \
&& pnpm --version
# Install pnpm and configure environment
RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh - \
&& echo 'export PNPM_HOME="/root/.local/share/pnpm"' >> /root/.shrc \
&& echo 'export PATH="$PNPM_HOME:$PATH"' >> /root/.shrc \
&& export PNPM_HOME="/root/.local/share/pnpm" \
&& export PATH="$PNPM_HOME:$PATH" \
&& pnpm --version
# Persist the environment variables in Docker
ENV PNPM_HOME="/root/.local/share/pnpm"
@@ -98,10 +86,6 @@ RUN rm -rf /gems/ruby/3.3.0/cache/*.gem \
# final build stage
FROM ruby:3.3.3-alpine3.19
ARG NODE_VERSION="23.7.0"
ARG PNPM_VERSION="10.2.0"
ENV NODE_VERSION=${NODE_VERSION}
ENV PNPM_VERSION=${PNPM_VERSION}
ARG BUNDLE_WITHOUT="development:test"
ENV BUNDLE_WITHOUT ${BUNDLE_WITHOUT}
@@ -130,14 +114,11 @@ RUN apk update && apk add --no-cache \
vips \
&& gem install bundler
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN if [ "$RAILS_ENV" != "production" ]; then \
apk add --no-cache curl \
&& ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \
&& npm install -g pnpm@${PNPM_VERSION} \
apk add --no-cache nodejs=20.15.1-r0; \
# Install pnpm and configure environment
wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh - \
&& source /root/.shrc \
&& pnpm --version; \
fi
@@ -10,7 +10,7 @@ module Enterprise::SuperAdmin::AppConfigsController
when 'internal'
@allowed_configs = internal_config_options
when 'captain'
@allowed_configs = %w[CAPTAIN_OPEN_AI_API_KEY CAPTAIN_OPEN_AI_MODEL CAPTAIN_FIRECRAWL_API_KEY]
@allowed_configs = %w[CAPTAIN_OPEN_AI_API_KEY CAPTAIN_OPEN_AI_MODEL CAPTAIN_OPEN_AI_BASE_URL CAPTAIN_FIRECRAWL_API_KEY]
else
super
end
@@ -1,8 +1,10 @@
class Captain::Llm::BaseOpenAiService
DEFAULT_MODEL = 'gpt-4o-mini'.freeze
DEFAULT_BASE_URL = 'https://api.openai.com'.freeze
def initialize
@client = OpenAI::Client.new(
uri_base: InstallationConfig.find_by!(name: 'CAPTAIN_OPEN_AI_BASE_URL').value || OpenAI::Client::DEFAULT_BASE_URL,
access_token: InstallationConfig.find_by!(name: 'CAPTAIN_OPEN_AI_API_KEY').value,
log_errors: Rails.env.development?
)
+30 -31
View File
@@ -34,17 +34,18 @@
"@chatwoot/ninja-keys": "1.2.3",
"@chatwoot/prosemirror-schema": "1.1.1-next",
"@chatwoot/utils": "^0.0.35",
"@formkit/core": "^1.6.9",
"@formkit/vue": "^1.6.9",
"@formkit/core": "^1.6.7",
"@formkit/vue": "^1.6.7",
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
"@highlightjs/vue-plugin": "^2.1.0",
"@iconify-json/material-symbols": "^1.2.10",
"@june-so/analytics-next": "^2.0.0",
"@lk77/vue3-color": "^3.0.6",
"@radix-ui/colors": "^3.0.0",
"@rails/actioncable": "6.1.3",
"@rails/ujs": "^7.1.400",
"@scmmishra/pico-search": "0.5.4",
"@sentry/vue": "^8.54.0",
"@sentry/vue": "^8.31.0",
"@sindresorhus/slugify": "2.2.1",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/vue-table": "^8.20.5",
@@ -52,25 +53,25 @@
"@vue/compiler-sfc": "^3.5.8",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/components": "^12.5.0",
"@vueuse/core": "^12.5.0",
"@vueuse/components": "^12.0.0",
"@vueuse/core": "^12.0.0",
"activestorage": "^5.2.6",
"axios": "^1.7.9",
"axios": "^1.7.7",
"camelcase-keys": "^9.1.3",
"chart.js": "~4.4.4",
"color2k": "^2.0.2",
"company-email-validator": "^1.1.0",
"core-js": "3.38.1",
"countries-and-timezones": "^3.7.2",
"countries-and-timezones": "^3.6.0",
"date-fns": "2.21.1",
"date-fns-tz": "^1.3.3",
"dompurify": "3.1.6",
"flag-icons": "^7.3.2",
"flag-icons": "^7.2.3",
"floating-vue": "^5.2.2",
"highlight.js": "^11.11.1",
"idb": "^8.0.2",
"highlight.js": "^11.10.0",
"idb": "^8.0.0",
"js-cookie": "^3.0.5",
"libphonenumber-js": "^1.11.19",
"libphonenumber-js": "^1.11.9",
"markdown-it": "^13.0.2",
"markdown-it-link-attributes": "^4.0.1",
"md5": "^2.3.0",
@@ -85,10 +86,10 @@
"video.js": "7.18.1",
"videojs-record": "4.5.0",
"videojs-wavesurfer": "3.8.0",
"vue": "^3.5.13",
"vue": "^3.5.12",
"vue-chartjs": "5.3.1",
"vue-datepicker-next": "^1.0.3",
"vue-dompurify-html": "^5.2.0",
"vue-dompurify-html": "^5.1.0",
"vue-i18n": "9.14.2",
"vue-letter": "^0.2.0",
"vue-multiselect": "3.1.0",
@@ -102,19 +103,18 @@
"wavesurfer.js": "7.8.6"
},
"devDependencies": {
"@egoist/tailwindcss-icons": "^1.9.0",
"@egoist/tailwindcss-icons": "^1.8.1",
"@histoire/plugin-vue": "0.17.15",
"@iconify-json/logos": "^1.2.4",
"@iconify-json/lucide": "^1.2.26",
"@iconify-json/material-symbols": "^1.2.14",
"@iconify-json/ph": "^1.2.2",
"@iconify-json/ri": "^1.2.5",
"@iconify-json/teenyicons": "^1.2.2",
"@iconify-json/logos": "^1.2.3",
"@iconify-json/lucide": "^1.2.11",
"@iconify-json/ph": "^1.2.1",
"@iconify-json/ri": "^1.2.3",
"@iconify-json/teenyicons": "^1.2.1",
"@size-limit/file": "^8.2.4",
"@vitest/coverage-v8": "3.0.5",
"@vitest/coverage-v8": "2.0.1",
"@vue/test-utils": "^2.4.6",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"eslint": "^8.57.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-interactive": "^11.1.0",
@@ -133,14 +133,14 @@
"prettier": "^3.3.3",
"prosemirror-model": "^1.22.3",
"size-limit": "^8.2.4",
"tailwindcss": "^3.4.17",
"vite": "^5.4.12",
"tailwindcss": "^3.4.13",
"vite": "^5.4.8",
"vite-plugin-ruby": "^5.0.0",
"vitest": "3.0.5"
"vitest": "2.0.1"
},
"engines": {
"node": "23.x",
"pnpm": "10.x"
"node": "20.x",
"pnpm": "9.x"
},
"husky": {
"hooks": {
@@ -150,8 +150,8 @@
"pnpm": {
"overrides": {
"vite-node": "2.0.1",
"vite": "5.4.12",
"vitest": "3.0.5"
"vite": "5.4.8",
"vitest": "2.0.1"
}
},
"lint-staged": {
@@ -162,6 +162,5 @@
"*.scss": [
"scss-lint"
]
},
"packageManager": "pnpm@10.2.0+sha512.0d27364e0139c6aadeed65ada153135e0ca96c8da42123bd50047f961339dc7a758fc2e944b428f52be570d1bd3372455c1c65fa2e7aa0bfbf931190f9552001"
}
}
+844 -710
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -538,7 +538,6 @@ RSpec.describe Conversation do
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
created_at: conversation.created_at.to_i,
updated_at: conversation.updated_at.to_f,
waiting_since: conversation.waiting_since.to_i,
priority: nil,
unread_count: 0
@@ -31,7 +31,6 @@ RSpec.describe Conversations::EventDataPresenter do
contact_last_seen_at: conversation.contact_last_seen_at.to_i,
agent_last_seen_at: conversation.agent_last_seen_at.to_i,
created_at: conversation.created_at.to_i,
updated_at: conversation.updated_at.to_f,
waiting_since: conversation.waiting_since.to_i,
priority: nil,
unread_count: 0
@@ -309,22 +309,5 @@ describe Telegram::IncomingMessageService do
expect(telegram_channel.inbox.messages.first.content).to eq('Option 1')
end
end
context 'when valid contact message params' do
it 'creates appropriate conversations, message and contacts' do
params = {
'update_id' => 2_342_342_343_242,
'message' => {
'contact': {
'phone_number': '+918660944581'
}
}.merge(message_params)
}.with_indifferent_access
described_class.new(inbox: telegram_channel.inbox, params: params).perform
expect(telegram_channel.inbox.conversations.count).not_to eq(0)
expect(Contact.all.first.name).to eq('Sojan Jose')
expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('contact')
end
end
end
end