Compare commits

..
Author SHA1 Message Date
aakashb95 11cbf327f0 fix(contacts): avoid unnecessary contact_inboxes preload 2026-03-11 01:21:41 +05:30
95 changed files with 1533 additions and 4953 deletions
+2 -2
View File
@@ -93,8 +93,8 @@ jobs:
exit 1
fi
mkdir -p ~/tmp
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/7.19.0/openapi-generator-cli-7.19.0.jar > ~/tmp/openapi-generator-cli-7.19.0.jar
java -jar ~/tmp/openapi-generator-cli-7.19.0.jar validate -i swagger/swagger.json
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.3.0/openapi-generator-cli-6.3.0.jar > ~/tmp/openapi-generator-cli-6.3.0.jar
java -jar ~/tmp/openapi-generator-cli-6.3.0.jar validate -i swagger/swagger.json
# Bundle audit
- run:
-1
View File
@@ -103,5 +103,4 @@ CLAUDE.local.md
.histoire
.pnpm-store/*
local/
sw.js
Procfile.worktree
-1
View File
@@ -268,7 +268,6 @@ group :development, :test do
gem 'seed_dump'
gem 'shoulda-matchers'
gem 'simplecov', '>= 0.21', require: false
gem 'skooma'
gem 'spring'
gem 'spring-watcher-listen'
end
-11
View File
@@ -473,12 +473,6 @@ GEM
hana (~> 1.3)
regexp_parser (~> 2.0)
uri_template (~> 0.7)
json_skooma (0.2.5)
bigdecimal
hana (~> 1.3)
regexp_parser (~> 2.0)
uri-idna (~> 0.2)
zeitwerk (~> 2.6)
judoscale-rails (1.8.2)
judoscale-ruby (= 1.8.2)
railties
@@ -916,9 +910,6 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4)
skooma (0.3.7)
json_skooma (~> 0.2.5)
zeitwerk (~> 2.6)
slack-ruby-client (2.7.0)
faraday (>= 2.0.1)
faraday-mashify
@@ -979,7 +970,6 @@ GEM
unicode-emoji (4.0.4)
uniform_notifier (1.17.0)
uri (1.1.1)
uri-idna (0.3.1)
uri_template (0.7.0)
valid_email2 (5.2.6)
activemodel (>= 3.2)
@@ -1153,7 +1143,6 @@ DEPENDENCIES
sidekiq_alive
simplecov (>= 0.21)
simplecov_json_formatter
skooma
slack-ruby-client (~> 2.7.0)
spring
spring-watcher-listen
+1 -1
View File
@@ -104,7 +104,7 @@ class ContactIdentifyAction
# blank identifier or email will throw unique index error
# TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded
@contact.discard_invalid_attrs if discard_invalid_attrs
@contact.save! if @contact.changed?
@contact.save!
enqueue_avatar_job
end
@@ -18,18 +18,10 @@ const newMessage = ref('');
const isLoading = ref(false);
const formatMessagesForApi = () => {
return messages.value.map(message => {
const payload = {
role: message.sender,
content: message.content,
};
if (message.sender === 'assistant' && message.agentName) {
payload.agent_name = message.agentName;
}
return payload;
});
return messages.value.map(message => ({
role: message.sender,
content: message.content,
}));
};
const resetConversation = () => {
@@ -70,7 +62,6 @@ const sendMessage = async () => {
messages.value.push({
content: data.response,
sender: 'assistant',
agentName: data.agent_name,
timestamp: new Date().toISOString(),
});
} catch (error) {
@@ -78,7 +78,7 @@ const onSend = () => {
<div
v-else-if="isGeneratingContent"
key="loading-state"
class="bg-n-iris-5 rounded min-h-[4.75rem] w-full mb-4 p-4 flex items-start"
class="bg-n-iris-5 rounded min-h-16 w-full mb-4 p-4 flex items-start"
>
<div class="flex items-center gap-2">
<CaptainLoader class="text-n-iris-10 size-4" />
@@ -162,8 +162,7 @@ export default {
this.inReplyTo?.id &&
!this.isPrivate &&
this.inboxHasFeature(INBOX_FEATURES.REPLY_TO) &&
!this.is360DialogWhatsAppChannel &&
!this.copilot.isActive.value
!this.is360DialogWhatsAppChannel
);
},
showWhatsappTemplates() {
@@ -1465,7 +1464,7 @@ export default {
}
.reply-box__top {
@apply relative py-0 px-3 -mt-px;
@apply relative py-0 px-4 -mt-px;
}
.emoji-dialog {
@@ -14,7 +14,7 @@ const emit = defineEmits(['dismiss']);
<template>
<div
class="reply-editor bg-n-slate-9/10 rounded-md py-1 pl-2 pr-1 text-xs tracking-wide mt-2 flex items-center gap-1.5"
class="reply-editor bg-n-slate-9/10 rounded-md py-1 pl-2 pr-1 text-xs tracking-wide mt-2 flex items-center gap-1.5 -mx-2"
>
<fluent-icon class="flex-shrink-0 icon" icon="arrow-reply" size="14" />
<div class="flex-grow gap-1 mt-px text-xs truncate">
@@ -38,7 +38,7 @@ describe('useAutomation', () => {
});
useMapGetter.mockImplementation(getter => {
const getterMap = {
'agents/getVerifiedAgents': agents,
'agents/getAgents': agents,
'campaigns/getAllCampaigns': campaigns,
'contacts/getContacts': contacts,
'inboxes/getInboxes': inboxes,
@@ -111,7 +111,7 @@ describe('useMacros', () => {
useStoreGetters.mockReturnValue({
'labels/getLabels': { value: mockLabels },
'teams/getTeams': { value: mockTeams },
'agents/getVerifiedAgents': { value: mockAgents },
'agents/getAgents': { value: mockAgents },
});
});
@@ -167,7 +167,7 @@ describe('useMacros', () => {
useStoreGetters.mockReturnValue({
'labels/getLabels': { value: [] },
'teams/getTeams': { value: [] },
'agents/getVerifiedAgents': { value: [] },
'agents/getAgents': { value: [] },
});
const { getMacroDropdownValues } = useMacros();
@@ -20,7 +20,7 @@ import {
export default function useAutomationValues() {
const getters = useStoreGetters();
const { t } = useI18n();
const agents = useMapGetter('agents/getVerifiedAgents');
const agents = useMapGetter('agents/getAgents');
const campaigns = useMapGetter('campaigns/getAllCampaigns');
const contacts = useMapGetter('contacts/getContacts');
const inboxes = useMapGetter('inboxes/getInboxes');
@@ -13,7 +13,7 @@ export const useMacros = () => {
const labels = computed(() => getters['labels/getLabels'].value);
const teams = computed(() => getters['teams/getTeams'].value);
const agents = computed(() => getters['agents/getVerifiedAgents'].value);
const agents = computed(() => getters['agents/getAgents'].value);
/**
* Get dropdown values based on the specified type
+2 -20
View File
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
import NotificationSubscriptions from '../api/notificationSubscription';
import auth from '../api/auth';
import { useAlert } from 'dashboard/composables';
@@ -15,26 +16,7 @@ export const verifyServiceWorkerExistence = (callback = () => {}) => {
navigator.serviceWorker
.register('/sw.js')
.then(registration => {
// Check for updates on load
registration.update();
// Listen for updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (
newWorker.state === 'installed' &&
navigator.serviceWorker.controller
) {
// eslint-disable-next-line no-console
console.log('New service worker available');
}
});
});
callback(registration);
})
.then(registration => callback(registration))
.catch(registrationError => {
// eslint-disable-next-line
console.log('SW registration failed: ', registrationError);
-166
View File
@@ -1,166 +0,0 @@
#!/usr/bin/env node
/* eslint-disable no-console */
const fs = require('fs');
const path = require('path');
const { build } = require('vite');
const ROOT_DIR = process.cwd();
const PATHS = {
manifest: path.join(ROOT_DIR, 'public/vite/.vite/manifest.json'),
swRuntime: path.join(__dirname, 'sw-runtime.js'),
swRuntimeBuilt: path.join(ROOT_DIR, 'tmp/sw-build/sw-runtime.js'),
pushHandlers: path.join(__dirname, 'push-handlers.js'),
buildDir: path.join(ROOT_DIR, 'tmp/sw-build'),
output: path.join(ROOT_DIR, 'public/sw.js'),
};
const buildSWContent = (runtimeBundle, pushHandlers, assetCount) => `
/* Service Worker for Chatwoot */
/* Generated: ${new Date().toISOString()} */
/* Precached assets: ${assetCount} */
${runtimeBundle}
${pushHandlers}
`;
function generateAssetManifest() {
const manifestPath = fs.existsSync(PATHS.manifest) ? PATHS.manifest : null;
if (!manifestPath) {
console.warn(
'⚠️ No Vite manifest found, skipping asset manifest generation'
);
return [];
}
console.log(`📋 Reading manifest from: ${manifestPath}`);
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
const assets = [];
const seen = new Set();
Object.entries(manifest).forEach(([, entry]) => {
const file = entry.file;
if (file && !seen.has(file)) {
seen.add(file);
if (file.endsWith('.js') || file.endsWith('.css')) {
assets.push({ url: file, revision: null });
}
}
if (entry.css) {
entry.css.forEach(cssFile => {
if (!seen.has(cssFile)) {
seen.add(cssFile);
assets.push({ url: cssFile, revision: null });
}
});
}
if (entry.assets) {
entry.assets.forEach(assetFile => {
if (!seen.has(assetFile)) {
seen.add(assetFile);
assets.push({ url: assetFile, revision: null });
}
});
}
});
console.log(`📦 Found ${assets.length} assets to precache`);
return assets;
}
async function buildServiceWorker() {
console.log('🔨 Building service worker...');
let assetOrigin = process.env.ASSET_CDN_HOST || '';
if (assetOrigin && !assetOrigin.startsWith('http')) {
assetOrigin = `https://${assetOrigin}`;
}
const isProduction = process.env.NODE_ENV === 'production';
const assetPath = '/vite/';
console.log(`📁 Root dir: ${ROOT_DIR}`);
console.log(`🌐 Asset origin: ${assetOrigin || '(local)'}`);
console.log(`🏭 Environment: ${isProduction ? 'production' : 'development'}`);
if (!isProduction) {
console.log(
'⚠️ Development mode: Using push notifications only (no caching)'
);
const devServiceWorker = fs.readFileSync(PATHS.pushHandlers, 'utf8');
fs.writeFileSync(PATHS.output, devServiceWorker);
console.log('✅ Development service worker created at public/sw.js');
return;
}
const assetManifest = generateAssetManifest();
try {
console.log('📦 Building service worker runtime...');
await build({
configFile: false,
css: {
postcss: {
plugins: [],
},
},
build: {
lib: {
entry: PATHS.swRuntime,
formats: ['iife'],
name: 'ServiceWorkerRuntime',
fileName: () => 'sw-runtime.js',
},
outDir: PATHS.buildDir,
emptyOutDir: true,
minify: true,
rollupOptions: {
output: {
inlineDynamicImports: true,
},
},
},
define: {
__ASSET_ORIGIN__: JSON.stringify(assetOrigin),
__ASSET_PATH__: JSON.stringify(assetPath),
__ASSET_MANIFEST__: JSON.stringify(assetManifest),
'process.env.NODE_ENV': JSON.stringify('production'),
},
logLevel: 'warn',
});
console.log('✅ Service worker runtime built');
const runtimeBundle = fs.readFileSync(PATHS.swRuntimeBuilt, 'utf8');
const pushHandlers = fs.readFileSync(PATHS.pushHandlers, 'utf8');
const finalServiceWorker = buildSWContent(
runtimeBundle,
pushHandlers,
assetManifest.length
);
fs.writeFileSync(PATHS.output, finalServiceWorker);
console.log('✅ Service worker built successfully at public/sw.js');
console.log(` - ${assetManifest.length} assets in precache manifest`);
} catch (error) {
console.error('❌ Failed to build service worker:', error);
process.exit(1);
}
}
buildServiceWorker().catch(err => {
console.error('❌ Unhandled error:', err);
process.exit(1);
});
-142
View File
@@ -1,142 +0,0 @@
/* eslint-disable no-restricted-globals, no-use-before-define, no-undef */
// Build-time injected values (replaced by Vite define at build time)
const ASSET_ORIGIN = __ASSET_ORIGIN__;
const ASSET_PATH = __ASSET_PATH__;
const ASSET_MANIFEST = __ASSET_MANIFEST__;
const CACHE_NAME = 'chatwoot-assets-v1';
const EXCLUDED_PATHS = [
'/api/',
'/auth/',
'/rails/',
'/cable',
'/sidekiq',
'/super_admin',
'/swagger',
'/webhooks/',
'/widget',
'/survey/',
'/__vite',
'/sw.js',
];
const BATCH_SIZE = 5;
const buildAssetListChunks = (arr, size) => {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
};
const prefetchBatch = async (cache, baseUrl, assets) => {
const fetchAndCacheAsset = async asset => {
const url = `${baseUrl}${asset.url}`;
if (await cache.match(url)) {
return;
}
try {
const response = await fetch(url);
if (response.ok) {
cache.put(url, response);
}
} catch {
// Ignore prefetch failures
}
};
await Promise.all(assets.map(fetchAndCacheAsset));
};
const cacheFirst = async request => {
const cache = await caches.open(CACHE_NAME);
const cached = await cache.match(request);
if (cached) return cached;
try {
const response = await fetch(request);
if (response.ok) {
cache.put(request, response.clone());
}
return response;
} catch (err) {
const stale = await cache.match(request);
if (stale) return stale;
throw err;
}
};
const prefetchAssets = async () => {
if (!ASSET_MANIFEST?.length) return;
const cache = await caches.open(CACHE_NAME);
const baseUrl = ASSET_ORIGIN
? `${ASSET_ORIGIN}${ASSET_PATH}`
: `${self.location.origin}${ASSET_PATH}`;
const batches = buildAssetListChunks(ASSET_MANIFEST, BATCH_SIZE);
await batches.reduce(
(promise, batch) =>
promise.then(() => prefetchBatch(cache, baseUrl, batch)),
Promise.resolve()
);
};
const cleanupStaleAssets = async () => {
if (!ASSET_MANIFEST?.length) return;
const cache = await caches.open(CACHE_NAME);
const cachedRequests = await cache.keys();
const baseUrl = ASSET_ORIGIN
? `${ASSET_ORIGIN}${ASSET_PATH}`
: `${self.location.origin}${ASSET_PATH}`;
const validUrls = new Set(
ASSET_MANIFEST.map(asset => `${baseUrl}${asset.url}`)
);
const deletions = cachedRequests
.filter(request => {
const url = new URL(request.url);
const isAssetPath = url.pathname.startsWith('/vite/assets/');
return isAssetPath && !validUrls.has(request.url);
})
.map(request => cache.delete(request));
await Promise.all(deletions);
};
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', event => {
event.waitUntil(
Promise.all([cleanupStaleAssets(), prefetchAssets(), self.clients.claim()])
);
});
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
if (EXCLUDED_PATHS.some(path => url.pathname.startsWith(path))) return;
if (request.method !== 'GET') return;
const isAChatwootRequest =
url.origin === self.location.origin ||
(ASSET_ORIGIN && url.origin === ASSET_ORIGIN);
if (!isAChatwootRequest) return;
const isAnAsset =
url.pathname.startsWith('/vite/') ||
url.origin === ASSET_ORIGIN ||
/\.(js|css|woff2?|ttf|otf|eot|svg|png)$/i.test(url.pathname);
if (isAnAsset) {
event.respondWith(cacheFirst(request));
}
});
+1 -1
View File
@@ -143,7 +143,7 @@ class Conversation < ApplicationRecord
end
def last_incoming_message
messages.where(account_id: account_id)&.incoming&.last
messages&.incoming&.last
end
def toggle_status
+2 -2
View File
@@ -125,7 +125,7 @@ class Message < ApplicationRecord
belongs_to :account
belongs_to :inbox
belongs_to :conversation
belongs_to :conversation, touch: true
belongs_to :sender, polymorphic: true, optional: true
has_many :attachments, dependent: :destroy, autosave: true, before_add: :validate_attachments_limit
@@ -429,7 +429,7 @@ class Message < ApplicationRecord
def set_conversation_activity
# rubocop:disable Rails/SkipsModelValidations
conversation.update_columns(last_activity_at: created_at, updated_at: Time.current)
conversation.update_columns(last_activity_at: created_at)
# rubocop:enable Rails/SkipsModelValidations
end
+1 -3
View File
@@ -47,9 +47,7 @@ class ActionService
@agent = @account.users.find_by(id: agent_ids)
return unless @agent.present? && @agent.confirmed?
@conversation.update!(assignee_id: @agent.id)
@conversation.update!(assignee_id: @agent.id) if @agent.present?
end
def remove_label(labels)
+1
View File
@@ -191,6 +191,7 @@
- name: assignment_v2
display_name: Assignment V2
enabled: false
chatwoot_internal: true
- name: twilio_content_templates
display_name: Twilio Content Templates
enabled: false
@@ -24,16 +24,10 @@ class Api::V1::Accounts::Captain::AssistantsController < Api::V1::Accounts::Base
end
def playground
response = if captain_v2_enabled?
Captain::Assistant::AgentRunnerService.new(assistant: @assistant, source: 'playground').generate_response(
message_history: playground_message_history
)
else
Captain::Llm::AssistantChatService.new(assistant: @assistant, source: 'playground').generate_response(
additional_message: playground_params[:message_content],
message_history: message_history
)
end
response = Captain::Llm::AssistantChatService.new(assistant: @assistant).generate_response(
additional_message: params[:message_content],
message_history: message_history
)
render json: response
end
@@ -70,31 +64,10 @@ class Api::V1::Accounts::Captain::AssistantsController < Api::V1::Accounts::Base
end
def playground_params
params.require(:assistant).permit(:message_content, message_history: [:role, :content, :agent_name])
params.require(:assistant).permit(:message_content, message_history: [:role, :content])
end
def message_history
(playground_params[:message_history] || []).map do |message|
{
role: message[:role],
content: message[:content],
agent_name: message[:agent_name]
}.compact
end
end
def playground_message_history
history = message_history
current_message = playground_params[:message_content]
return history if current_message.blank?
current_user_message = { role: 'user', content: current_message }
return history if history.last == current_user_message
history + [current_user_message]
end
def captain_v2_enabled?
@assistant.account.feature_enabled?('captain_integration_v2')
(playground_params[:message_history] || []).map { |message| { role: message[:role], content: message[:content] } }
end
end
@@ -5,6 +5,7 @@ module Captain::ChatHelper
def request_chat_completion
log_chat_completion_request
chat = build_chat
add_messages_to_chat(chat)
@@ -85,8 +86,7 @@ module Captain::ChatHelper
temperature: temperature,
metadata: {
assistant_id: @assistant&.id,
channel_type: resolved_channel_type,
source: @source
channel_type: resolved_channel_type
}.compact
}
end
@@ -130,6 +130,7 @@ module Captain::ChatHelper
end
def log_chat_completion_request
Rails.logger.info("#{self.class.name} Assistant: #{@assistant.id}, requesting completion for #{@messages} with #{@tools&.length || 0} tools")
Rails.logger.info("#{self.class.name} Assistant: #{@assistant.id}, Requesting chat completion " \
"for messages #{@messages} with #{@tools&.length || 0} tools")
end
end
+1 -1
View File
@@ -106,7 +106,7 @@ class Captain::Assistant < ApplicationRecord
scenarios: scenarios.enabled.map do |scenario|
{
title: scenario.title,
key: scenario.handoff_key,
key: scenario.title.parameterize.underscore,
description: scenario.description
}
end,
+1 -39
View File
@@ -24,19 +24,6 @@ class Captain::Scenario < ApplicationRecord
include Concerns::CaptainToolsHelpers
include Concerns::Agentable
# OpenAI enforces a 64-char limit on function names. The ai-agents gem
# prepends "handoff_to_" (11 chars), so we keep a safety margin and cap
# the full tool name to MAX_HANDOFF_TOOL_NAME_LENGTH (60 chars).
# Format: "scenario_{id}_{slug}_agent" for persisted records (stable + readable),
# and "scenario_draft_{slug}_agent" for unsaved records, with slug truncated
# based on the available length budget.
HANDOFF_TOOL_PREFIX = 'handoff_to_'.freeze
HANDOFF_KEY_PREFIX = 'scenario'.freeze
HANDOFF_KEY_SUFFIX = 'agent'.freeze
MAX_HANDOFF_TOOL_NAME_LENGTH = 60
MAX_AGENT_NAME_LENGTH = MAX_HANDOFF_TOOL_NAME_LENGTH - HANDOFF_TOOL_PREFIX.length
MAX_HANDOFF_SLUG_LENGTH = 24
self.table_name = 'captain_scenarios'
belongs_to :assistant, class_name: 'Captain::Assistant'
@@ -55,10 +42,6 @@ class Captain::Scenario < ApplicationRecord
before_save :resolve_tool_references
def handoff_key
[handoff_id_key, compact_handoff_slug, HANDOFF_KEY_SUFFIX].compact.join('_')
end
def prompt_context
{
title: title,
@@ -73,28 +56,7 @@ class Captain::Scenario < ApplicationRecord
private
def agent_name
handoff_key
end
def handoff_id_key
return "#{HANDOFF_KEY_PREFIX}_#{id}" if id.present?
"#{HANDOFF_KEY_PREFIX}_draft"
end
def compact_handoff_slug
slug = title.to_s.parameterize(separator: '_').presence
return nil if slug.blank?
max_slug_length = [MAX_HANDOFF_SLUG_LENGTH, dynamic_slug_max_length].min
return nil if max_slug_length <= 0
slug.first(max_slug_length).sub(/_+\z/, '').presence
end
def dynamic_slug_max_length
# handoff_to_#{scenario_<id>_<slug>_agent}
MAX_AGENT_NAME_LENGTH - handoff_id_key.length - HANDOFF_KEY_SUFFIX.length - 2
"#{title} Agent".parameterize(separator: '_')
end
def agent_tools
@@ -19,11 +19,11 @@ class Captain::Assistant::AgentRunnerService
CONTACT_INBOX_STATE_ATTRIBUTES = %i[id hmac_verified].freeze
CAMPAIGN_STATE_ATTRIBUTES = %i[id title message campaign_type description].freeze
def initialize(assistant:, conversation: nil, callbacks: {}, source: nil)
def initialize(assistant:, conversation: nil, callbacks: {})
@assistant = assistant
@conversation = conversation
@callbacks = callbacks
@source = source
end
def generate_response(message_history: [])
@@ -32,7 +32,8 @@ class Captain::Assistant::AgentRunnerService
process_agent_result(result)
rescue StandardError => e
# In rake/local runs, conversation may not be present, so account is optional here.
# when running the agent runner service in a rake task, the conversation might not have an account associated
# for regular production usage, it will run just fine
ChatwootExceptionTracker.new(e, account: @conversation&.account).capture_exception
Rails.logger.error "[Captain V2] AgentRunnerService error: #{e.message}"
Rails.logger.error e.backtrace.join("\n")
@@ -127,7 +128,6 @@ class Captain::Assistant::AgentRunnerService
assistant_id: @assistant.id,
assistant_config: @assistant.config
}
state[:source] = @source if @source.present?
build_conversation_state(state) if @conversation
state
@@ -140,7 +140,8 @@ class Captain::Assistant::AgentRunnerService
state[:campaign] = @conversation.campaign.attributes.symbolize_keys.slice(*CAMPAIGN_STATE_ATTRIBUTES) if @conversation.campaign
return unless @conversation.contact_inbox
state[:contact_inbox] = @conversation.contact_inbox.attributes.symbolize_keys.slice(*CONTACT_INBOX_STATE_ATTRIBUTES)
state[:contact_inbox] =
@conversation.contact_inbox.attributes.symbolize_keys.slice(*CONTACT_INBOX_STATE_ATTRIBUTES)
end
def build_and_wire_agents
@@ -179,7 +180,6 @@ class Captain::Assistant::AgentRunnerService
format(ATTR_LANGFUSE_METADATA, 'conversation_id') => conversation[:id],
format(ATTR_LANGFUSE_METADATA, 'conversation_display_id') => conversation[:display_id],
format(ATTR_LANGFUSE_METADATA, 'channel_type') => state[:channel_type],
format(ATTR_LANGFUSE_METADATA, 'source') => state[:source],
ATTR_LANGFUSE_TRACE_INPUT => trace_input,
ATTR_LANGFUSE_OBSERVATION_INPUT => trace_input
}.compact.transform_values(&:to_s)
@@ -1,12 +1,11 @@
class Captain::Llm::AssistantChatService < Llm::BaseAiService
include Captain::ChatHelper
def initialize(assistant: nil, conversation_id: nil, source: nil)
def initialize(assistant: nil, conversation_id: nil)
super()
@assistant = assistant
@conversation_id = conversation_id
@source = source
@messages = [system_message]
@response = ''
+3 -9
View File
@@ -8,12 +8,6 @@ task before_assets_precompile: :environment do
system('echo "-------------- Bulding App for Production --------------"')
end
task after_assets_precompile: :environment do
# Build service worker after Vite has generated the asset manifest
system('echo "-------------- Building Service Worker --------------"')
system('NODE_ENV=production pnpm run build:sw')
end
Rake::Task['assets:precompile'].enhance %w[before_assets_precompile] do
Rake::Task['after_assets_precompile'].invoke
end
# every time you execute 'rake assets:precompile'
# run 'before_assets_precompile' first
Rake::Task['assets:precompile'].enhance %w[before_assets_precompile]
+9 -12
View File
@@ -13,7 +13,6 @@
"dev": "overmind start -f ./Procfile.dev",
"ruby:prettier": "bundle exec rubocop -a",
"build:sdk": "BUILD_MODE=library vite build",
"build:sw": "node app/javascript/service-worker/build.js",
"prepare": "husky install",
"size": "size-limit",
"story:dev": "histoire dev",
@@ -37,8 +36,8 @@
"@chatwoot/ninja-keys": "1.2.3",
"@chatwoot/prosemirror-schema": "1.3.7",
"@chatwoot/utils": "^0.0.52",
"@formkit/core": "^1.7.2",
"@formkit/vue": "^1.7.2",
"@formkit/core": "^1.6.7",
"@formkit/vue": "^1.6.7",
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
"@highlightjs/vue-plugin": "^2.1.0",
"@iconify-json/fluent": "^1.2.32",
@@ -50,7 +49,7 @@
"@scmmishra/pico-search": "0.6.0",
"@sentry/vue": "^8.55.0",
"@sindresorhus/slugify": "2.2.1",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/typography": "^0.5.15",
"@tanstack/vue-table": "^8.20.5",
"@twilio/voice-sdk": "^2.12.4",
"@vitejs/plugin-vue": "^5.1.4",
@@ -60,7 +59,7 @@
"@vueuse/components": "^12.0.0",
"@vueuse/core": "^12.0.0",
"activestorage": "^5.2.6",
"axios": "^1.13.6",
"axios": "^1.13.2",
"camelcase-keys": "^9.1.3",
"chart.js": "~4.4.4",
"color2k": "^2.0.2",
@@ -69,7 +68,7 @@
"countries-and-timezones": "^3.6.0",
"date-fns": "2.21.1",
"date-fns-tz": "^1.3.3",
"dompurify": "3.3.2",
"dompurify": "3.2.4",
"flag-icons": "^7.2.3",
"floating-vue": "^5.2.2",
"highlight.js": "^11.10.0",
@@ -100,7 +99,7 @@
"vue": "^3.5.12",
"vue-chartjs": "5.3.1",
"vue-datepicker-next": "^1.0.3",
"vue-dompurify-html": "^5.3.0",
"vue-dompurify-html": "^5.1.0",
"vue-i18n": "9.14.5",
"vue-letter": "^0.2.1",
"vue-router": "~4.4.5",
@@ -112,7 +111,7 @@
"wavesurfer.js": "7.8.6"
},
"devDependencies": {
"@egoist/tailwindcss-icons": "^1.9.2",
"@egoist/tailwindcss-icons": "^1.9.0",
"@histoire/plugin-vue": "0.17.15",
"@iconify-json/logos": "^1.2.10",
"@iconify-json/lucide": "^1.2.82",
@@ -143,7 +142,7 @@
"prettier": "^3.3.3",
"prosemirror-model": "^1.22.3",
"size-limit": "^8.2.4",
"tailwindcss": "^3.4.19",
"tailwindcss": "^3.4.13",
"vite": "^5.4.21",
"vite-plugin-ruby": "^5.0.0",
"vitest": "3.0.5"
@@ -161,9 +160,7 @@
"overrides": {
"vite-node": "2.0.1",
"vite": "5.4.21",
"vitest": "3.0.5",
"minimatch@<4": "3.1.5",
"minimatch@>=9.0.0 <9.0.7": "9.0.9"
"vitest": "3.0.5"
}
},
"lint-staged": {
+240 -152
View File
@@ -8,8 +8,6 @@ overrides:
vite-node: 2.0.1
vite: 5.4.21
vitest: 3.0.5
minimatch@<4: 3.1.5
minimatch@>=9.0.0 <9.0.7: 9.0.9
importers:
@@ -31,11 +29,11 @@ importers:
specifier: ^0.0.52
version: 0.0.52
'@formkit/core':
specifier: ^1.7.2
version: 1.7.2
specifier: ^1.6.7
version: 1.6.7
'@formkit/vue':
specifier: ^1.7.2
version: 1.7.2(vue@3.5.12(typescript@5.6.2))
specifier: ^1.6.7
version: 1.6.7(tailwindcss@3.4.13)(vue@3.5.12(typescript@5.6.2))
'@hcaptcha/vue3-hcaptcha':
specifier: ^1.3.0
version: 1.3.0(vue@3.5.12(typescript@5.6.2))
@@ -70,8 +68,8 @@ importers:
specifier: 2.2.1
version: 2.2.1
'@tailwindcss/typography':
specifier: ^0.5.19
version: 0.5.19(tailwindcss@3.4.19)
specifier: ^0.5.15
version: 0.5.15(tailwindcss@3.4.13)
'@tanstack/vue-table':
specifier: ^8.20.5
version: 8.20.5(vue@3.5.12(typescript@5.6.2))
@@ -100,8 +98,8 @@ importers:
specifier: ^5.2.6
version: 5.2.8
axios:
specifier: ^1.13.6
version: 1.13.6
specifier: ^1.13.2
version: 1.13.2
camelcase-keys:
specifier: ^9.1.3
version: 9.1.3
@@ -127,8 +125,8 @@ importers:
specifier: ^1.3.3
version: 1.3.8(date-fns@2.21.1)
dompurify:
specifier: 3.3.2
version: 3.3.2
specifier: 3.2.4
version: 3.2.4
flag-icons:
specifier: ^7.2.3
version: 7.2.3
@@ -220,8 +218,8 @@ importers:
specifier: ^1.0.3
version: 1.0.3(vue@3.5.12(typescript@5.6.2))
vue-dompurify-html:
specifier: ^5.3.0
version: 5.3.0(vue@3.5.12(typescript@5.6.2))
specifier: ^5.1.0
version: 5.1.0(vue@3.5.12(typescript@5.6.2))
vue-i18n:
specifier: 9.14.5
version: 9.14.5(vue@3.5.12(typescript@5.6.2))
@@ -251,8 +249,8 @@ importers:
version: 7.8.6
devDependencies:
'@egoist/tailwindcss-icons':
specifier: ^1.9.2
version: 1.9.2(tailwindcss@3.4.19)
specifier: ^1.9.0
version: 1.9.0(tailwindcss@3.4.13)
'@histoire/plugin-vue':
specifier: 0.17.15
version: 0.17.15(histoire@0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.21(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)))(vite@5.4.21(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2))
@@ -344,8 +342,8 @@ importers:
specifier: ^8.2.4
version: 8.2.6
tailwindcss:
specifier: ^3.4.19
version: 3.4.19
specifier: ^3.4.13
version: 3.4.13
vite:
specifier: 5.4.21
version: 5.4.21(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)
@@ -404,6 +402,9 @@ packages:
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
'@antfu/utils@8.1.1':
resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==}
'@asamuzakjp/css-color@4.1.0':
resolution: {integrity: sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==}
@@ -699,8 +700,8 @@ packages:
peerDependencies:
postcss-selector-parser: ^6.0.10
'@egoist/tailwindcss-icons@1.9.2':
resolution: {integrity: sha512-I6XsSykmhu2cASg5Hp/ICLsJ/K/1aXPaSKjgbWaNp2xYnb4We/arWMmkhhV+9CglOFCUbqx0A3mM2kWV32ZIhw==}
'@egoist/tailwindcss-icons@1.9.0':
resolution: {integrity: sha512-xWA9cUy6hzlK7Y6TaoRIcwmilSXiTJ8rbXcEdf9uht7yzDgw/yIgF4rThIQMrpD2Y2v4od51+r2y6Z7GStanDQ==}
peerDependencies:
tailwindcss: '*'
@@ -873,35 +874,46 @@ packages:
'@floating-ui/utils@0.2.7':
resolution: {integrity: sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==}
'@formkit/core@1.7.2':
resolution: {integrity: sha512-XDRVqkDtOziU3z44hdvgWEqGpiv6nTNeCmP8cnESKkr24a4keQuEwmfDVvibYV0ywyTMg6ylp2lyklCYeRHykQ==}
'@formkit/core@1.6.7':
resolution: {integrity: sha512-wEoWK7crcCPRV5KJfEGLjjIS+qwbuD8I5Ur0zTtKRQrdO4oRL6kVoubxQOpgnq1l8sWfcRY8Wpf22Wna2LD20Q==}
'@formkit/dev@1.7.2':
resolution: {integrity: sha512-W38xbbFS4h4LTV22kUC6ZbPHBmlM2lVbAz5PSYU3SPaNc4FeXp4GTe4GzLEmcS82B+5L1zbSOgGB43kWZts7wA==}
'@formkit/dev@1.6.7':
resolution: {integrity: sha512-mMtkfvfkl1P1v0haizUE4DadalbG9/3m0ZymmMKKb0F3pojQJFtdfohy67ZQKtmzy4bamowHyEUr+XzLbKY2EA==}
'@formkit/i18n@1.7.2':
resolution: {integrity: sha512-Zs6f+rtP2j2Nnt1HkNrL85WU6rtS1l1Q0BAQjCMGQPsbrppiL7/TZ5bNIrVm0DTntLgG3firDS7bNWAxPSwerQ==}
'@formkit/i18n@1.6.7':
resolution: {integrity: sha512-i9Mnc2XHCm2c10fppEIxdGv+jqOaixO22iFXX3xF+AkJnxtOzV5hP4f3/TeG+BNahGUq8vj+e1y+VMnjS6duxA==}
'@formkit/inputs@1.7.2':
resolution: {integrity: sha512-4xs7RJN8EFGctTCNRXBTvor2O8RvuEEK3q2Hl/RMU5lhhn5tmck90fkkeAr9o+rWRoSNiV8XgbLQArQ/B1IYPw==}
'@formkit/inputs@1.6.7':
resolution: {integrity: sha512-VLxoAJn5VGOEXkGI499lju5Irnu12cu+spI1HL//46nlVkqnb1XcV0k0MK9K+hogz4fGmyZUvHf0lxU6dh7ZZQ==}
'@formkit/observer@1.7.2':
resolution: {integrity: sha512-KUr5mcu2SAeHOOK1FaMyFigtA8hkHLsUkaPFWpTC81j79o1AUjJppmfPaX6PbtlHLeMs2Zq8Qhp6VAhi7D2WJg==}
'@formkit/observer@1.6.7':
resolution: {integrity: sha512-ei5z5ernNMKKiBuoRcFgEthhP1i+KKb02hsPsikLA3XehuoJdWIejn9AAq6jOEGbUMZ5XAAgFJcxzO5tnKuPnw==}
'@formkit/rules@1.7.2':
resolution: {integrity: sha512-2e5qKlXzmL9LAetncFp7xkHEX/UAJsU3bo1iL3idloH3HALf5fIdg3lgOKwCNDMdLjbfiKJ6lVwFaeFfFpcDKw==}
'@formkit/rules@1.6.7':
resolution: {integrity: sha512-adzOuTvf6ghZbV0g0ZH9+MU9jfoF4DojBztAbqzFP/fT4d+WxhSHHlkWq6PU66fHPy3OH4DkWdx9trL1wGHuzQ==}
'@formkit/themes@1.7.2':
resolution: {integrity: sha512-AaZHy7l9D44Ya3cQRqZ8RIpaTTsCjTB1gX13NlpqYSs6yG0yBTmJ7L7kW60bOGdhs/vmIwSel578EBwz98nybQ==}
'@formkit/themes@1.6.7':
resolution: {integrity: sha512-TIiWr4TMAFUg1pQz2E4GErfAhBv2Q2VbWlk6pqXPWI8UyPTjmcinEnCSIWDCX6FPPqiYShBnh8123nTO7pyvjA==}
peerDependencies:
tailwindcss: ^3.2.0
unocss: 0.x.x
windicss: ^3.0.0
peerDependenciesMeta:
tailwindcss:
optional: true
unocss:
optional: true
windicss:
optional: true
'@formkit/utils@1.7.2':
resolution: {integrity: sha512-bBF6alUBOqFfJHjVB95Vck0hp36vlw4QfFJxGfTO6BX68AEaFzzzabtpwfy0DbcHtwHh4Yn7l/rOWGxXEve+QQ==}
'@formkit/utils@1.6.7':
resolution: {integrity: sha512-aU3CDLzCkC5Dnx6iS3swbsIbys7E+2VOaLWFRnS7wk7kFa8EnENi67qc2E2KFE05RT4UCEAIYMAQY6wvek29gA==}
'@formkit/validation@1.7.2':
resolution: {integrity: sha512-JJsLP7AI/++VujdwtBeUcPhCqY3FXSKZ7hajT7Ow5Feab7JLqVPjxVMFbXDgEOLjq0ex1NX/I+EQHfm5sqMRsA==}
'@formkit/validation@1.6.7':
resolution: {integrity: sha512-4wUUG+Pz3hPeiLccYiXAzsrF7SXk28PYAeHJeBngIv9K82ieljBJpvvuCJDyA6SeSMOvmbI92TG4wx4u5cDLOw==}
'@formkit/vue@1.7.2':
resolution: {integrity: sha512-jrvXl2ZhS6X5klTbP4N5zc0Dg37AjbAf8SCVoz7mfligVkmwrrF4SN3waMBcH+n9gqT+vrkOYb6bQXdvjUdnCQ==}
'@formkit/vue@1.6.7':
resolution: {integrity: sha512-w3kjQD0lvtImyyTiy+fLaIZM/4r6sLDbpBIwke8ZA/d5orzNE96JPaloVKbH+HwCo6+z/1mclh1pW1S+7RgMcw==}
peerDependencies:
vue: ^3.4.0
@@ -973,8 +985,8 @@ packages:
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
'@iconify/utils@3.1.0':
resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==}
'@iconify/utils@2.3.0':
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
'@intlify/core-base@9.14.2':
resolution: {integrity: sha512-DZyQ4Hk22sC81MP4qiCDuU+LdaYW91A6lCjq8AWPvY3+mGMzhGDfOCzvyR6YBQxtlPjFqMoFk9ylnNYRAQwXtQ==}
@@ -1279,10 +1291,10 @@ packages:
peerDependencies:
size-limit: 8.2.6
'@tailwindcss/typography@0.5.19':
resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
'@tailwindcss/typography@0.5.15':
resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20'
'@tanstack/table-core@8.20.5':
resolution: {integrity: sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==}
@@ -1567,8 +1579,8 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
acorn@8.15.0:
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
engines: {node: '>=0.4.0'}
hasBin: true
@@ -1700,8 +1712,8 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
axios@1.13.6:
resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==}
axios@1.13.2:
resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1733,8 +1745,8 @@ packages:
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
brace-expansion@2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
@@ -1910,6 +1922,9 @@ packages:
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
confbox@0.2.2:
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
@@ -2151,9 +2166,8 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
dompurify@3.3.2:
resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==}
engines: {node: '>=20'}
dompurify@3.2.4:
resolution: {integrity: sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==}
domutils@3.1.0:
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
@@ -2436,6 +2450,9 @@ packages:
resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==}
engines: {node: '>=12.0.0'}
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
extend-shallow@2.0.1:
resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
engines: {node: '>=0.10.0'}
@@ -2628,6 +2645,10 @@ packages:
resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==}
engines: {node: '>=18'}
globals@15.15.0:
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
engines: {node: '>=18'}
globalthis@1.0.3:
resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==}
engines: {node: '>= 0.4'}
@@ -2775,8 +2796,8 @@ packages:
resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==}
engines: {node: '>= 4'}
immutable@4.3.8:
resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==}
immutable@4.3.7:
resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==}
import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
@@ -2980,10 +3001,6 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
jiti@1.21.7:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
js-beautify@1.15.1:
resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==}
engines: {node: '>=14'}
@@ -3060,6 +3077,9 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
launch-editor@2.9.1:
resolution: {integrity: sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==}
@@ -3077,8 +3097,8 @@ packages:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
lilconfig@3.1.2:
resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==}
engines: {node: '>=14'}
lines-and-columns@1.2.4:
@@ -3111,6 +3131,10 @@ packages:
lit@2.2.6:
resolution: {integrity: sha512-K2vkeGABfSJSfkhqHy86ujchJs3NR9nW1bEEiV+bXDkbiQ60Tv5GUausYN2mXigZn8lC1qXuc46ArQRKYmumZw==}
local-pkg@1.1.2:
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
engines: {node: '>=14'}
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@@ -3123,6 +3147,12 @@ packages:
resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
lodash.castarray@4.4.0:
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -3257,11 +3287,15 @@ packages:
min-document@2.19.0:
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
minimatch@3.1.5:
resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimatch@9.0.9:
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
minimatch@9.0.1:
resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
engines: {node: '>=16 || 14 >=14.17'}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
minimist@1.2.8:
@@ -3274,8 +3308,8 @@ packages:
mitt@3.0.1:
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
mlly@1.8.1:
resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==}
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
mpd-parser@0.21.0:
resolution: {integrity: sha512-NbpMJ57qQzFmfCiP1pbL7cGMbVTD0X1hqNgL0VYP1wLlZXLf/HtmvQpNkOA1AHkPVeGQng+7/jEtSvNUzV7Gdg==}
@@ -3572,6 +3606,9 @@ packages:
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
@@ -3878,6 +3915,9 @@ packages:
engines: {node: '>=10.13.0'}
hasBin: true
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
@@ -4229,8 +4269,8 @@ packages:
resolution: {integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==}
engines: {node: '>=10.0.0'}
tailwindcss@3.4.19:
resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==}
tailwindcss@3.4.13:
resolution: {integrity: sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==}
engines: {node: '>=14.0.0'}
hasBin: true
@@ -4392,8 +4432,8 @@ packages:
uc.micro@2.1.0:
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
ufo@1.6.3:
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
@@ -4582,10 +4622,10 @@ packages:
'@vue/composition-api':
optional: true
vue-dompurify-html@5.3.0:
resolution: {integrity: sha512-HJQGBHbfSPcb6Mu97McdKbX7TqRHZa6Ji8OCpCNyuHca5QvQZ8IiuwghFPSO8OkSQfqXPNPKFMZdCOrnGGmOSQ==}
vue-dompurify-html@5.1.0:
resolution: {integrity: sha512-616o2/PBdOLM2bwlRWLdzeEC9NerLkwiudqNgaIJ5vBQWXec+u7Kuzh+45DtQQrids67s4pHnTnJZLVfyPMxbA==}
peerDependencies:
vue: ^3.4.36
vue: ^3.0.0
vue-eslint-parser@9.4.3:
resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
@@ -4904,6 +4944,8 @@ snapshots:
package-manager-detector: 1.6.0
tinyexec: 1.0.2
'@antfu/utils@8.1.1': {}
'@asamuzakjp/css-color@4.1.0':
dependencies:
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
@@ -5220,10 +5262,12 @@ snapshots:
dependencies:
postcss-selector-parser: 6.1.1
'@egoist/tailwindcss-icons@1.9.2(tailwindcss@3.4.19)':
'@egoist/tailwindcss-icons@1.9.0(tailwindcss@3.4.13)':
dependencies:
'@iconify/utils': 3.1.0
tailwindcss: 3.4.19
'@iconify/utils': 2.3.0
tailwindcss: 3.4.13
transitivePeerDependencies:
- supports-color
'@esbuild/aix-ppc64@0.21.5':
optional: true
@@ -5310,7 +5354,7 @@ snapshots:
ignore: 5.2.4
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.5
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
@@ -5324,7 +5368,7 @@ snapshots:
ignore: 5.2.4
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.5
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
@@ -5341,61 +5385,67 @@ snapshots:
'@floating-ui/utils@0.2.7': {}
'@formkit/core@1.7.2':
'@formkit/core@1.6.7':
dependencies:
'@formkit/utils': 1.7.2
'@formkit/utils': 1.6.7
'@formkit/dev@1.7.2':
'@formkit/dev@1.6.7':
dependencies:
'@formkit/core': 1.7.2
'@formkit/utils': 1.7.2
'@formkit/core': 1.6.7
'@formkit/utils': 1.6.7
'@formkit/i18n@1.7.2':
'@formkit/i18n@1.6.7':
dependencies:
'@formkit/core': 1.7.2
'@formkit/utils': 1.7.2
'@formkit/validation': 1.7.2
'@formkit/core': 1.6.7
'@formkit/utils': 1.6.7
'@formkit/validation': 1.6.7
'@formkit/inputs@1.7.2':
'@formkit/inputs@1.6.7':
dependencies:
'@formkit/core': 1.7.2
'@formkit/utils': 1.7.2
'@formkit/core': 1.6.7
'@formkit/utils': 1.6.7
'@formkit/observer@1.7.2':
'@formkit/observer@1.6.7':
dependencies:
'@formkit/core': 1.7.2
'@formkit/utils': 1.7.2
'@formkit/core': 1.6.7
'@formkit/utils': 1.6.7
'@formkit/rules@1.7.2':
'@formkit/rules@1.6.7':
dependencies:
'@formkit/core': 1.7.2
'@formkit/utils': 1.7.2
'@formkit/validation': 1.7.2
'@formkit/core': 1.6.7
'@formkit/utils': 1.6.7
'@formkit/validation': 1.6.7
'@formkit/themes@1.7.2':
'@formkit/themes@1.6.7(tailwindcss@3.4.13)':
dependencies:
'@formkit/core': 1.7.2
'@formkit/core': 1.6.7
optionalDependencies:
tailwindcss: 3.4.13
'@formkit/utils@1.7.2': {}
'@formkit/utils@1.6.7': {}
'@formkit/validation@1.7.2':
'@formkit/validation@1.6.7':
dependencies:
'@formkit/core': 1.7.2
'@formkit/observer': 1.7.2
'@formkit/utils': 1.7.2
'@formkit/core': 1.6.7
'@formkit/observer': 1.6.7
'@formkit/utils': 1.6.7
'@formkit/vue@1.7.2(vue@3.5.12(typescript@5.6.2))':
'@formkit/vue@1.6.7(tailwindcss@3.4.13)(vue@3.5.12(typescript@5.6.2))':
dependencies:
'@formkit/core': 1.7.2
'@formkit/dev': 1.7.2
'@formkit/i18n': 1.7.2
'@formkit/inputs': 1.7.2
'@formkit/observer': 1.7.2
'@formkit/rules': 1.7.2
'@formkit/themes': 1.7.2
'@formkit/utils': 1.7.2
'@formkit/validation': 1.7.2
'@formkit/core': 1.6.7
'@formkit/dev': 1.6.7
'@formkit/i18n': 1.6.7
'@formkit/inputs': 1.6.7
'@formkit/observer': 1.6.7
'@formkit/rules': 1.6.7
'@formkit/themes': 1.6.7(tailwindcss@3.4.13)
'@formkit/utils': 1.6.7
'@formkit/validation': 1.6.7
vue: 3.5.12(typescript@5.6.2)
transitivePeerDependencies:
- tailwindcss
- unocss
- windicss
'@hcaptcha/vue3-hcaptcha@1.3.0(vue@3.5.12(typescript@5.6.2))':
dependencies:
@@ -5461,7 +5511,7 @@ snapshots:
dependencies:
'@humanwhocodes/object-schema': 2.0.3
debug: 4.3.5
minimatch: 3.1.5
minimatch: 3.1.2
transitivePeerDependencies:
- supports-color
@@ -5499,11 +5549,18 @@ snapshots:
'@iconify/types@2.0.0': {}
'@iconify/utils@3.1.0':
'@iconify/utils@2.3.0':
dependencies:
'@antfu/install-pkg': 1.1.0
'@antfu/utils': 8.1.1
'@iconify/types': 2.0.0
mlly: 1.8.1
debug: 4.4.3
globals: 15.15.0
kolorist: 1.8.0
local-pkg: 1.1.2
mlly: 1.8.0
transitivePeerDependencies:
- supports-color
'@intlify/core-base@9.14.2':
dependencies:
@@ -5790,10 +5847,13 @@ snapshots:
semver: 7.5.3
size-limit: 8.2.6
'@tailwindcss/typography@0.5.19(tailwindcss@3.4.19)':
'@tailwindcss/typography@0.5.15(tailwindcss@3.4.13)':
dependencies:
lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
tailwindcss: 3.4.19
tailwindcss: 3.4.13
'@tanstack/table-core@8.20.5': {}
@@ -6170,7 +6230,7 @@ snapshots:
acorn@8.14.0: {}
acorn@8.16.0: {}
acorn@8.15.0: {}
activestorage@5.2.8:
dependencies:
@@ -6331,7 +6391,7 @@ snapshots:
dependencies:
possible-typed-array-names: 1.0.0
axios@1.13.6:
axios@1.13.2:
dependencies:
follow-redirects: 1.15.11
form-data: 4.0.5
@@ -6371,7 +6431,7 @@ snapshots:
balanced-match: 1.0.2
concat-map: 0.0.1
brace-expansion@2.0.2:
brace-expansion@2.0.1:
dependencies:
balanced-match: 1.0.2
@@ -6573,6 +6633,8 @@ snapshots:
confbox@0.1.8: {}
confbox@0.2.2: {}
config-chain@1.1.13:
dependencies:
ini: 1.3.8
@@ -6788,7 +6850,7 @@ snapshots:
dependencies:
domelementtype: 2.3.0
dompurify@3.3.2:
dompurify@3.2.4:
optionalDependencies:
'@types/trusted-types': 2.0.7
@@ -6815,7 +6877,7 @@ snapshots:
dependencies:
'@one-ini/wasm': 0.1.1
commander: 10.0.1
minimatch: 9.0.9
minimatch: 9.0.1
semver: 7.6.3
ee-first@1.1.1: {}
@@ -7082,7 +7144,7 @@ snapshots:
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
minimatch: 3.1.5
minimatch: 3.1.2
object.fromentries: 2.0.8
object.groupby: 1.0.3
object.values: 1.2.0
@@ -7162,7 +7224,7 @@ snapshots:
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
lodash.merge: 4.6.2
minimatch: 3.1.5
minimatch: 3.1.2
natural-compare: 1.4.0
optionator: 0.9.3
strip-ansi: 6.0.1
@@ -7208,6 +7270,8 @@ snapshots:
expect-type@1.1.0: {}
exsolve@1.0.8: {}
extend-shallow@2.0.1:
dependencies:
is-extendable: 0.1.1
@@ -7392,7 +7456,7 @@ snapshots:
dependencies:
foreground-child: 3.3.0
jackspeak: 3.4.3
minimatch: 9.0.9
minimatch: 9.0.5
minipass: 7.1.2
package-json-from-dist: 1.0.0
path-scurry: 1.11.1
@@ -7402,7 +7466,7 @@ snapshots:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.5
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
@@ -7423,6 +7487,8 @@ snapshots:
globals@15.14.0: {}
globals@15.15.0: {}
globalthis@1.0.3:
dependencies:
define-properties: 1.2.0
@@ -7618,7 +7684,7 @@ snapshots:
ignore@6.0.2: {}
immutable@4.3.8:
immutable@4.3.7:
optional: true
import-fresh@3.3.0:
@@ -7808,8 +7874,6 @@ snapshots:
jiti@1.21.6: {}
jiti@1.21.7: {}
js-beautify@1.15.1:
dependencies:
config-chain: 1.1.13
@@ -7926,6 +7990,8 @@ snapshots:
kind-of@6.0.3: {}
kolorist@1.8.0: {}
launch-editor@2.9.1:
dependencies:
picocolors: 1.1.0
@@ -7942,7 +8008,7 @@ snapshots:
lilconfig@2.1.0: {}
lilconfig@3.1.3: {}
lilconfig@3.1.2: {}
lines-and-columns@1.2.4: {}
@@ -7993,6 +8059,12 @@ snapshots:
lit-element: 3.3.3
lit-html: 2.8.0
local-pkg@1.1.2:
dependencies:
mlly: 1.8.0
pkg-types: 2.3.0
quansync: 0.2.11
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
@@ -8005,6 +8077,10 @@ snapshots:
dependencies:
p-locate: 6.0.0
lodash.castarray@4.4.0: {}
lodash.isplainobject@4.0.6: {}
lodash.merge@4.6.2: {}
lodash.truncate@4.4.2: {}
@@ -8141,13 +8217,17 @@ snapshots:
dependencies:
dom-walk: 0.1.2
minimatch@3.1.5:
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
minimatch@9.0.9:
minimatch@9.0.1:
dependencies:
brace-expansion: 2.0.2
brace-expansion: 2.0.1
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.1
minimist@1.2.8: {}
@@ -8155,12 +8235,12 @@ snapshots:
mitt@3.0.1: {}
mlly@1.8.1:
mlly@1.8.0:
dependencies:
acorn: 8.16.0
acorn: 8.15.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.3
ufo: 1.6.1
mpd-parser@0.21.0:
dependencies:
@@ -8431,7 +8511,13 @@ snapshots:
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.1
mlly: 1.8.0
pathe: 2.0.3
pkg-types@2.3.0:
dependencies:
confbox: 0.2.2
exsolve: 1.0.8
pathe: 2.0.3
pngjs@5.0.0: {}
@@ -8548,7 +8634,7 @@ snapshots:
postcss-load-config@4.0.2(postcss@8.4.47):
dependencies:
lilconfig: 3.1.3
lilconfig: 3.1.2
yaml: 2.5.1
optionalDependencies:
postcss: 8.4.47
@@ -8803,6 +8889,8 @@ snapshots:
pngjs: 5.0.0
yargs: 15.4.1
quansync@0.2.11: {}
querystringify@2.2.0: {}
queue-microtask@1.2.3: {}
@@ -8947,7 +9035,7 @@ snapshots:
sass@1.79.3:
dependencies:
chokidar: 4.0.3
immutable: 4.3.8
immutable: 4.3.7
source-map-js: 1.2.1
optional: true
@@ -9201,7 +9289,7 @@ snapshots:
string-width: 4.2.3
strip-ansi: 6.0.1
tailwindcss@3.4.19:
tailwindcss@3.4.13:
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -9211,12 +9299,12 @@ snapshots:
fast-glob: 3.3.2
glob-parent: 6.0.2
is-glob: 4.0.3
jiti: 1.21.7
lilconfig: 3.1.3
jiti: 1.21.6
lilconfig: 2.1.0
micromatch: 4.0.8
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.1.1
picocolors: 1.1.0
postcss: 8.4.47
postcss-import: 15.1.0(postcss@8.4.47)
postcss-js: 4.0.1(postcss@8.4.47)
@@ -9236,7 +9324,7 @@ snapshots:
terser@5.33.0:
dependencies:
'@jridgewell/source-map': 0.3.11
acorn: 8.16.0
acorn: 8.15.0
commander: 2.20.3
source-map-support: 0.5.21
optional: true
@@ -9245,7 +9333,7 @@ snapshots:
dependencies:
'@istanbuljs/schema': 0.1.3
glob: 10.4.5
minimatch: 9.0.9
minimatch: 9.0.5
text-segmentation@1.0.3:
dependencies:
@@ -9399,7 +9487,7 @@ snapshots:
uc.micro@2.1.0: {}
ufo@1.6.3: {}
ufo@1.6.1: {}
unbox-primitive@1.0.2:
dependencies:
@@ -9584,9 +9672,9 @@ snapshots:
dependencies:
vue: 3.5.12(typescript@5.6.2)
vue-dompurify-html@5.3.0(vue@3.5.12(typescript@5.6.2)):
vue-dompurify-html@5.1.0(vue@3.5.12(typescript@5.6.2)):
dependencies:
dompurify: 3.3.2
dompurify: 3.2.4
vue: 3.5.12(typescript@5.6.2)
vue-eslint-parser@9.4.3(eslint@8.57.0):
@@ -1,6 +1,5 @@
/* eslint-disable no-restricted-globals */
/* eslint-disable no-restricted-globals, no-console */
/* globals clients */
self.addEventListener('push', event => {
let notification = event.data && event.data.json();
@@ -145,24 +145,5 @@ describe ContactIdentifyAction do
expect(contact.phone_number).to be_nil
end
end
context 'when params have not changed' do
it 'skips save and does not issue an UPDATE query' do
contact.update!(name: 'test', identifier: 'test_id', custom_attributes: { test: 'test', test1: 'test1' })
params = { name: 'test', identifier: 'test_id', custom_attributes: { test: 'test', test1: 'test1' } }
# any_instance is needed because merge lookup can reassign @contact to a different Ruby object
expect_any_instance_of(Contact).not_to receive(:save!) # rubocop:disable RSpec/AnyInstance
described_class.new(contact: contact, params: params).perform
end
it 'still enqueues avatar job even when attributes have not changed' do
contact.update!(name: 'test')
params = { name: 'test', avatar_url: 'https://chatwoot-assets.local/sample.png' }
expect(Avatar::AvatarFromUrlJob).to receive(:perform_later).with(contact, params[:avatar_url]).once
described_class.new(contact: contact, params: params).perform
end
end
end
end
@@ -25,7 +25,6 @@ RSpec.describe 'Agents API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.parsed_body.size).to eq(account.users.count)
end
@@ -123,7 +122,6 @@ RSpec.describe 'Agents API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(other_agent.reload.name).to eq(params[:name])
end
@@ -173,7 +171,6 @@ RSpec.describe 'Agents API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.parsed_body['email']).to eq(params[:email])
expect(account.users.last.name).to eq('NewUser')
end
@@ -45,7 +45,6 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
response_body = response.parsed_body
contact_emails = response_body['payload'].pluck('email')
contact_inboxes_source_ids = response_body['payload'].flat_map { |c| c['contact_inboxes'].pluck('source_id') }
@@ -332,7 +331,6 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(contact2.email)
expect(response.body).not_to include(contact1.email)
end
@@ -445,7 +443,6 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(contact2.email)
expect(response.body).to include(contact1.email)
end
@@ -500,7 +497,6 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(contact.name)
end
end
@@ -624,7 +620,6 @@ RSpec.describe 'Contacts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(contact.reload.name).to eq('Test Blub')
# custom attributes are merged properly without overwriting existing ones
expect(contact.custom_attributes).to eq({ 'test' => 'new test', 'test1' => 'test1', 'test2' => 'test2' })
@@ -31,7 +31,6 @@ RSpec.describe 'Conversation Messages API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(conversation.messages.count).to eq(1)
expect(conversation.messages.first.content).to eq(params[:content])
end
@@ -183,7 +182,6 @@ RSpec.describe 'Conversation Messages API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:meta][:contact][:id]).to eq(conversation.contact_id)
end
end
@@ -27,7 +27,6 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
body = JSON.parse(response.body, symbolize_names: true)
expect(body[:data][:meta][:all_count]).to eq(1)
expect(body[:data][:meta].keys).to include(:all_count, :mine_count, :assigned_count, :unassigned_count)
@@ -166,7 +165,6 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
response_data = JSON.parse(response.body, symbolize_names: true)
expect(response_data.count).to eq(2)
end
@@ -236,7 +234,6 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:id]).to eq(conversation.display_id)
end
@@ -285,7 +282,6 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:priority]).to eq('high')
end
@@ -346,7 +342,6 @@ RSpec.describe 'Conversations API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
response_data = JSON.parse(response.body, symbolize_names: true)
expect(response_data[:additional_attributes]).to eq(additional_attributes)
end
@@ -454,11 +449,9 @@ RSpec.describe 'Conversations API', type: :request do
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
headers: agent.create_new_auth_token,
params: { status: 'open' },
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(conversation.reload.status).to eq('open')
end
@@ -32,7 +32,6 @@ RSpec.describe 'Inboxes API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:payload].size).to eq(2)
end
@@ -96,7 +95,6 @@ RSpec.describe 'Inboxes API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(JSON.parse(response.body, symbolize_names: true)[:id]).to eq(inbox.id)
end
@@ -385,7 +383,6 @@ RSpec.describe 'Inboxes API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include('test.com')
end
@@ -481,7 +478,6 @@ RSpec.describe 'Inboxes API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(inbox.reload.enable_auto_assignment).to be_falsey
expect(inbox.reload.portal_id).to eq(portal.id)
expect(response.parsed_body['name']).to eq 'new test inbox'
@@ -22,7 +22,6 @@ RSpec.describe 'Teams API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.parsed_body.first['id']).to eq(account.teams.first.id)
end
end
@@ -46,7 +45,6 @@ RSpec.describe 'Teams API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.parsed_body['id']).to eq(team.id)
end
end
@@ -85,7 +83,6 @@ RSpec.describe 'Teams API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(Team.count).to eq(2)
end
end
@@ -124,7 +121,6 @@ RSpec.describe 'Teams API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(team.reload.name).to eq('new-team')
end
end
@@ -149,7 +149,6 @@ RSpec.describe 'Accounts API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(account.name)
expect(response.body).to include(account.locale)
expect(response.body).to include(account.domain)
@@ -185,22 +184,22 @@ RSpec.describe 'Accounts API', type: :request do
end
end
describe 'PATCH /api/v1/accounts/{account.id}' do
describe 'PUT /api/v1/accounts/{account.id}' do
let(:account) { create(:account) }
let(:agent) { create(:user, account: account, role: :agent) }
let(:admin) { create(:user, account: account, role: :administrator) }
context 'when it is an unauthenticated user' do
it 'returns unauthorized' do
patch "/api/v1/accounts/#{account.id}"
put "/api/v1/accounts/#{account.id}"
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an unauthorized user' do
it 'returns unauthorized' do
patch "/api/v1/accounts/#{account.id}",
headers: agent.create_new_auth_token
put "/api/v1/accounts/#{account.id}",
headers: agent.create_new_auth_token
expect(response).to have_http_status(:unauthorized)
end
@@ -220,20 +219,11 @@ RSpec.describe 'Accounts API', type: :request do
company_size: '1-10'
}
it 'returns a valid schema' do
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to conform_schema(200)
end
it 'modifies an account' do
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
put "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(account.reload.name).to eq(params[:name])
@@ -252,19 +242,19 @@ RSpec.describe 'Accounts API', type: :request do
it 'updates onboarding step to invite_team if onboarding step is present in account custom attributes' do
account.update(custom_attributes: { onboarding_step: 'account_update' })
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
put "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(account.reload.custom_attributes['onboarding_step']).to eq('invite_team')
end
it 'will not update onboarding step if onboarding step is not present in account custom attributes' do
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
put "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(account.reload.custom_attributes['onboarding_step']).to be_nil
end
@@ -272,10 +262,10 @@ RSpec.describe 'Accounts API', type: :request do
it 'Throws error 422' do
params[:name] = 'test' * 999
patch "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
put "/api/v1/accounts/#{account.id}",
params: params,
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unprocessable_entity)
json_response = response.parsed_body
@@ -21,7 +21,6 @@ RSpec.describe 'Profile API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
json_response = response.parsed_body
expect(json_response['id']).to eq(agent.id)
expect(json_response['email']).to eq(agent.email)
@@ -51,7 +50,6 @@ RSpec.describe 'Profile API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
json_response = response.parsed_body
agent.reload
expect(json_response['id']).to eq(agent.id)
@@ -66,7 +64,6 @@ RSpec.describe 'Profile API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
agent.reload
expect(agent.custom_attributes['phone_number']).to eq('+123456789')
@@ -94,7 +91,6 @@ RSpec.describe 'Profile API', type: :request do
as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(agent.reload.valid_password?('Test1234!')).to be true
end
@@ -144,7 +144,6 @@ RSpec.describe 'Platform Accounts API', type: :request do
headers: { api_access_token: platform_app.access_token.token }, as: :json
expect(response).to have_http_status(:success)
expect(response).to conform_schema(200)
expect(response.body).to include(account.name)
end
end
@@ -259,12 +259,10 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
message_content: 'Hello assistant',
message_history: [
{ role: 'user', content: 'Previous message' },
{ role: 'assistant', content: 'Previous response', agent_name: 'billing_scenario' }
{ role: 'assistant', content: 'Previous response' }
]
}
end
let(:chat_service) { instance_double(Captain::Llm::AssistantChatService) }
let(:agent_runner_service) { instance_double(Captain::Assistant::AgentRunnerService) }
context 'when it is an un-authenticated user' do
it 'returns unauthorized' do
@@ -276,14 +274,11 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
end
end
context 'when captain v2 is disabled' do
it 'generates a response with the legacy assistant chat service' do
allow(Captain::Llm::AssistantChatService).to receive(:new).with(
assistant: assistant,
source: 'playground'
).and_return(chat_service)
context 'when it is an agent' do
it 'generates a response' do
chat_service = instance_double(Captain::Llm::AssistantChatService)
allow(Captain::Llm::AssistantChatService).to receive(:new).with(assistant: assistant).and_return(chat_service)
allow(chat_service).to receive(:generate_response).and_return({ content: 'Assistant response' })
expect(Captain::Assistant::AgentRunnerService).not_to receive(:new)
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/playground",
params: valid_params,
@@ -297,15 +292,14 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
)
expect(json_response[:content]).to eq('Assistant response')
end
end
context 'when message_history is not provided' do
it 'uses empty array as default' do
params_without_history = { message_content: 'Hello assistant' }
allow(Captain::Llm::AssistantChatService).to receive(:new).with(
assistant: assistant,
source: 'playground'
).and_return(chat_service)
chat_service = instance_double(Captain::Llm::AssistantChatService)
allow(Captain::Llm::AssistantChatService).to receive(:new).with(assistant: assistant).and_return(chat_service)
allow(chat_service).to receive(:generate_response).and_return({ content: 'Assistant response' })
expect(Captain::Assistant::AgentRunnerService).not_to receive(:new)
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/playground",
params: params_without_history,
@@ -319,53 +313,5 @@ RSpec.describe 'Api::V1::Accounts::Captain::Assistants', type: :request do
)
end
end
context 'when captain v2 is enabled' do
before do
account.enable_features('captain_integration_v2')
end
it 'generates a response with the agent runner service' do
allow(Captain::Assistant::AgentRunnerService).to receive(:new).with(
assistant: assistant,
source: 'playground'
).and_return(agent_runner_service)
allow(agent_runner_service).to receive(:generate_response).and_return({ response: 'Assistant response' })
expect(Captain::Llm::AssistantChatService).not_to receive(:new)
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/playground",
params: valid_params,
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(agent_runner_service).to have_received(:generate_response).with(
message_history: valid_params[:message_history] + [{ role: 'user', content: valid_params[:message_content] }]
)
expect(json_response[:response]).to eq('Assistant response')
end
it 'does not duplicate the latest user message if it is already in history' do
params_with_latest_message = {
message_content: 'Hello assistant',
message_history: [{ role: 'user', content: 'Hello assistant' }]
}
allow(Captain::Assistant::AgentRunnerService).to receive(:new).with(
assistant: assistant,
source: 'playground'
).and_return(agent_runner_service)
allow(agent_runner_service).to receive(:generate_response).and_return({ response: 'Assistant response' })
post "/api/v1/accounts/#{account.id}/captain/assistants/#{assistant.id}/playground",
params: params_with_latest_message,
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(agent_runner_service).to have_received(:generate_response).with(
message_history: params_with_latest_message[:message_history]
)
end
end
end
end
@@ -42,45 +42,6 @@ RSpec.describe Captain::Scenario, type: :model do
end
end
describe '#handoff_key' do
let(:account) { create(:account) }
let(:assistant) { create(:captain_assistant, account: account) }
it 'uses id plus readable slug for persisted scenarios' do
scenario = create(:captain_scenario, assistant: assistant, account: account,
title: 'Handle complex refund requests requiring manager approval steps')
expect(scenario.handoff_key).to start_with("scenario_#{scenario.id}_")
expect(scenario.handoff_key).to end_with('_agent')
expect("handoff_to_#{scenario.handoff_key}".length).to be <= 60
end
it 'uses a truncated slug key for unsaved scenarios' do
scenario = build(:captain_scenario, assistant: assistant, account: account,
title: 'Troubleshoot payment gateway errors for recurring subscription charges')
expect(scenario.handoff_key).to match(/\Ascenario_draft_[a-z0-9_]+_agent\z/)
expect("handoff_to_#{scenario.handoff_key}".length).to be <= 60
end
it 'stays within length budget even for large ids' do
scenario = build(:captain_scenario, assistant: assistant, account: account,
title: 'A very long scenario title used only for budget verification')
allow(scenario).to receive(:id).and_return(1_234_567_890_123_456_789)
expect("handoff_to_#{scenario.handoff_key}".length).to be <= 60
end
it 'exposes handoff keys in assistant prompt context' do
scenario = create(:captain_scenario, assistant: assistant, account: account)
prompt_context = assistant.send(:prompt_context)
scenario_config = prompt_context[:scenarios].find { |entry| entry[:title] == scenario.title }
expect(scenario_config[:key]).to eq(scenario.handoff_key)
end
end
describe 'tool validation and population' do
let(:account) { create(:account) }
let(:assistant) { create(:captain_assistant, account: account) }
@@ -24,7 +24,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'schedules captain response job for incoming messages on pending conversations' do
expect(Captain::Conversation::ResponseBuilderJob).to receive(:perform_later).with(conversation, assistant)
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end
end
@@ -43,7 +43,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'schedules captain response job outside business hours (Captain always responds when configured)' do
expect(Captain::Conversation::ResponseBuilderJob).to receive(:perform_later).with(conversation, assistant)
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end
it 'performs captain handoff when quota is exceeded (OOO template will kick in after handoff)' do
@@ -52,7 +52,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
custom_attributes: account.custom_attributes.merge('captain_responses_usage' => 100)
)
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
expect(conversation.reload.status).to eq('open')
end
@@ -62,7 +62,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new)
end
@@ -76,7 +76,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'schedules captain response job regardless of time' do
expect(Captain::Conversation::ResponseBuilderJob).to receive(:perform_later).with(conversation, assistant)
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end
end
@@ -95,7 +95,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
end
it 'performs handoff within business hours when quota exceeded' do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
expect(conversation.reload.status).to eq('open')
end
@@ -110,7 +110,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'does not schedule captain response job' do
expect(Captain::Conversation::ResponseBuilderJob).not_to receive(:perform_later)
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end
end
@@ -122,7 +122,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'does not schedule captain response job' do
expect(Captain::Conversation::ResponseBuilderJob).not_to receive(:perform_later)
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end
end
@@ -130,7 +130,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'does not schedule captain response job' do
expect(Captain::Conversation::ResponseBuilderJob).not_to receive(:perform_later)
create(:message, conversation: conversation, message_type: :outgoing, account: account)
create(:message, conversation: conversation, message_type: :outgoing)
end
end
@@ -144,7 +144,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
inbox.update!(greeting_enabled: true, greeting_message: 'Hello! How can we help you?', enable_email_collect: false)
expect do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end.not_to(change { conversation.reload.messages.template.count })
end
@@ -160,7 +160,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
)
expect do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end.not_to(change { conversation.reload.messages.template.count })
end
end
@@ -174,7 +174,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
inbox.update!(greeting_enabled: true, greeting_message: 'Hello! How can we help you?', enable_email_collect: false)
expect do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end.to change { conversation.reload.messages.template.count }.by(1)
greeting_message = conversation.reload.messages.template.last
@@ -193,7 +193,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
)
expect do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end.to change { conversation.reload.messages.template.count }.by(1)
out_of_office_message = conversation.reload.messages.template.last
@@ -211,7 +211,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
inbox.update!(greeting_enabled: true, greeting_message: 'Hello! How can we help you?', enable_email_collect: false)
expect do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end.to change { conversation.reload.messages.template.count }.by(1)
greeting_message = conversation.reload.messages.template.last
@@ -230,7 +230,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
)
expect do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end.to change { conversation.reload.messages.template.count }.by(1)
out_of_office_message = conversation.reload.messages.template.last
@@ -245,7 +245,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'schedules captain response job for incoming messages on pending campaign conversations' do
expect(Captain::Conversation::ResponseBuilderJob).to receive(:perform_later).with(campaign_conversation, assistant)
create(:message, conversation: campaign_conversation, message_type: :incoming, account: account)
create(:message, conversation: campaign_conversation, message_type: :incoming)
end
it 'does not send greeting template on campaign conversations' do
@@ -255,7 +255,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
allow(greeting_service).to receive(:perform).and_return(true)
create(:message, conversation: campaign_conversation, message_type: :incoming, account: account)
create(:message, conversation: campaign_conversation, message_type: :incoming)
expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
end
@@ -271,7 +271,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
create(:message, conversation: campaign_conversation, message_type: :incoming, account: account)
create(:message, conversation: campaign_conversation, message_type: :incoming)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new)
end
@@ -284,7 +284,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(email_collect_service)
allow(email_collect_service).to receive(:perform).and_return(true)
create(:message, conversation: campaign_conversation, message_type: :incoming, account: account)
create(:message, conversation: campaign_conversation, message_type: :incoming)
expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new)
end
@@ -304,7 +304,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
)
expect do
create(:message, conversation: campaign_conversation, message_type: :incoming, account: account)
create(:message, conversation: campaign_conversation, message_type: :incoming)
end.not_to(change { campaign_conversation.messages.template.count })
end
end
@@ -332,7 +332,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'sends out of office message after handoff due to quota exceeded' do
expect do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end.to change { conversation.messages.template.count }.by(1)
expect(conversation.reload.status).to eq('open')
@@ -356,7 +356,7 @@ RSpec.describe MessageTemplates::HookExecutionService do
it 'does not send out of office message after handoff' do
expect do
create(:message, conversation: conversation, message_type: :incoming, account: account)
create(:message, conversation: conversation, message_type: :incoming)
end.not_to(change { conversation.messages.template.count })
expect(conversation.reload.status).to eq('open')
-4
View File
@@ -75,10 +75,6 @@ RSpec.configure do |config|
config.include ActiveSupport::Testing::TimeHelpers
config.include ActionCable::TestHelper
config.include ActiveJob::TestHelper
# OpenAPI response validation via Skooma
path_to_openapi = Rails.root.join('swagger/swagger.json')
config.include Skooma::RSpec[path_to_openapi], type: :request
end
Shoulda::Matchers.configure do |config|
-20
View File
@@ -50,26 +50,6 @@ describe ActionService do
action_service.assign_agent(['nil'])
expect(conversation.reload.assignee).to be_nil
end
context 'when agent is confirmed' do
it 'assigns the agent to the conversation' do
inbox_member
action_service.assign_agent([agent.id])
expect(conversation.reload.assignee).to eq(agent)
end
end
context 'when agent is unconfirmed' do
let(:unconfirmed_agent) { create(:user, account: account, role: :agent, skip_confirmation: false) }
let(:unconfirmed_inbox_member) { create(:inbox_member, inbox: conversation.inbox, user: unconfirmed_agent) }
it 'does not assign unconfirmed agent to the conversation' do
unconfirmed_inbox_member
original_assignee = conversation.assignee
action_service.assign_agent([unconfirmed_agent.id])
expect(conversation.reload.assignee).to eq(original_assignee)
end
end
end
describe '#assign_team' do
@@ -15,7 +15,7 @@ describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::Greeting).to receive(:new)
# described class gets called in message after commit
create(:message, conversation: conversation, account: conversation.account, message_type: 'activity', content: 'Conversation marked resolved!!')
create(:message, conversation: conversation, message_type: 'activity', content: 'Conversation marked resolved!!')
expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new)
@@ -36,7 +36,7 @@ describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::Greeting).to receive(:new)
# described class gets called in message after commit
message = create(:message, conversation: conversation, account: conversation.account)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
expect(MessageTemplates::Template::EmailCollect).to have_received(:new).with(conversation: message.conversation)
@@ -54,7 +54,7 @@ describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
allow(greeting_service).to receive(:perform).and_return(true)
message = create(:message, conversation: conversation, account: conversation.account)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::Greeting).not_to have_received(:new).with(conversation: message.conversation)
end
end
@@ -75,7 +75,7 @@ describe MessageTemplates::HookExecutionService do
allow(greeting_service).to receive(:perform).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation, account: conversation.account)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::Greeting).to have_received(:new).with(conversation: message.conversation)
expect(greeting_service).to have_received(:perform)
@@ -90,7 +90,7 @@ describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation, account: conversation.account)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new).with(conversation: message.conversation)
end
@@ -105,7 +105,7 @@ describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::EmailCollect).to receive(:new).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation, account: conversation.account)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::EmailCollect).not_to have_received(:new).with(conversation: message.conversation)
end
@@ -123,7 +123,7 @@ describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::Greeting).to receive(:new).and_return(greeting_service)
allow(greeting_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation, account: conversation.account)
create(:message, conversation: conversation)
expect(MessageTemplates::Template::Greeting).not_to have_received(:new)
end
@@ -139,7 +139,7 @@ describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation, account: conversation.account)
create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new)
end
@@ -151,7 +151,7 @@ describe MessageTemplates::HookExecutionService do
conversation = create(:conversation, contact: contact)
conversation.inbox.update(greeting_enabled: true, enable_email_collect: true, greeting_message: 'Hi, this is a greeting message')
message = create(:message, conversation: conversation, account: conversation.account, content_type: :incoming_email)
message = create(:message, conversation: conversation, content_type: :incoming_email)
message.content_attributes = { email: { auto_reply: true } }
message.save!
@@ -188,7 +188,7 @@ describe MessageTemplates::HookExecutionService do
allow(out_of_office_service).to receive(:perform).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation, account: conversation.account)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).to have_received(:new).with(conversation: message.conversation)
expect(out_of_office_service).to have_received(:perform)
@@ -202,13 +202,13 @@ describe MessageTemplates::HookExecutionService do
conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
conversation.inbox.working_hours.today.update!(closed_all_day: true)
create(:message, conversation: conversation, account: conversation.account, message_type: :outgoing, created_at: 2.minutes.ago)
create(:message, conversation: conversation, message_type: :outgoing, created_at: 2.minutes.ago)
out_of_office_service = double
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation, account: conversation.account)
create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new)
expect(out_of_office_service).not_to have_received(:perform)
@@ -221,13 +221,13 @@ describe MessageTemplates::HookExecutionService do
conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office')
conversation.inbox.working_hours.today.update!(closed_all_day: true)
create(:message, conversation: conversation, account: conversation.account, private: true, message_type: :outgoing, created_at: 2.minutes.ago)
create(:message, conversation: conversation, private: true, message_type: :outgoing, created_at: 2.minutes.ago)
out_of_office_service = double
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(true)
create(:message, conversation: conversation, account: conversation.account)
create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).to have_received(:new).with(conversation: conversation)
expect(out_of_office_service).to have_received(:perform)
@@ -247,7 +247,7 @@ describe MessageTemplates::HookExecutionService do
allow(out_of_office_service).to receive(:perform).and_return(true)
# described class gets called in message after commit
message = create(:message, conversation: conversation, account: conversation.account, message_type: 'outgoing')
message = create(:message, conversation: conversation, message_type: 'outgoing')
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new).with(conversation: message.conversation)
expect(out_of_office_service).not_to have_received(:perform)
@@ -265,7 +265,7 @@ describe MessageTemplates::HookExecutionService do
allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service)
allow(out_of_office_service).to receive(:perform).and_return(false)
message = create(:message, conversation: conversation, account: conversation.account)
message = create(:message, conversation: conversation)
expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new).with(conversation: message.conversation)
expect(out_of_office_service).not_to receive(:perform)
end
-7
View File
@@ -1,7 +0,0 @@
require 'rails_helper'
RSpec.describe 'OpenAPI document', type: :request do
it 'is valid against the OpenAPI 3.1.0 meta-schema' do
expect(skooma_openapi_schema).to be_valid_document
end
end
-6
View File
@@ -70,8 +70,6 @@ platform_account:
$ref: ./resource/platform_account.yml
team:
$ref: ./resource/team.yml
label:
$ref: ./resource/label.yml
integrations_app:
$ref: ./resource/integrations/app.yml
integrations_hook:
@@ -144,10 +142,6 @@ inbox_update_payload:
team_create_update_payload:
$ref: ./request/team/create_update_payload.yml
# Label
label_create_update_payload:
$ref: ./request/label/create_update_payload.yml
# Custom Filter
custom_filter_create_update_payload:
$ref: ./request/custom_filter/create_update_payload.yml
@@ -18,23 +18,20 @@ properties:
example: 'support@example.com'
# Settings parameters (stored in settings JSONB column)
auto_resolve_after:
type:
- integer
- 'null'
type: integer
minimum: 10
maximum: 1439856
nullable: true
description: Auto resolve conversations after specified minutes
example: 1440
auto_resolve_message:
type:
- string
- 'null'
type: string
nullable: true
description: Message to send when auto resolving
example: "This conversation has been automatically resolved due to inactivity"
auto_resolve_ignore_waiting:
type:
- boolean
- 'null'
type: boolean
nullable: true
description: Whether to ignore waiting conversations for auto resolve
example: false
# Custom attributes parameters (stored in custom_attributes JSONB column)
@@ -1,6 +1,7 @@
type: object
required:
- source_id
- inbox_id
properties:
source_id:
type: string
@@ -1,18 +0,0 @@
type: object
properties:
title:
type: string
description: The label title
example: support
description:
type: string
description: A short description for the label
example: Conversations that need support follow-up
color:
type: string
description: Hex color code for the label
example: '#1f93ff'
show_on_sidebar:
type: boolean
description: Whether the label should appear in the sidebar
example: true
@@ -26,7 +26,9 @@ properties:
type: object
description: Cache keys for the account
features:
type: object
type: array
items:
type: string
description: Enabled features for the account
settings:
type: object
@@ -46,24 +48,16 @@ properties:
description: Custom attributes of the account
properties:
plan_name:
type:
- string
- 'null'
type: string
description: Subscription plan name
subscribed_quantity:
type:
- number
- 'null'
type: number
description: Subscribed quantity
subscription_status:
type:
- string
- 'null'
type: string
description: Subscription status
subscription_ends_on:
type:
- string
- 'null'
type: string
format: date
description: Subscription end date
industry:
@@ -3,9 +3,7 @@ allOf:
- type: object
properties:
latest_chatwoot_version:
type:
- string
- 'null'
type: string
description: Latest version of Chatwoot available
example: "3.0.0"
subscribed_features:
+1 -3
View File
@@ -31,7 +31,5 @@ properties:
type: string
description: The thumbnail of the agent
custom_role_id:
type:
- integer
- 'null'
type: integer
description: The custom role id of the agent
+4 -6
View File
@@ -38,9 +38,8 @@ properties:
type: integer
description: Version number of the audit log entry
comment:
type:
- string
- 'null'
type: string
nullable: true
description: Optional comment associated with the audit log entry
request_uuid:
type: string
@@ -49,7 +48,6 @@ properties:
type: integer
description: Unix timestamp when the audit log entry was created
remote_address:
type:
- string
- 'null'
type: string
nullable: true
description: IP address from which the action was performed
@@ -31,10 +31,9 @@ properties:
type: string
description: Status of the message
source_id:
type:
- string
- 'null'
type: string
description: Source ID of the message
nullable: true
content_type:
type: string
description: Type of the content
@@ -42,15 +41,13 @@ properties:
type: object
description: Attributes of the content
sender_type:
type:
- string
- 'null'
type: string
description: Type of the sender
nullable: true
sender_id:
type:
- integer
- 'null'
type: integer
description: ID of the sender
nullable: true
external_source_ids:
type: object
description: External source IDs
@@ -58,10 +55,9 @@ properties:
type: object
description: Additional attributes of the message
processed_message_content:
type:
- string
- 'null'
type: string
description: Processed message content
nullable: true
sentiment:
type: object
description: Sentiment analysis of the message
@@ -70,10 +66,9 @@ properties:
description: Conversation details
properties:
assignee_id:
type:
- integer
- 'null'
type: integer
description: ID of the assignee
nullable: true
unread_count:
type: integer
description: Count of unread messages
@@ -11,9 +11,7 @@ properties:
type: string
description: Country of the contact
country_code:
type:
- string
- 'null'
type: string
description: Country code of the contact
created_at_ip:
type: string
@@ -28,18 +26,16 @@ properties:
type: integer
description: The ID of the contact
identifier:
type:
- string
- 'null'
type: string
description: The identifier of the contact
nullable: true
name:
type: string
description: The name of the contact
phone_number:
type:
- string
- 'null'
type: string
description: The phone number of the contact
nullable: true
thumbnail:
type: string
description: The thumbnail of the contact
@@ -22,7 +22,6 @@ properties:
type: string
description: Type of channel
provider:
type:
- string
- 'null'
description: Provider of the inbox
type: string
description: Provider of the inbox
nullable: true
@@ -11,9 +11,7 @@ properties:
type: string
description: Country of the contact
country_code:
type:
- string
- 'null'
type: string
description: Country code of the contact
created_at_ip:
type: string
@@ -23,10 +21,9 @@ properties:
description: Availability status of the contact
enum: ["online", "offline"]
email:
type:
- string
- 'null'
type: string
description: The email address of the contact
nullable: true
id:
type: integer
description: The ID of the contact
@@ -34,18 +31,16 @@ properties:
type: string
description: The name of the contact
phone_number:
type:
- string
- 'null'
type: string
description: The phone number of the contact
nullable: true
blocked:
type: boolean
description: Whether the contact is blocked
identifier:
type:
- string
- 'null'
type: string
description: The identifier of the contact
nullable: true
thumbnail:
type: string
description: The thumbnail of the contact
@@ -53,10 +48,9 @@ properties:
type: object
description: The custom attributes of the contact
last_activity_at:
type:
- integer
- 'null'
type: integer
description: Timestamp of last activity
nullable: true
created_at:
type: integer
description: Timestamp when contact was created
@@ -4,7 +4,5 @@ properties:
type: integer
description: Total number of contacts
current_page:
type:
- string
- integer
description: Current page number
type: string
description: Current page number
+8 -19
View File
@@ -43,9 +43,7 @@ properties:
type: boolean
description: Whether the conversation is muted
snoozed_until:
type:
- number
- 'null'
type: number
description: The time at which the conversation will be unmuted
status:
type: string
@@ -58,38 +56,29 @@ properties:
type: number
description: The time at which conversation was updated
timestamp:
type: number
type: string
description: The time at which conversation was created
first_reply_created_at:
type:
- number
- 'null'
type: number
description: The time at which the first reply was created
unread_count:
type: number
description: The number of unread messages
last_non_activity_message:
oneOf:
- $ref: '#/components/schemas/message'
- type: 'null'
type: object
$ref: '#/components/schemas/message'
description: The last non activity message
last_activity_at:
type: number
description: The last activity at of the conversation
priority:
type:
- string
- 'null'
type: string
description: The priority of the conversation
waiting_since:
type:
- number
- 'null'
type: number
description: The time at which the conversation was waiting
sla_policy_id:
type:
- number
- 'null'
type: number
description: The ID of the SLA policy
applied_sla:
type: object
@@ -51,12 +51,10 @@ properties:
description: The agent assigned to the conversation
nullable: true
agent_last_seen_at:
type:
- string
- 'null'
type: string
description: Timestamp when the agent last saw the conversation
nullable: true
assignee_last_seen_at:
type:
- string
- 'null'
description: Timestamp when the assignee last saw the conversation
type: string
description: Timestamp when the assignee last saw the conversation
nullable: true
@@ -13,9 +13,7 @@ properties:
type: string
description: The availability status of the sender
email:
type:
- string
- 'null'
type: string
description: The email of the sender
id:
type: number
@@ -24,22 +22,16 @@ properties:
type: string
description: The name of the sender
phone_number:
type:
- string
- 'null'
type: string
description: The phone number of the sender
blocked:
type: boolean
description: Whether the sender is blocked
identifier:
type:
- string
- 'null'
type: string
description: The identifier of the sender
thumbnail:
type:
- string
- 'null'
type: string
description: Avatar URL of the contact
custom_attributes:
type: object
+14 -44
View File
@@ -28,22 +28,16 @@ properties:
type: string
description: Script used to load the website widget
welcome_title:
type:
- string
- 'null'
type: string
description: Welcome title to be displayed on the widget
welcome_tagline:
type:
- string
- 'null'
type: string
description: Welcome tagline to be displayed on the widget
greeting_enabled:
type: boolean
description: The flag which shows whether greeting is enabled
greeting_message:
type:
- string
- 'null'
type: string
description: A greeting message when the user starts the conversation
channel_id:
type: number
@@ -61,9 +55,7 @@ properties:
type: object
description: Configuration settings for auto assignment
out_of_office_message:
type:
- string
- 'null'
type: string
description: Message to show when agents are out of office
working_hours:
type: array
@@ -78,24 +70,16 @@ properties:
type: boolean
description: Whether the inbox is closed for the entire day
open_hour:
type:
- number
- 'null'
type: number
description: Hour when inbox opens (0-23)
open_minutes:
type:
- number
- 'null'
type: number
description: Minutes of the hour when inbox opens (0-59)
close_hour:
type:
- number
- 'null'
type: number
description: Hour when inbox closes (0-23)
close_minutes:
type:
- number
- 'null'
type: number
description: Minutes of the hour when inbox closes (0-59)
open_all_day:
type: boolean
@@ -104,9 +88,7 @@ properties:
type: string
description: Timezone configuration for the inbox
callback_webhook_url:
type:
- string
- 'null'
type: string
description: Webhook URL for callbacks
allow_messages_after_resolved:
type: boolean
@@ -118,38 +100,26 @@ properties:
type: string
description: Type of sender name to display (e.g., friendly)
business_name:
type:
- string
- 'null'
type: string
description: Business name associated with the inbox
hmac_mandatory:
type: boolean
description: Whether HMAC verification is mandatory
selected_feature_flags:
type:
- array
- 'null'
type: object
description: Selected feature flags for the inbox
items:
type: string
reply_time:
type: string
description: Expected reply time
messaging_service_sid:
type:
- string
- 'null'
type: string
description: Messaging service SID for SMS providers
phone_number:
type:
- string
- 'null'
type: string
description: Phone number associated with the inbox
medium:
type: string
description: Medium of communication (e.g., sms, email)
provider:
type:
- string
- 'null'
type: string
description: Provider of the channel
-17
View File
@@ -1,17 +0,0 @@
type: object
properties:
id:
type: number
description: The ID of the label
title:
type: string
description: The title of the label
description:
type: string
description: The description of the label
color:
type: string
description: Hex color code for the label
show_on_sidebar:
type: boolean
description: Whether the label should appear in the sidebar
+14 -34
View File
@@ -17,49 +17,37 @@ properties:
description: The ID of the conversation
message_type:
type: integer
enum: [0, 1, 2, 3]
enum: [0, 1, 2]
description: The type of the message
created_at:
type: integer
description: The time at which message was created
updated_at:
type:
- integer
- string
type: integer
description: The time at which message was updated
private:
type: boolean
description: The flags which shows whether the message is private or not
status:
type:
- string
- 'null'
enum: ["sent", "delivered", "read", "failed", null]
type: string
enum: ["sent", "delivered", "read", "failed"]
description: The status of the message
source_id:
type:
- string
- 'null'
type: string
description: The source ID of the message
content_type:
type:
- string
- 'null'
enum: ["text", "input_text", "input_textarea", "input_email", "input_select", "cards", "form", "article", "incoming_email", "input_csat", "integrations", "sticker", "voice_call", null]
type: string
enum: ["text", "input_select", "cards", "form"]
description: The type of the template message
content_attributes:
type: object
description: The content attributes for each content_type
sender_type:
type:
- string
- 'null'
enum: ["Contact", "User", "AgentBot", "Captain::Assistant", null]
type: string
enum: ["contact", "agent", "agent_bot"]
description: The type of the sender
sender_id:
type:
- number
- 'null'
type: number
description: The ID of the sender
external_source_ids:
type: object
@@ -68,24 +56,16 @@ properties:
type: object
description: The additional attributes of the message
processed_message_content:
type:
- string
- 'null'
type: string
description: The processed message content
sentiment:
type:
- object
- 'null'
type: object
description: The sentiment of the message
conversation:
type:
- object
- 'null'
type: object
description: The conversation object
attachment:
type:
- object
- 'null'
type: object
description: The file object attached to the image
sender:
type: object
@@ -18,7 +18,7 @@ properties:
description: "The type of the message (0: incoming, 1: outgoing, 2: activity, 3: template)"
content_type:
type: string
enum: ["text", "input_text", "input_textarea", "input_email", "input_select", "cards", "form", "article", "incoming_email", "input_csat", "integrations", "sticker", "voice_call"]
enum: ["text", "input_select", "cards", "form", "input_csat"]
description: The type of the message content
status:
type: string
@@ -29,15 +29,13 @@ properties:
description: The content attributes for each content_type
properties:
in_reply_to:
type:
- string
- 'null'
type: string
description: ID of the message this is replying to
nullable: true
echo_id:
type:
- string
- 'null'
type: string
description: The echo ID of the message, used for deduplication
nullable: true
created_at:
type: integer
description: The timestamp when message was created
@@ -45,10 +43,9 @@ properties:
type: boolean
description: The flag which shows whether the message is private or not
source_id:
type:
- string
- 'null'
type: string
description: The source ID of the message
nullable: true
sender:
$ref: '#/components/schemas/contact_detail'
description: The sender of the message (only for incoming messages)
@@ -79,4 +76,4 @@ properties:
description: The thumbnail URL of the attached file
file_size:
type: number
description: The size of the attached file in bytes
description: The size of the attached file in bytes
+6 -9
View File
@@ -4,19 +4,16 @@ properties:
type: integer
description: Total number of articles
archived_articles_count:
type:
- integer
- 'null'
type: integer
nullable: true
description: Number of archived articles
published_count:
type:
- integer
- 'null'
type: integer
nullable: true
description: Number of published articles
draft_articles_count:
type:
- integer
- 'null'
type: integer
nullable: true
description: Number of draft articles
categories_count:
type: integer
@@ -26,19 +26,16 @@ properties:
type: number
description: ID of the account
conversation_id:
type:
- number
- 'null'
type: number
nullable: true
description: ID of the conversation
inbox_id:
type:
- number
- 'null'
type: number
nullable: true
description: ID of the inbox
user_id:
type:
- number
- 'null'
type: number
nullable: true
description: ID of the user/agent
created_at:
type: string
@@ -13,19 +13,16 @@ items:
type: number
description: Number of conversations resolved by the agent during the date range
avg_resolution_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) to resolve conversations. Null if no data available.
avg_first_response_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) for the first response. Null if no data available.
avg_reply_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) between replies. Null if no data available.
example:
- id: 1
@@ -13,19 +13,16 @@ items:
type: number
description: Number of conversations resolved in the inbox during the date range
avg_resolution_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) to resolve conversations. Null if no data available.
avg_first_response_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) for the first response. Null if no data available.
avg_reply_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) between replies. Null if no data available.
example:
- id: 1
@@ -13,19 +13,16 @@ items:
type: number
description: Number of conversations resolved by the team during the date range
avg_resolution_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) to resolve conversations. Null if no data available.
avg_first_response_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) for the first response. Null if no data available.
avg_reply_time:
type:
- number
- 'null'
type: number
nullable: true
description: Average time (in seconds) between replies. Null if no data available.
example:
- id: 1
+1 -3
View File
@@ -7,9 +7,7 @@ properties:
type: string
description: The name of the team
description:
type:
- string
- 'null'
type: string
description: The description about the team
allow_auto_assign:
type: boolean
+12 -21
View File
@@ -13,21 +13,17 @@ properties:
confirmed:
type: boolean
display_name:
type:
- string
- 'null'
type: string
nullable: true
message_signature:
type:
- string
- 'null'
type: string
nullable: true
email:
type: string
hmac_identifier:
type: string
inviter_id:
type:
- number
- 'null'
type: number
name:
type: string
provider:
@@ -42,9 +38,8 @@ properties:
uid:
type: string
type:
type:
- string
- 'null'
type: string
nullable: true
custom_attributes:
type: object
description: Available for users who are created through platform APIs and has custom attributes associated.
@@ -60,9 +55,7 @@ properties:
status:
type: string
active_at:
type:
- string
- 'null'
type: string
format: date-time
role:
type: string
@@ -78,10 +71,8 @@ properties:
auto_offline:
type: boolean
custom_role_id:
type:
- number
- 'null'
type: number
nullable: true
custom_role:
type:
- object
- 'null'
type: object
nullable: true
+1 -4
View File
@@ -1,4 +1,4 @@
openapi: '3.1.0'
openapi: '3.0.4'
info:
title: Chatwoot
description: This is the API documentation for Chatwoot server.
@@ -67,8 +67,6 @@ tags:
description: Communication channels setup
- name: Integrations
description: Third-party integrations
- name: Labels
description: Account label management APIs
- name: Messages
description: Message management APIs
- name: Profile
@@ -114,7 +112,6 @@ x-tagGroups:
- Custom Filters
- Inboxes
- Integrations
- Labels
- Messages
- Profile
- Reports
@@ -38,14 +38,10 @@ responses:
assignee:
$ref: '#/components/schemas/agent'
agent_last_seen_at:
type:
- string
- 'null'
type: string
format: date-time
assignee_last_seen_at:
type:
- string
- 'null'
type: string
format: date-time
payload:
type: array
@@ -25,10 +25,6 @@ requestBody:
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/conversation'
'401':
description: Unauthorized
content:
@@ -33,38 +33,3 @@ get:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
post:
tags:
- Inboxes
operationId: inboxCreation
summary: Create an inbox
description: You can create more than one website inbox in each account
security:
- userApiKey: []
parameters:
- $ref: '#/components/parameters/account_id'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/inbox_create_payload'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/inbox'
'404':
description: Inbox not found
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
'403':
description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
+2 -35
View File
@@ -1,38 +1,3 @@
get:
tags:
- Inboxes
operationId: GetInbox
summary: Get an inbox
security:
- userApiKey: []
description: Get an inbox available in the current account
parameters:
- $ref: '#/components/parameters/account_id'
- name: id
in: path
schema:
type: number
description: ID of the inbox
required: true
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/inbox'
'404':
description: Inbox not found
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
'403':
description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
patch:
tags:
- Inboxes
@@ -61,6 +26,8 @@ patch:
content:
application/json:
schema:
type: object
description: 'Updated inbox object'
$ref: '#/components/schemas/inbox'
'404':
description: Inbox not found
@@ -1,26 +0,0 @@
tags:
- Labels
operationId: create-a-label
summary: Create a label
security:
- userApiKey: []
description: Create a label in the account
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/label_create_update_payload'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/label'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
@@ -1,22 +0,0 @@
tags:
- Labels
operationId: delete-a-label
summary: Delete a label
security:
- userApiKey: []
description: Delete a label from the account
responses:
'200':
description: Success
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
'404':
description: The label does not exist in the account
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
@@ -1,26 +0,0 @@
tags:
- Labels
operationId: list-all-labels
summary: List all labels
security:
- userApiKey: []
description: List all labels available in the current account
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
payload:
type: array
description: Array of labels
items:
$ref: '#/components/schemas/label'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
-26
View File
@@ -1,26 +0,0 @@
tags:
- Labels
operationId: get-details-of-a-single-label
summary: Get a label
security:
- userApiKey: []
description: Get the details of a label in the account
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/label'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
'404':
description: The given label ID does not exist in the account
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
@@ -1,26 +0,0 @@
tags:
- Labels
operationId: update-a-label
summary: Update a label
security:
- userApiKey: []
description: Update a label's attributes
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/label_create_update_payload'
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/label'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
+5 -25
View File
@@ -167,7 +167,7 @@
# ------------ Application API routes ------------#
# Accounts
/api/v1/accounts/{account_id}:
/api/v1/accounts/{id}:
parameters:
- $ref: '#/components/parameters/account_id'
get:
@@ -413,6 +413,10 @@
# Inboxes
/api/v1/accounts/{account_id}/inboxes:
$ref: ./application/inboxes/index.yml
/api/v1/accounts/{account_id}/inboxes/{id}/:
$ref: ./application/inboxes/show.yml
/api/v1/accounts/{account_id}/inboxes/:
$ref: ./application/inboxes/create.yml
/api/v1/accounts/{account_id}/inboxes/{id}:
$ref: ./application/inboxes/update.yml
/api/v1/accounts/{account_id}/inboxes/{id}/agent_bot:
@@ -438,30 +442,6 @@
delete:
$ref: ./application/inboxes/inbox_members/delete.yml
# Labels
/api/v1/accounts/{account_id}/labels:
parameters:
- $ref: '#/components/parameters/account_id'
get:
$ref: ./application/labels/index.yml
post:
$ref: ./application/labels/create.yml
/api/v1/accounts/{account_id}/labels/{id}:
parameters:
- $ref: '#/components/parameters/account_id'
- name: id
in: path
required: true
schema:
type: number
description: ID of the label
get:
$ref: ./application/labels/show.yml
patch:
$ref: ./application/labels/update.yml
delete:
$ref: ./application/labels/delete.yml
# Messages
/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages:
parameters:
-77
View File
@@ -19,80 +19,3 @@ get:
application/json:
schema:
$ref: '#/components/schemas/bad_request_error'
put:
tags:
- Profile
operationId: updateProfile
summary: Update user profile
description: Update the user profile details
security:
- userApiKey: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- profile
properties:
profile:
type: object
properties:
name:
type: string
email:
type: string
display_name:
type: string
message_signature:
type: string
phone_number:
type: string
current_password:
type: string
password:
type: string
password_confirmation:
type: string
ui_settings:
type: object
multipart/form-data:
schema:
type: object
required:
- profile
properties:
profile:
type: object
properties:
name:
type: string
email:
type: string
display_name:
type: string
message_signature:
type: string
phone_number:
type: string
current_password:
type: string
password:
type: string
password_confirmation:
type: string
avatar:
type: string
format: binary
ui_settings:
type: object
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/user'
'401':
description: Unauthorized
+243 -952
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -1,4 +1,4 @@
openapi: '3.1.0'
openapi: '3.0.4'
info:
title: Chatwoot - Application API
description: Application API endpoints for Chatwoot
@@ -36,8 +36,6 @@ tags:
description: Manage inboxes
- name: Integrations
description: Manage integrations
- name: Labels
description: Manage account labels
- name: Messages
description: Manage messages
- name: Profile
@@ -64,4 +62,4 @@ components:
type: apiKey
in: header
name: api_access_token
description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels.
description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels.
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
openapi: '3.1.0'
openapi: '3.0.4'
info:
title: Chatwoot - Client API
description: Client API endpoints for Chatwoot
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
openapi: '3.1.0'
openapi: '3.0.4'
info:
title: Chatwoot - Other APIs
description: Other API endpoints for Chatwoot
+1 -1
View File
@@ -1,4 +1,4 @@
openapi: '3.1.0'
openapi: '3.0.4'
info:
title: Chatwoot - Platform API
description: Platform API endpoints for Chatwoot
File diff suppressed because it is too large Load Diff