@consilioweb/payload-support 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +636 -402
- package/dist/components/RichTextEditor/index.d.ts +19 -0
- package/dist/components/TicketConversation/RewriteDropdown.d.ts +6 -0
- package/dist/components/TicketConversation/SkeletonText.d.ts +4 -0
- package/dist/components/TicketConversation/components/AISummaryPanel.d.ts +14 -0
- package/dist/components/TicketConversation/components/ActionPanels.d.ts +47 -0
- package/dist/components/TicketConversation/components/ActivityLog.d.ts +5 -0
- package/dist/components/TicketConversation/components/ClientBar.d.ts +5 -0
- package/dist/components/TicketConversation/components/ClientHistory.d.ts +24 -0
- package/dist/components/TicketConversation/components/CodeBlock.d.ts +26 -0
- package/dist/components/TicketConversation/components/CodeBlockInserter.d.ts +12 -0
- package/dist/components/TicketConversation/components/QuickActions.d.ts +32 -0
- package/dist/components/TicketConversation/components/TicketHeader.d.ts +19 -0
- package/dist/components/TicketConversation/components/TimeTrackingPanel.d.ts +24 -0
- package/dist/components/TicketConversation/config.d.ts +10 -0
- package/dist/components/TicketConversation/config.js +1 -40
- package/dist/components/TicketConversation/constants.d.ts +56 -0
- package/dist/components/TicketConversation/context.d.ts +53 -0
- package/dist/components/TicketConversation/hooks/useAI.d.ts +21 -0
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.d.ts +3 -0
- package/dist/components/TicketConversation/hooks/useFeatures.d.ts +23 -0
- package/dist/components/TicketConversation/hooks/useFeatures.js +61 -0
- package/dist/components/TicketConversation/hooks/useMessageActions.d.ts +20 -0
- package/dist/components/TicketConversation/hooks/useReply.d.ts +27 -0
- package/dist/components/TicketConversation/hooks/useTicketActions.d.ts +58 -0
- package/dist/components/TicketConversation/hooks/useTimeTracking.d.ts +18 -0
- package/dist/components/TicketConversation/hooks/useTranslation.d.ts +33 -0
- package/dist/components/TicketConversation/index.d.ts +4 -0
- package/dist/components/TicketConversation/index.js +2 -2
- package/dist/components/TicketConversation/types.d.ts +56 -0
- package/dist/components/TicketConversation/utils.d.ts +2 -0
- package/dist/index.cjs +392 -320
- package/dist/index.d.cts +100 -3
- package/dist/index.d.ts +100 -3
- package/dist/index.js +388 -321
- package/dist/utils/features.d.ts +123 -0
- package/dist/utils/features.js +147 -0
- package/dist/views/BillingView/client.d.ts +2 -0
- package/dist/views/BillingView/index.d.ts +4 -0
- package/dist/views/ChatView/client.d.ts +2 -0
- package/dist/views/ChatView/client.js +9 -9
- package/dist/views/ChatView/index.d.ts +4 -0
- package/dist/views/CrmView/client.d.ts +2 -0
- package/dist/views/CrmView/client.js +6 -9
- package/dist/views/CrmView/index.d.ts +4 -0
- package/dist/views/EmailTrackingView/client.d.ts +2 -0
- package/dist/views/EmailTrackingView/index.d.ts +4 -0
- package/dist/views/ImportConversationView/client.d.ts +2 -0
- package/dist/views/ImportConversationView/index.d.ts +4 -0
- package/dist/views/LogsView/client.d.ts +2 -0
- package/dist/views/LogsView/index.d.ts +4 -0
- package/dist/views/NewTicketView/client.d.ts +2 -0
- package/dist/views/NewTicketView/index.d.ts +4 -0
- package/dist/views/PendingEmailsView/client.d.ts +2 -0
- package/dist/views/PendingEmailsView/index.d.ts +4 -0
- package/dist/views/SupportDashboardView/client.d.ts +2 -0
- package/dist/views/SupportDashboardView/index.d.ts +4 -0
- package/dist/views/TicketDetailView/NextActionItem.d.ts +6 -0
- package/dist/views/TicketDetailView/RewriteDropdown.d.ts +7 -0
- package/dist/views/TicketDetailView/client.d.ts +2 -0
- package/dist/views/TicketDetailView/client.js +2 -2
- package/dist/views/TicketDetailView/constants.d.ts +13 -0
- package/dist/views/TicketDetailView/helpers.d.ts +2 -0
- package/dist/views/TicketDetailView/index.d.ts +4 -0
- package/dist/views/TicketDetailView/types.d.ts +40 -0
- package/dist/views/TicketInboxView/client.d.ts +2 -0
- package/dist/views/TicketInboxView/client.js +5 -5
- package/dist/views/TicketInboxView/index.d.ts +4 -0
- package/dist/views/TicketingSettingsView/client.d.ts +2 -0
- package/dist/views/TicketingSettingsView/client.js +5 -4
- package/dist/views/TicketingSettingsView/index.d.ts +4 -0
- package/dist/views/TimeDashboardView/client.d.ts +2 -0
- package/dist/views/TimeDashboardView/index.d.ts +4 -0
- package/dist/views/shared/AdminViewHeader.d.ts +13 -0
- package/dist/views/shared/ErrorBoundary.d.ts +17 -0
- package/dist/views/shared/Skeleton.d.ts +21 -0
- package/dist/views/shared/StatusPill.d.ts +8 -0
- package/dist/views/shared/adminTokens.d.ts +19 -0
- package/dist/views/shared/config.d.ts +8 -0
- package/dist/views/shared/config.js +1 -40
- package/dist/views/shared/dateLocale.d.ts +5 -0
- package/dist/views/shared/index.d.ts +12 -0
- package/dist/views/shared/index.js +2 -1
- package/dist/views/shared/sla.d.ts +14 -0
- package/dist/views.d.ts +13 -13
- package/package.json +16 -31
- package/src/collections/SupportClients.ts +29 -0
- package/src/collections/TicketCollaborators.ts +6 -1
- package/src/collections/TicketMessages.ts +46 -24
- package/src/collections/Tickets.ts +117 -79
- package/src/collections/TimeEntries.ts +57 -32
- package/src/components/TicketConversation/config.ts +16 -80
- package/src/components/TicketConversation/hooks/useFeatures.ts +109 -0
- package/src/components/TicketConversation/index.tsx +2 -2
- package/src/endpoints/ai.ts +2 -1
- package/src/endpoints/client-intelligence.ts +2 -1
- package/src/endpoints/process-scheduled.ts +13 -8
- package/src/endpoints/round-robin-config.ts +30 -20
- package/src/endpoints/settings.ts +28 -77
- package/src/index.ts +6 -2
- package/src/portal/LiveChat.tsx +33 -22
- package/src/portal/auth/tickets/detail/TicketPolling.tsx +5 -1
- package/src/types/lucide-react.d.ts +10 -2
- package/src/utils/aiAgent.ts +2 -1
- package/src/utils/aiProvider.ts +21 -0
- package/src/utils/features.ts +297 -0
- package/src/utils/fireWebhooks.ts +6 -0
- package/src/utils/generateTicketSynthesis.ts +2 -1
- package/src/utils/index.ts +4 -2
- package/src/utils/readSettings.ts +82 -9
- package/src/utils/webhookDispatcher.ts +19 -7
- package/src/views/ChatView/client.tsx +13 -9
- package/src/views/CrmView/client.tsx +10 -11
- package/src/views/TicketDetailView/client.tsx +2 -2
- package/src/views/TicketInboxView/client.tsx +10 -5
- package/src/views/TicketingSettingsView/client.tsx +8 -6
- package/src/views/shared/config.ts +14 -80
- package/src/views/shared/index.ts +3 -1
- package/dist/components/RichTextEditor/index.cjs +0 -279
- package/dist/components/TicketConversation/RewriteDropdown.cjs +0 -98
- package/dist/components/TicketConversation/SkeletonText.cjs +0 -21
- package/dist/components/TicketConversation/components/AISummaryPanel.cjs +0 -85
- package/dist/components/TicketConversation/components/ActionPanels.cjs +0 -193
- package/dist/components/TicketConversation/components/ActivityLog.cjs +0 -24
- package/dist/components/TicketConversation/components/ClientBar.cjs +0 -43
- package/dist/components/TicketConversation/components/ClientHistory.cjs +0 -138
- package/dist/components/TicketConversation/components/CodeBlock.cjs +0 -171
- package/dist/components/TicketConversation/components/CodeBlockInserter.cjs +0 -156
- package/dist/components/TicketConversation/components/QuickActions.cjs +0 -87
- package/dist/components/TicketConversation/components/TicketHeader.cjs +0 -93
- package/dist/components/TicketConversation/components/TimeTrackingPanel.cjs +0 -134
- package/dist/components/TicketConversation/config.cjs +0 -44
- package/dist/components/TicketConversation/constants.cjs +0 -127
- package/dist/components/TicketConversation/context.cjs +0 -13
- package/dist/components/TicketConversation/hooks/useAI.cjs +0 -196
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +0 -20
- package/dist/components/TicketConversation/hooks/useMessageActions.cjs +0 -136
- package/dist/components/TicketConversation/hooks/useReply.cjs +0 -188
- package/dist/components/TicketConversation/hooks/useTicketActions.cjs +0 -290
- package/dist/components/TicketConversation/hooks/useTimeTracking.cjs +0 -123
- package/dist/components/TicketConversation/hooks/useTranslation.cjs +0 -101
- package/dist/components/TicketConversation/index.cjs +0 -1114
- package/dist/components/TicketConversation/types.cjs +0 -2
- package/dist/components/TicketConversation/utils.cjs +0 -27
- package/dist/views/BillingView/client.cjs +0 -469
- package/dist/views/BillingView/index.cjs +0 -34
- package/dist/views/ChatView/client.cjs +0 -301
- package/dist/views/ChatView/index.cjs +0 -34
- package/dist/views/CrmView/client.cjs +0 -458
- package/dist/views/CrmView/index.cjs +0 -34
- package/dist/views/EmailTrackingView/client.cjs +0 -171
- package/dist/views/EmailTrackingView/index.cjs +0 -34
- package/dist/views/ImportConversationView/client.cjs +0 -238
- package/dist/views/ImportConversationView/index.cjs +0 -34
- package/dist/views/LogsView/client.cjs +0 -148
- package/dist/views/LogsView/index.cjs +0 -32
- package/dist/views/NewTicketView/client.cjs +0 -213
- package/dist/views/NewTicketView/index.cjs +0 -32
- package/dist/views/PendingEmailsView/client.cjs +0 -471
- package/dist/views/PendingEmailsView/index.cjs +0 -34
- package/dist/views/SupportDashboardView/client.cjs +0 -437
- package/dist/views/SupportDashboardView/index.cjs +0 -34
- package/dist/views/TicketDetailView/NextActionItem.cjs +0 -40
- package/dist/views/TicketDetailView/RewriteDropdown.cjs +0 -80
- package/dist/views/TicketDetailView/client.cjs +0 -1412
- package/dist/views/TicketDetailView/constants.cjs +0 -20
- package/dist/views/TicketDetailView/helpers.cjs +0 -23
- package/dist/views/TicketDetailView/index.cjs +0 -34
- package/dist/views/TicketDetailView/types.cjs +0 -2
- package/dist/views/TicketInboxView/client.cjs +0 -359
- package/dist/views/TicketInboxView/index.cjs +0 -32
- package/dist/views/TicketingSettingsView/client.cjs +0 -740
- package/dist/views/TicketingSettingsView/index.cjs +0 -34
- package/dist/views/TimeDashboardView/client.cjs +0 -180
- package/dist/views/TimeDashboardView/index.cjs +0 -34
- package/dist/views/shared/AdminViewHeader.cjs +0 -68
- package/dist/views/shared/ErrorBoundary.cjs +0 -55
- package/dist/views/shared/Skeleton.cjs +0 -77
- package/dist/views/shared/StatusPill.cjs +0 -47
- package/dist/views/shared/adminTokens.cjs +0 -34
- package/dist/views/shared/config.cjs +0 -44
- package/dist/views/shared/dateLocale.cjs +0 -5
- package/dist/views/shared/index.cjs +0 -72
- package/dist/views/shared/sla.cjs +0 -37
- package/dist/views.cjs +0 -86
- package/dist/views.d.cts +0 -13
- package/src/__tests__/aiSummaryRendering.test.ts +0 -21
- package/src/__tests__/authResponses.test.ts +0 -69
- package/src/__tests__/capabilities.test.ts +0 -71
- package/src/__tests__/generateTrackingToken.test.ts +0 -67
- package/src/__tests__/integration/ai-agent.test.ts +0 -46
- package/src/__tests__/integration/auth.test.ts +0 -43
- package/src/__tests__/integration/automation-rules.test.ts +0 -68
- package/src/__tests__/integration/buildTestPayload.ts +0 -52
- package/src/__tests__/integration/channels.test.ts +0 -52
- package/src/__tests__/integration/core-behaviors.test.ts +0 -81
- package/src/__tests__/integration/dashboard-volume.test.ts +0 -31
- package/src/__tests__/integration/digest.test.ts +0 -48
- package/src/__tests__/integration/invoice-pdf.test.ts +0 -30
- package/src/__tests__/integration/isolation.test.ts +0 -81
- package/src/__tests__/integration/mentions-invoice.test.ts +0 -50
- package/src/__tests__/integration/nps.test.ts +0 -37
- package/src/__tests__/integration/per-team-sla.test.ts +0 -44
- package/src/__tests__/integration/push.test.ts +0 -75
- package/src/__tests__/integration/sanitization.test.ts +0 -35
- package/src/__tests__/integration/sla-pause.test.ts +0 -39
- package/src/__tests__/integration/smoke.test.ts +0 -26
- package/src/__tests__/integration/snooze.test.ts +0 -68
- package/src/__tests__/integration/teams.test.ts +0 -38
- package/src/__tests__/rateLimiter.test.ts +0 -83
- package/src/__tests__/sanitizeHtml.test.ts +0 -165
- package/src/__tests__/sla.test.ts +0 -258
- package/src/__tests__/trackOpenEndpoint.test.ts +0 -54
- package/src/__tests__/translations.test.ts +0 -67
- package/src/__tests__/webhookSecurity.test.ts +0 -58
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
SUPPORT_SETTINGS_PREF_KEY as PREF_KEY,
|
|
6
|
+
invalidateSupportSettingsCache,
|
|
7
|
+
mergeSupportSettings,
|
|
8
|
+
readSupportSettingsState,
|
|
9
|
+
} from '../utils/readSettings'
|
|
10
|
+
import { stripProjectedFeatures } from '../utils/features'
|
|
5
11
|
|
|
6
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Round-robin used to live in its own `support-round-robin` preference row,
|
|
14
|
+
* while the admin UI toggled a `roundRobin` flag in `localStorage` — the two
|
|
15
|
+
* never met, so the toggle had no effect on assignment.
|
|
16
|
+
*
|
|
17
|
+
* Both endpoints below now read and write `settings.features.roundRobin`, the
|
|
18
|
+
* same value `/api/support/settings` exposes and `Tickets`' auto-assign hook
|
|
19
|
+
* reads. The legacy row is still honoured on read until the first save (see
|
|
20
|
+
* `readSupportSettingsState`), so an install that had it enabled keeps it.
|
|
21
|
+
*/
|
|
7
22
|
|
|
8
23
|
/**
|
|
9
24
|
* GET /api/support/round-robin-config — Get round-robin enabled status
|
|
@@ -18,18 +33,9 @@ export function createRoundRobinConfigGetEndpoint(slugs: CollectionSlugs): Endpo
|
|
|
18
33
|
|
|
19
34
|
requireAdmin(req, slugs)
|
|
20
35
|
|
|
21
|
-
const
|
|
22
|
-
where: { key: { equals: PREF_KEY } },
|
|
23
|
-
limit: 1,
|
|
24
|
-
depth: 0,
|
|
25
|
-
overrideAccess: true,
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
const enabled = prefs.docs.length > 0
|
|
29
|
-
? (prefs.docs[0].value as { enabled?: boolean })?.enabled === true
|
|
30
|
-
: false
|
|
36
|
+
const { settings } = await readSupportSettingsState(payload)
|
|
31
37
|
|
|
32
|
-
return Response.json({ enabled })
|
|
38
|
+
return Response.json({ enabled: settings.features.roundRobin })
|
|
33
39
|
} catch (error) {
|
|
34
40
|
const authResponse = handleAuthError(error)
|
|
35
41
|
if (authResponse) return authResponse
|
|
@@ -55,19 +61,21 @@ export function createRoundRobinConfigPostEndpoint(slugs: CollectionSlugs): Endp
|
|
|
55
61
|
|
|
56
62
|
const { enabled } = (await req.json!()) as { enabled: boolean }
|
|
57
63
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
64
|
+
const { settings: current } = await readSupportSettingsState(payload)
|
|
65
|
+
const merged = mergeSupportSettings(
|
|
66
|
+
{ features: { ...current.features, roundRobin: !!enabled } },
|
|
67
|
+
current,
|
|
68
|
+
)
|
|
64
69
|
|
|
65
70
|
await payload.db.upsert({
|
|
66
71
|
collection: 'payload-preferences',
|
|
67
72
|
data: {
|
|
68
73
|
key: PREF_KEY,
|
|
69
74
|
user: { relationTo: req.user!.collection, value: req.user!.id },
|
|
70
|
-
value: {
|
|
75
|
+
value: {
|
|
76
|
+
...merged,
|
|
77
|
+
features: stripProjectedFeatures(merged.features),
|
|
78
|
+
} as unknown as Record<string, unknown>,
|
|
71
79
|
},
|
|
72
80
|
req: { payload, user: req.user } as any,
|
|
73
81
|
where: {
|
|
@@ -79,6 +87,8 @@ export function createRoundRobinConfigPostEndpoint(slugs: CollectionSlugs): Endp
|
|
|
79
87
|
},
|
|
80
88
|
})
|
|
81
89
|
|
|
90
|
+
invalidateSupportSettingsCache()
|
|
91
|
+
|
|
82
92
|
return Response.json({ enabled: !!enabled })
|
|
83
93
|
} catch (error) {
|
|
84
94
|
const authResponse = handleAuthError(error)
|
|
@@ -1,59 +1,21 @@
|
|
|
1
1
|
import type { Endpoint } from 'payload'
|
|
2
2
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
3
|
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
fromName: string
|
|
13
|
-
replyToAddress: string
|
|
14
|
-
}
|
|
15
|
-
ai: {
|
|
16
|
-
provider: 'anthropic' | 'openai' | 'gemini' | 'ollama'
|
|
17
|
-
model: string
|
|
18
|
-
enableSentiment: boolean
|
|
19
|
-
enableSynthesis: boolean
|
|
20
|
-
enableSuggestion: boolean
|
|
21
|
-
enableRewrite: boolean
|
|
22
|
-
}
|
|
23
|
-
sla: {
|
|
24
|
-
firstResponseMinutes: number
|
|
25
|
-
resolutionMinutes: number
|
|
26
|
-
businessHoursOnly: boolean
|
|
27
|
-
escalationEmail: string
|
|
28
|
-
}
|
|
29
|
-
autoClose: {
|
|
30
|
-
enabled: boolean
|
|
31
|
-
daysBeforeClose: number
|
|
32
|
-
reminderDaysBefore: number
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const DEFAULT_SUPPORT_SETTINGS: SupportSettings = {
|
|
37
|
-
email: { fromAddress: '', fromName: 'Support', replyToAddress: '' },
|
|
38
|
-
ai: {
|
|
39
|
-
provider: 'anthropic',
|
|
40
|
-
model: 'claude-haiku-4-5-20251001',
|
|
41
|
-
enableSentiment: true,
|
|
42
|
-
enableSynthesis: true,
|
|
43
|
-
enableSuggestion: true,
|
|
44
|
-
enableRewrite: true,
|
|
45
|
-
},
|
|
46
|
-
sla: {
|
|
47
|
-
firstResponseMinutes: 120,
|
|
48
|
-
resolutionMinutes: 1440,
|
|
49
|
-
businessHoursOnly: true,
|
|
50
|
-
escalationEmail: '',
|
|
51
|
-
},
|
|
52
|
-
autoClose: { enabled: true, daysBeforeClose: 7, reminderDaysBefore: 2 },
|
|
53
|
-
}
|
|
4
|
+
import {
|
|
5
|
+
SUPPORT_SETTINGS_PREF_KEY as PREF_KEY,
|
|
6
|
+
invalidateSupportSettingsCache,
|
|
7
|
+
mergeSupportSettings,
|
|
8
|
+
readSupportSettingsState,
|
|
9
|
+
type SupportSettings,
|
|
10
|
+
} from '../utils/readSettings'
|
|
11
|
+
import { stripProjectedFeatures } from '../utils/features'
|
|
54
12
|
|
|
55
13
|
/**
|
|
56
14
|
* GET /api/support/settings — Read support settings
|
|
15
|
+
*
|
|
16
|
+
* `featuresConfigured` tells the admin UI whether the server has ever had
|
|
17
|
+
* feature flags saved; a client still holding pre-2.1 `localStorage` flags uses
|
|
18
|
+
* it to seed the server once instead of losing its configuration.
|
|
57
19
|
*/
|
|
58
20
|
export function createSettingsGetEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
59
21
|
return {
|
|
@@ -65,25 +27,9 @@ export function createSettingsGetEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
65
27
|
|
|
66
28
|
requireAdmin(req, slugs)
|
|
67
29
|
|
|
68
|
-
const
|
|
69
|
-
where: { key: { equals: PREF_KEY } },
|
|
70
|
-
limit: 1,
|
|
71
|
-
depth: 0,
|
|
72
|
-
overrideAccess: true,
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
let settings = { ...DEFAULT_SUPPORT_SETTINGS }
|
|
76
|
-
if (prefs.docs.length > 0) {
|
|
77
|
-
const stored = prefs.docs[0].value as Partial<SupportSettings>
|
|
78
|
-
settings = {
|
|
79
|
-
email: { ...DEFAULT_SUPPORT_SETTINGS.email, ...stored.email },
|
|
80
|
-
ai: { ...DEFAULT_SUPPORT_SETTINGS.ai, ...stored.ai },
|
|
81
|
-
sla: { ...DEFAULT_SUPPORT_SETTINGS.sla, ...stored.sla },
|
|
82
|
-
autoClose: { ...DEFAULT_SUPPORT_SETTINGS.autoClose, ...stored.autoClose },
|
|
83
|
-
}
|
|
84
|
-
}
|
|
30
|
+
const { settings, featuresConfigured } = await readSupportSettingsState(payload)
|
|
85
31
|
|
|
86
|
-
return Response.json(settings)
|
|
32
|
+
return Response.json({ ...settings, featuresConfigured })
|
|
87
33
|
} catch (error) {
|
|
88
34
|
const authResponse = handleAuthError(error)
|
|
89
35
|
if (authResponse) return authResponse
|
|
@@ -95,7 +41,11 @@ export function createSettingsGetEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
95
41
|
}
|
|
96
42
|
|
|
97
43
|
/**
|
|
98
|
-
* POST /api/support/settings — Save
|
|
44
|
+
* POST /api/support/settings — Save support settings (admin-only)
|
|
45
|
+
*
|
|
46
|
+
* The body is merged onto the CURRENT settings, not onto the defaults: the
|
|
47
|
+
* feature-flag screen posts `{ features }` alone, and that must not reset the
|
|
48
|
+
* email / AI / SLA blocks.
|
|
99
49
|
*/
|
|
100
50
|
export function createSettingsPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
101
51
|
return {
|
|
@@ -109,19 +59,20 @@ export function createSettingsPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
109
59
|
|
|
110
60
|
const body = (await req.json!()) as Partial<SupportSettings>
|
|
111
61
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
ai: { ...DEFAULT_SUPPORT_SETTINGS.ai, ...body.ai },
|
|
115
|
-
sla: { ...DEFAULT_SUPPORT_SETTINGS.sla, ...body.sla },
|
|
116
|
-
autoClose: { ...DEFAULT_SUPPORT_SETTINGS.autoClose, ...body.autoClose },
|
|
117
|
-
}
|
|
62
|
+
const { settings: current } = await readSupportSettingsState(payload)
|
|
63
|
+
const merged = mergeSupportSettings(body, current)
|
|
118
64
|
|
|
119
65
|
await payload.db.upsert({
|
|
120
66
|
collection: 'payload-preferences',
|
|
121
67
|
data: {
|
|
122
68
|
key: PREF_KEY,
|
|
123
69
|
user: { relationTo: req.user!.collection, value: req.user!.id },
|
|
124
|
-
value:
|
|
70
|
+
value: {
|
|
71
|
+
...merged,
|
|
72
|
+
// Persist the flags without the two projected ones, so `features`
|
|
73
|
+
// and the `autoClose` block can never drift apart.
|
|
74
|
+
features: stripProjectedFeatures(merged.features),
|
|
75
|
+
} as unknown as Record<string, unknown>,
|
|
125
76
|
},
|
|
126
77
|
req: { payload, user: req.user } as any,
|
|
127
78
|
where: {
|
|
@@ -135,7 +86,7 @@ export function createSettingsPostEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
|
135
86
|
|
|
136
87
|
invalidateSupportSettingsCache()
|
|
137
88
|
|
|
138
|
-
return Response.json(merged)
|
|
89
|
+
return Response.json({ ...merged, featuresConfigured: true })
|
|
139
90
|
} catch (error) {
|
|
140
91
|
const authResponse = handleAuthError(error)
|
|
141
92
|
if (authResponse) return authResponse
|
package/src/index.ts
CHANGED
|
@@ -21,8 +21,12 @@ export { DEFAULT_FEATURES } from './types'
|
|
|
21
21
|
// Utils
|
|
22
22
|
export { resolveSlugs, DEFAULT_SLUGS } from './utils/slugs'
|
|
23
23
|
export type { CollectionSlugs } from './utils/slugs'
|
|
24
|
-
export { readSupportSettings, readUserPrefs, DEFAULT_SETTINGS, DEFAULT_USER_PREFS } from './utils/readSettings'
|
|
25
|
-
export type { SupportSettings, UserPrefs } from './utils/readSettings'
|
|
24
|
+
export { readSupportSettings, readSupportSettingsState, readUserPrefs, DEFAULT_SETTINGS, DEFAULT_USER_PREFS } from './utils/readSettings'
|
|
25
|
+
export type { SupportSettings, SupportSettingsState, UserPrefs } from './utils/readSettings'
|
|
26
|
+
// Runtime feature flags (server-authoritative, see utils/features.ts). Distinct
|
|
27
|
+
// from the build-time `SupportFeatures` passed to `supportPlugin()`.
|
|
28
|
+
export { DEFAULT_TICKETING_FEATURES, normalizeFeatures, projectAutoClose, stripProjectedFeatures } from './utils/features'
|
|
29
|
+
export type { TicketingFeatures } from './utils/features'
|
|
26
30
|
export { MemoryRateLimitStore, PayloadRateLimitStore, RateLimiter } from './utils/rateLimiter'
|
|
27
31
|
export type { RateLimitEntry, RateLimitStore } from './utils/rateLimiter'
|
|
28
32
|
export { DEFAULT_INBOUND_EMAIL_LIMITS, validateInboundEmailPayload, verifySecret } from './utils/webhookSecurity'
|
package/src/portal/LiveChat.tsx
CHANGED
|
@@ -29,6 +29,13 @@ export function LiveChat() {
|
|
|
29
29
|
const lastFetchRef = useRef<string | null>(null)
|
|
30
30
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
31
31
|
const pollIntervalRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
32
|
+
// Mirror of `messages`. The poll decision (and the unread badge) MUST be taken
|
|
33
|
+
// outside the setMessages updater: React skips the updater on the eager-state
|
|
34
|
+
// path, so a flag assigned inside it is only ever written on the very first
|
|
35
|
+
// update — after which `hasNewMessages` stays false, the adaptive backoff climbs
|
|
36
|
+
// to its 30 s ceiling and never comes back down, mid-conversation. Same pattern
|
|
37
|
+
// as ChatWidget.tsx.
|
|
38
|
+
const messagesRef = useRef<ChatMessage[]>([])
|
|
32
39
|
|
|
33
40
|
// Load session from localStorage on mount
|
|
34
41
|
useEffect(() => {
|
|
@@ -42,6 +49,7 @@ export function LiveChat() {
|
|
|
42
49
|
|
|
43
50
|
// Auto-scroll to bottom when messages change
|
|
44
51
|
useEffect(() => {
|
|
52
|
+
messagesRef.current = messages
|
|
45
53
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
46
54
|
}, [messages])
|
|
47
55
|
|
|
@@ -61,31 +69,32 @@ export function LiveChat() {
|
|
|
61
69
|
|
|
62
70
|
const data = await res.json()
|
|
63
71
|
if (data.messages?.length > 0) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
if (agentNewMsgs.length > 0) {
|
|
80
|
-
setUnreadCount((prev) => prev + agentNewMsgs.length)
|
|
81
|
-
}
|
|
72
|
+
const existingIds = new Set(messagesRef.current.map((m) => String(m.id)))
|
|
73
|
+
const newMsgs: ChatMessage[] = data.messages.filter(
|
|
74
|
+
(m: ChatMessage) => !existingIds.has(String(m.id)),
|
|
75
|
+
)
|
|
76
|
+
lastFetchRef.current = data.messages[data.messages.length - 1].createdAt
|
|
77
|
+
if (newMsgs.length === 0) return false
|
|
78
|
+
|
|
79
|
+
// Count agent messages as unread if chat panel is closed. Kept OUT of the
|
|
80
|
+
// updater: nesting a setState there also double-counted the badge under
|
|
81
|
+
// StrictMode's double invocation in dev.
|
|
82
|
+
if (screen === 'closed') {
|
|
83
|
+
const agentNewMsgs = newMsgs.filter((m) => m.senderType === 'agent')
|
|
84
|
+
if (agentNewMsgs.length > 0) {
|
|
85
|
+
setUnreadCount((prev) => prev + agentNewMsgs.length)
|
|
82
86
|
}
|
|
87
|
+
}
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
setMessages((prev) => {
|
|
90
|
+
// Re-dedupe against the authoritative state: the mirror can lag by one
|
|
91
|
+
// commit if a message was appended optimistically in the meantime.
|
|
92
|
+
const prevIds = new Set(prev.map((m) => String(m.id)))
|
|
93
|
+
const trulyNew = newMsgs.filter((m) => !prevIds.has(String(m.id)))
|
|
94
|
+
if (trulyNew.length === 0) return prev
|
|
95
|
+
return [...prev, ...trulyNew]
|
|
85
96
|
})
|
|
86
|
-
|
|
87
|
-
data.messages[data.messages.length - 1].createdAt
|
|
88
|
-
return hadNewMessages
|
|
97
|
+
return true
|
|
89
98
|
}
|
|
90
99
|
return false
|
|
91
100
|
} catch {
|
|
@@ -127,6 +136,8 @@ export function LiveChat() {
|
|
|
127
136
|
// If we have a session, fetch messages
|
|
128
137
|
if (session.sessionId) {
|
|
129
138
|
lastFetchRef.current = null
|
|
139
|
+
// Synchronous too: pollMessages() runs before the mirroring effect commits.
|
|
140
|
+
messagesRef.current = []
|
|
130
141
|
setMessages([])
|
|
131
142
|
pollMessages()
|
|
132
143
|
}
|
|
@@ -30,8 +30,12 @@ export function TicketPolling({
|
|
|
30
30
|
|
|
31
31
|
const interval = setInterval(async () => {
|
|
32
32
|
try {
|
|
33
|
+
// `/count` returns a single number. The previous limit=0 idiom did NOT return
|
|
34
|
+
// zero documents — Payload disables pagination for limit=0 and derives
|
|
35
|
+
// totalDocs from the rows it materialised, so every open portal tab used to
|
|
36
|
+
// re-download its whole conversation every 30 s to read one counter.
|
|
33
37
|
const res = await fetch(
|
|
34
|
-
`/api/ticket-messages?where[ticket][equals]=${ticketId}
|
|
38
|
+
`/api/ticket-messages/count?where[ticket][equals]=${ticketId}`,
|
|
35
39
|
{ credentials: 'include' },
|
|
36
40
|
)
|
|
37
41
|
if (res.status === 401 || res.status === 403) {
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Ambient stub for 'lucide-react'.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* lucide-react is a REQUIRED peer dependency — three unconditionally registered
|
|
5
|
+
* components import it statically, so a host without it fails at build time.
|
|
6
|
+
* It is deliberately not a devDependency here; this stub is what lets the plugin
|
|
7
|
+
* type-check in isolation without pulling the icon set into the repo.
|
|
8
|
+
*
|
|
9
|
+
* Keep the icon list below in sync with the actual imports:
|
|
10
|
+
* src/portal/LiveChat.tsx
|
|
11
|
+
* src/views/PendingEmailsView/client.tsx
|
|
12
|
+
* src/views/TicketingSettingsView/client.tsx
|
|
5
13
|
*/
|
|
6
14
|
declare module 'lucide-react' {
|
|
7
15
|
import type { FC, SVGProps } from 'react'
|
package/src/utils/aiAgent.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Payload } from 'payload'
|
|
|
2
2
|
import type { CollectionSlugs } from './slugs'
|
|
3
3
|
import { readSupportSettings, type SupportSettings } from './readSettings'
|
|
4
4
|
import { dbFind, dbFindByID, dbCreate, dbUpdate } from './db'
|
|
5
|
+
import { resolveOllamaBaseUrl } from './aiProvider'
|
|
5
6
|
|
|
6
7
|
export interface AgentDecision {
|
|
7
8
|
action: 'reply' | 'escalate'
|
|
@@ -61,7 +62,7 @@ export async function applyAgentDecision(
|
|
|
61
62
|
function getClient(aiSettings: SupportSettings['ai']) {
|
|
62
63
|
const Anthropic = require('@anthropic-ai/sdk').default
|
|
63
64
|
if (aiSettings.provider === 'ollama') {
|
|
64
|
-
return new Anthropic({ apiKey: 'ollama', baseURL:
|
|
65
|
+
return new Anthropic({ apiKey: 'ollama', baseURL: resolveOllamaBaseUrl() })
|
|
65
66
|
}
|
|
66
67
|
return new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
|
|
67
68
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { APIError } from 'payload'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the base URL of the OpenAI-compatible gateway used by the `ollama`
|
|
5
|
+
* provider.
|
|
6
|
+
*
|
|
7
|
+
* There is deliberately NO default here. An integrator who selects `ollama` does
|
|
8
|
+
* so precisely to keep ticket content off third-party infrastructure; a silent
|
|
9
|
+
* fallback to any hosted endpoint would ship the whole conversation — subject,
|
|
10
|
+
* message bodies, client name and company — to a server the operator never chose,
|
|
11
|
+
* and would do so invisibly because the AI answer still comes back normally.
|
|
12
|
+
*
|
|
13
|
+
* Fail loudly instead: a missing `OLLAMA_API_URL` is a configuration error.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveOllamaBaseUrl(): string {
|
|
16
|
+
const baseURL = process.env.OLLAMA_API_URL
|
|
17
|
+
if (!baseURL) {
|
|
18
|
+
throw new APIError("provider 'ollama' requires OLLAMA_API_URL", 500)
|
|
19
|
+
}
|
|
20
|
+
return baseURL
|
|
21
|
+
}
|