Merge remote-tracking branch 'origin/feature/cw-7495-api' into codex/pr-15017-main

# Conflicts:
#	enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb
This commit is contained in:
aakashb95
2026-07-21 18:21:09 +05:30
1182 changed files with 52581 additions and 2192 deletions
@@ -0,0 +1,62 @@
import CaptainAgentSessionsAPI from 'dashboard/api/captain/agentSessions';
import camelcaseKeys from 'camelcase-keys';
const SET_SESSION = 'SET_SESSION';
const SET_FETCHING = 'SET_FETCHING';
// Session capture runs right after the message is broadcast (and well after,
// for handoff notes created mid-run), so a 404 on a fresh message may just
// mean the session isn't written yet. Skip caching those so a later
// hover/click retries; older misses are permanent (V1 messages, failed runs).
const RECENT_MESSAGE_WINDOW_SECONDS = 60;
// Caches Captain agent-session metadata per message id. A missing session
// (404) is cached as null so the UI shows an empty state without refetching.
export default {
namespaced: true,
state: {
sessions: {},
fetchingIds: [],
},
getters: {
getSessionByMessageId: state => messageId => state.sessions[messageId],
isFetching: state => messageId => state.fetchingIds.includes(messageId),
hasFetched: state => messageId => messageId in state.sessions,
},
actions: {
fetch: async ({ state, commit }, { messageId, createdAt }) => {
if (messageId in state.sessions) return;
if (state.fetchingIds.includes(messageId)) return;
commit(SET_FETCHING, { messageId, isFetching: true });
try {
const { data } = await CaptainAgentSessionsAPI.show(messageId);
commit(SET_SESSION, {
messageId,
session: camelcaseKeys(data, { deep: true }),
});
} catch (error) {
const isRecentMessage =
createdAt &&
Date.now() / 1000 - createdAt < RECENT_MESSAGE_WINDOW_SECONDS;
// Only a 404 means "no session exists"; transient failures (5xx,
// network errors) stay uncached so a later hover retries.
if (error.response?.status === 404 && !isRecentMessage) {
commit(SET_SESSION, { messageId, session: null });
}
} finally {
commit(SET_FETCHING, { messageId, isFetching: false });
}
},
},
mutations: {
[SET_SESSION](state, { messageId, session }) {
state.sessions = { ...state.sessions, [messageId]: session };
},
[SET_FETCHING](state, { messageId, isFetching }) {
state.fetchingIds = isFetching
? [...state.fetchingIds, messageId]
: state.fetchingIds.filter(id => id !== messageId);
},
},
};
+2
View File
@@ -50,6 +50,7 @@ import teamMembers from './modules/teamMembers';
import teams from './modules/teams';
import userNotificationSettings from './modules/userNotificationSettings';
import webhooks from './modules/webhooks';
import captainAgentSessions from './captain/agentSessions';
import captainAssistants from './captain/assistant';
import captainDocuments from './captain/document';
import captainResponses from './captain/response';
@@ -116,6 +117,7 @@ export default createStore({
teams,
userNotificationSettings,
webhooks,
captainAgentSessions,
captainAssistants,
captainDocuments,
captainResponses,
@@ -7,6 +7,7 @@ import FBChannel from '../../api/channel/fbChannel';
import TwilioChannel from '../../api/channel/twilioChannel';
import WhatsappChannel from '../../api/channel/whatsappChannel';
import { throwErrorMessage } from '../utils/api';
import { isSendableTemplate } from '@chatwoot/utils';
import AnalyticsHelper from '../../helper/AnalyticsHelper';
import camelcaseKeys from 'camelcase-keys';
import { ACCOUNT_EVENTS } from '../../helper/AnalyticsHelper/events';
@@ -67,45 +68,8 @@ export const getters = {
return [];
}
return templates.filter(template => {
// Ensure template has required properties
if (!template || !template.status || !template.components) {
return false;
}
// Only show approved templates
if (template.status.toLowerCase() !== 'approved') {
return false;
}
// Filter out authentication templates
if (template.category === 'AUTHENTICATION') {
return false;
}
// Filter out CSAT templates (customer_satisfaction_survey and its versions)
if (
template.name &&
template.name.startsWith('customer_satisfaction_survey')
) {
return false;
}
// Filter out interactive templates (LIST, PRODUCT, CATALOG), location templates, and call permission templates
const hasUnsupportedComponents = template.components.some(
component =>
['LIST', 'PRODUCT', 'CATALOG', 'CALL_PERMISSION_REQUEST'].includes(
component.type
) ||
(component.type === 'HEADER' && component.format === 'LOCATION')
);
if (hasUnsupportedComponents) {
return false;
}
return true;
});
// Sendable-template filtering is shared with the mobile app via @chatwoot/utils.
return templates.filter(isSendableTemplate);
},
getNewConversationInboxes($state) {
return $state.records.filter(inbox => {