@consilioweb/payload-support 0.6.5 → 0.8.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 +1 -1
- package/dist/components/TicketConversation/hooks/useAI.cjs +2 -2
- package/dist/components/TicketConversation/hooks/useAI.js +2 -2
- package/dist/components/TicketConversation/index.cjs +97 -4
- package/dist/components/TicketConversation/index.js +97 -4
- package/dist/index.cjs +387 -11
- package/dist/index.js +387 -11
- package/package.json +1 -1
- package/src/collections/ClientSummaries.ts +118 -0
- package/src/collections/Tickets.ts +54 -8
- package/src/collections/index.ts +1 -0
- package/src/components/TicketConversation/hooks/useAI.ts +2 -2
- package/src/components/TicketConversation/index.tsx +96 -6
- package/src/endpoints/ai.ts +12 -2
- package/src/endpoints/client-intelligence.ts +257 -0
- package/src/endpoints/index.ts +5 -1
- package/src/plugin.ts +2 -0
package/dist/index.cjs
CHANGED
|
@@ -280,9 +280,18 @@ R\xE9dige une r\xE9ponse appropri\xE9e au dernier message du client. Sois concis
|
|
|
280
280
|
if (!aiSettings.enableRewrite) {
|
|
281
281
|
return Response.json({ rewritten: "", disabled: true });
|
|
282
282
|
}
|
|
283
|
-
const { text } = body;
|
|
283
|
+
const { text, style } = body;
|
|
284
284
|
if (!text?.trim()) return Response.json({ error: "text required" }, { status: 400 });
|
|
285
|
-
const
|
|
285
|
+
const styleInstructions = {
|
|
286
|
+
auto: "Garde le m\xEAme ton (tutoiement/vouvoiement).",
|
|
287
|
+
tutoyer: "Utilise le tutoiement. Si le texte vouvoie, convertis en tutoiement.",
|
|
288
|
+
vouvoyer: "Utilise le vouvoiement. Si le texte tutoie, convertis en vouvoiement.",
|
|
289
|
+
formel: "Adopte un ton formel et professionnel avec vouvoiement.",
|
|
290
|
+
court: "Raccourcis le texte au maximum tout en gardant le sens. Sois concis et direct.",
|
|
291
|
+
amical: "Adopte un ton chaleureux et amical avec tutoiement."
|
|
292
|
+
};
|
|
293
|
+
const styleGuide = styleInstructions[style || "auto"] || styleInstructions.auto;
|
|
294
|
+
const prompt = `Tu es un agent de support technique professionnel. Reformule le texte ci-dessous de mani\xE8re plus professionnelle et corrige les fautes d'orthographe/grammaire. ${styleGuide} Ne change pas le fond du message, am\xE9liore uniquement la forme. R\xE9ponds UNIQUEMENT avec le texte reformul\xE9, sans commentaire ni explication.
|
|
286
295
|
|
|
287
296
|
Texte original :
|
|
288
297
|
${text}`;
|
|
@@ -305,6 +314,210 @@ ${text}`;
|
|
|
305
314
|
};
|
|
306
315
|
}
|
|
307
316
|
|
|
317
|
+
// src/endpoints/client-intelligence.ts
|
|
318
|
+
function getClient2(aiSettings) {
|
|
319
|
+
const Anthropic = __require("@anthropic-ai/sdk").default;
|
|
320
|
+
if (aiSettings.provider === "ollama") {
|
|
321
|
+
const baseURL = process.env.OLLAMA_API_URL || "https://ollama.orkelis.app/v1";
|
|
322
|
+
return new Anthropic({ apiKey: "ollama", baseURL });
|
|
323
|
+
}
|
|
324
|
+
return new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
325
|
+
}
|
|
326
|
+
function getModel2(aiSettings) {
|
|
327
|
+
return aiSettings.model || "claude-haiku-4-5-20251001";
|
|
328
|
+
}
|
|
329
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
330
|
+
function createClientIntelligenceEndpoint(slugs) {
|
|
331
|
+
const getHandler = async (req) => {
|
|
332
|
+
try {
|
|
333
|
+
requireAdmin(req, slugs);
|
|
334
|
+
const payload = req.payload;
|
|
335
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
336
|
+
const clientId = url.searchParams.get("clientId");
|
|
337
|
+
if (!clientId) return Response.json({ error: "clientId required" }, { status: 400 });
|
|
338
|
+
const existing = await payload.find({
|
|
339
|
+
collection: "client-summaries",
|
|
340
|
+
where: { client: { equals: Number(clientId) } },
|
|
341
|
+
limit: 1,
|
|
342
|
+
depth: 0,
|
|
343
|
+
overrideAccess: true
|
|
344
|
+
});
|
|
345
|
+
if (existing.docs.length > 0) {
|
|
346
|
+
const cached = existing.docs[0];
|
|
347
|
+
const age = Date.now() - new Date(cached.generatedAt || 0).getTime();
|
|
348
|
+
if (age < CACHE_TTL_MS) {
|
|
349
|
+
return Response.json({ ...cached, fromCache: true });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return await generateSummary(payload, clientId, slugs, existing.docs[0]?.id);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
const authResponse = handleAuthError(error);
|
|
355
|
+
if (authResponse) return authResponse;
|
|
356
|
+
console.error("[client-intelligence] Error:", error);
|
|
357
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
const postHandler = async (req) => {
|
|
361
|
+
try {
|
|
362
|
+
requireAdmin(req, slugs);
|
|
363
|
+
const payload = req.payload;
|
|
364
|
+
const body = await req.json?.() || {};
|
|
365
|
+
const clientId = body.clientId;
|
|
366
|
+
if (!clientId) return Response.json({ error: "clientId required" }, { status: 400 });
|
|
367
|
+
const existing = await payload.find({
|
|
368
|
+
collection: "client-summaries",
|
|
369
|
+
where: { client: { equals: Number(clientId) } },
|
|
370
|
+
limit: 1,
|
|
371
|
+
depth: 0,
|
|
372
|
+
overrideAccess: true
|
|
373
|
+
});
|
|
374
|
+
return await generateSummary(payload, clientId, slugs, existing.docs[0]?.id);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
const authResponse = handleAuthError(error);
|
|
377
|
+
if (authResponse) return authResponse;
|
|
378
|
+
console.error("[client-intelligence] Refresh error:", error);
|
|
379
|
+
return Response.json({ error: "Internal server error" }, { status: 500 });
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
return [
|
|
383
|
+
{ path: "/support/client-intelligence", method: "get", handler: getHandler },
|
|
384
|
+
{ path: "/support/client-intelligence", method: "post", handler: postHandler }
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
async function generateSummary(payload, clientId, slugs, existingId) {
|
|
388
|
+
const aiSettings = (await readSupportSettings(payload)).ai;
|
|
389
|
+
if (!aiSettings.enableSynthesis) {
|
|
390
|
+
return Response.json({ error: "AI synthesis disabled in settings" }, { status: 400 });
|
|
391
|
+
}
|
|
392
|
+
const client = await payload.findByID({
|
|
393
|
+
collection: slugs.supportClients,
|
|
394
|
+
id: Number(clientId),
|
|
395
|
+
depth: 0,
|
|
396
|
+
overrideAccess: true
|
|
397
|
+
});
|
|
398
|
+
if (!client) return Response.json({ error: "Client not found" }, { status: 404 });
|
|
399
|
+
const clientName = [client.firstName, client.lastName].filter(Boolean).join(" ") || client.company || client.email;
|
|
400
|
+
const tickets = await payload.find({
|
|
401
|
+
collection: slugs.tickets,
|
|
402
|
+
where: { client: { equals: Number(clientId) } },
|
|
403
|
+
sort: "-createdAt",
|
|
404
|
+
limit: 50,
|
|
405
|
+
depth: 0,
|
|
406
|
+
overrideAccess: true
|
|
407
|
+
});
|
|
408
|
+
if (tickets.totalDocs === 0) {
|
|
409
|
+
return Response.json({
|
|
410
|
+
summary: "Aucun ticket pour ce client.",
|
|
411
|
+
recurringTopics: [],
|
|
412
|
+
patterns: [],
|
|
413
|
+
keyFacts: [],
|
|
414
|
+
ticketCount: 0,
|
|
415
|
+
messageCount: 0
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
const ticketIds = tickets.docs.slice(0, 20).map((t) => t.id);
|
|
419
|
+
const messages = await payload.find({
|
|
420
|
+
collection: slugs.ticketMessages,
|
|
421
|
+
where: { ticket: { in: ticketIds.join(",") } },
|
|
422
|
+
sort: "createdAt",
|
|
423
|
+
limit: 200,
|
|
424
|
+
depth: 0,
|
|
425
|
+
overrideAccess: true
|
|
426
|
+
});
|
|
427
|
+
let avgSatisfaction = null;
|
|
428
|
+
try {
|
|
429
|
+
const surveys = await payload.find({
|
|
430
|
+
collection: slugs.satisfactionSurveys || "satisfaction-surveys",
|
|
431
|
+
where: { client: { equals: Number(clientId) } },
|
|
432
|
+
limit: 50,
|
|
433
|
+
depth: 0,
|
|
434
|
+
overrideAccess: true
|
|
435
|
+
});
|
|
436
|
+
if (surveys.totalDocs > 0) {
|
|
437
|
+
const ratings = surveys.docs.filter((s) => s.rating).map((s) => s.rating);
|
|
438
|
+
if (ratings.length > 0) avgSatisfaction = Math.round(ratings.reduce((a, b) => a + b, 0) / ratings.length * 10) / 10;
|
|
439
|
+
}
|
|
440
|
+
} catch {
|
|
441
|
+
}
|
|
442
|
+
const ticketSummaries = tickets.docs.map((t) => {
|
|
443
|
+
const msgs = messages.docs.filter((m) => {
|
|
444
|
+
const mTicket = typeof m.ticket === "object" ? m.ticket.id : m.ticket;
|
|
445
|
+
return mTicket === t.id;
|
|
446
|
+
});
|
|
447
|
+
const clientMsgs = msgs.filter((m) => m.authorType === "client" || m.authorType === "email");
|
|
448
|
+
const adminMsgs = msgs.filter((m) => m.authorType === "admin");
|
|
449
|
+
return `Ticket ${t.ticketNumber} (${t.status}) \u2014 "${t.subject}"
|
|
450
|
+
Client: ${clientMsgs.map((m) => m.body?.slice(0, 200)).join(" | ")}
|
|
451
|
+
Admin: ${adminMsgs.map((m) => m.body?.slice(0, 200)).join(" | ")}`;
|
|
452
|
+
}).join("\n\n");
|
|
453
|
+
const prompt = `Tu es un assistant d'analyse CRM pour un support technique. Analyse l'historique complet de ce client et g\xE9n\xE8re un rapport structur\xE9.
|
|
454
|
+
|
|
455
|
+
CLIENT : ${clientName} (${client.company || "pas de soci\xE9t\xE9"})
|
|
456
|
+
Email : ${client.email}
|
|
457
|
+
Nombre de tickets : ${tickets.totalDocs}
|
|
458
|
+
Satisfaction moyenne : ${avgSatisfaction ?? "non \xE9valu\xE9e"}
|
|
459
|
+
|
|
460
|
+
HISTORIQUE DES TICKETS :
|
|
461
|
+
${ticketSummaries.slice(0, 4e3)}
|
|
462
|
+
|
|
463
|
+
R\xE9ponds en JSON strict (pas de markdown, pas de commentaires) avec cette structure :
|
|
464
|
+
{
|
|
465
|
+
"summary": "R\xE9sum\xE9 global du client en 2-3 phrases (qui il est, ce qu'il demande habituellement, son niveau de satisfaction)",
|
|
466
|
+
"recurringTopics": [{"topic": "nom du sujet", "count": N, "lastSeen": "YYYY-MM-DD"}],
|
|
467
|
+
"patterns": ["pattern 1 observ\xE9", "pattern 2 observ\xE9"],
|
|
468
|
+
"keyFacts": ["fait cl\xE9 1 sur le client", "fait cl\xE9 2"]
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
Sois factuel. Ne d\xE9passe pas 5 items par tableau. R\xE9ponds UNIQUEMENT avec le JSON.`;
|
|
472
|
+
const anthropic = getClient2(aiSettings);
|
|
473
|
+
const model = getModel2(aiSettings);
|
|
474
|
+
const res = await anthropic.messages.create({
|
|
475
|
+
model,
|
|
476
|
+
max_tokens: 1e3,
|
|
477
|
+
messages: [{ role: "user", content: prompt }]
|
|
478
|
+
});
|
|
479
|
+
const rawText = res.content[0].type === "text" ? res.content[0].text : "{}";
|
|
480
|
+
let parsed = {};
|
|
481
|
+
try {
|
|
482
|
+
const jsonMatch = rawText.match(/\{[\s\S]*\}/);
|
|
483
|
+
if (jsonMatch) parsed = JSON.parse(jsonMatch[0]);
|
|
484
|
+
} catch {
|
|
485
|
+
parsed = { summary: rawText, recurringTopics: [], patterns: [], keyFacts: [] };
|
|
486
|
+
}
|
|
487
|
+
const data = {
|
|
488
|
+
client: Number(clientId),
|
|
489
|
+
clientName,
|
|
490
|
+
summary: parsed.summary || "R\xE9sum\xE9 non disponible",
|
|
491
|
+
recurringTopics: parsed.recurringTopics || [],
|
|
492
|
+
patterns: parsed.patterns || [],
|
|
493
|
+
keyFacts: parsed.keyFacts || [],
|
|
494
|
+
ticketCount: tickets.totalDocs,
|
|
495
|
+
messageCount: messages.totalDocs,
|
|
496
|
+
averageSatisfaction: avgSatisfaction,
|
|
497
|
+
firstTicketAt: tickets.docs[tickets.docs.length - 1]?.createdAt || null,
|
|
498
|
+
lastTicketAt: tickets.docs[0]?.createdAt || null,
|
|
499
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
500
|
+
aiModel: model
|
|
501
|
+
};
|
|
502
|
+
let saved;
|
|
503
|
+
if (existingId) {
|
|
504
|
+
saved = await payload.update({
|
|
505
|
+
collection: "client-summaries",
|
|
506
|
+
id: existingId,
|
|
507
|
+
data,
|
|
508
|
+
overrideAccess: true
|
|
509
|
+
});
|
|
510
|
+
} else {
|
|
511
|
+
saved = await payload.create({
|
|
512
|
+
collection: "client-summaries",
|
|
513
|
+
data,
|
|
514
|
+
overrideAccess: true
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
console.log(`[client-intelligence] Generated summary for ${clientName} (${tickets.totalDocs} tickets, ${messages.totalDocs} messages)`);
|
|
518
|
+
return Response.json({ ...saved, fromCache: false });
|
|
519
|
+
}
|
|
520
|
+
|
|
308
521
|
// src/endpoints/search.ts
|
|
309
522
|
function createSearchEndpoint(slugs) {
|
|
310
523
|
return {
|
|
@@ -4756,7 +4969,10 @@ function createSupportEndpoints(slugs, options) {
|
|
|
4756
4969
|
createUserPrefsGetEndpoint(slugs),
|
|
4757
4970
|
createUserPrefsPostEndpoint(slugs)
|
|
4758
4971
|
];
|
|
4759
|
-
if (!f || f.ai !== false)
|
|
4972
|
+
if (!f || f.ai !== false) {
|
|
4973
|
+
endpoints.push(createAiEndpoint(slugs));
|
|
4974
|
+
endpoints.push(...createClientIntelligenceEndpoint(slugs));
|
|
4975
|
+
}
|
|
4760
4976
|
if (!f || f.bulkActions !== false) endpoints.push(createBulkActionEndpoint(slugs));
|
|
4761
4977
|
if (!f || f.merge !== false) endpoints.push(createMergeTicketsEndpoint(slugs));
|
|
4762
4978
|
if (!f || f.splitTicket !== false) endpoints.push(createSplitTicketEndpoint(slugs));
|
|
@@ -5584,6 +5800,17 @@ function createFireTicketWebhooks(slugs) {
|
|
|
5584
5800
|
subject: doc.subject,
|
|
5585
5801
|
previousStatus: previousDoc.status
|
|
5586
5802
|
});
|
|
5803
|
+
const clientId = typeof doc.client === "object" ? doc.client?.id : doc.client;
|
|
5804
|
+
if (clientId) {
|
|
5805
|
+
payload.update({
|
|
5806
|
+
collection: "client-summaries",
|
|
5807
|
+
where: { client: { equals: clientId } },
|
|
5808
|
+
data: { generatedAt: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
5809
|
+
// Force cache expiry
|
|
5810
|
+
overrideAccess: true
|
|
5811
|
+
}).catch(() => {
|
|
5812
|
+
});
|
|
5813
|
+
}
|
|
5587
5814
|
}
|
|
5588
5815
|
const oldAssigned = typeof previousDoc.assignedTo === "object" ? previousDoc.assignedTo?.id : previousDoc.assignedTo;
|
|
5589
5816
|
const newAssigned = typeof doc.assignedTo === "object" ? doc.assignedTo?.id : doc.assignedTo;
|
|
@@ -5672,6 +5899,37 @@ function createTicketsCollection(slugs, options) {
|
|
|
5672
5899
|
});
|
|
5673
5900
|
}
|
|
5674
5901
|
const billingFields = [
|
|
5902
|
+
{
|
|
5903
|
+
type: "row",
|
|
5904
|
+
fields: [
|
|
5905
|
+
{
|
|
5906
|
+
name: "billingType",
|
|
5907
|
+
type: "select",
|
|
5908
|
+
label: "Type de facturation",
|
|
5909
|
+
defaultValue: "hourly",
|
|
5910
|
+
options: [
|
|
5911
|
+
{ label: "Au temps pass\xE9", value: "hourly" },
|
|
5912
|
+
{ label: "Forfait", value: "flat" }
|
|
5913
|
+
],
|
|
5914
|
+
admin: { width: "33%" }
|
|
5915
|
+
},
|
|
5916
|
+
{
|
|
5917
|
+
name: "flatRateAmount",
|
|
5918
|
+
type: "number",
|
|
5919
|
+
label: "Montant forfait (EUR)",
|
|
5920
|
+
admin: {
|
|
5921
|
+
width: "33%",
|
|
5922
|
+
condition: (data) => data?.billingType === "flat"
|
|
5923
|
+
}
|
|
5924
|
+
},
|
|
5925
|
+
{
|
|
5926
|
+
name: "billedAmount",
|
|
5927
|
+
type: "number",
|
|
5928
|
+
label: "Montant factur\xE9 (EUR)",
|
|
5929
|
+
admin: { width: "33%" }
|
|
5930
|
+
}
|
|
5931
|
+
]
|
|
5932
|
+
},
|
|
5675
5933
|
{
|
|
5676
5934
|
type: "row",
|
|
5677
5935
|
fields: [
|
|
@@ -5712,14 +5970,8 @@ function createTicketsCollection(slugs, options) {
|
|
|
5712
5970
|
{
|
|
5713
5971
|
name: "paidAt",
|
|
5714
5972
|
type: "date",
|
|
5715
|
-
label: "
|
|
5973
|
+
label: "Pay\xE9 le",
|
|
5716
5974
|
admin: { width: "33%", date: { displayFormat: "dd/MM/yyyy HH:mm" } }
|
|
5717
|
-
},
|
|
5718
|
-
{
|
|
5719
|
-
name: "billedAmount",
|
|
5720
|
-
type: "number",
|
|
5721
|
-
label: "Montant facture (EUR)",
|
|
5722
|
-
admin: { width: "33%" }
|
|
5723
5975
|
}
|
|
5724
5976
|
]
|
|
5725
5977
|
}
|
|
@@ -5881,7 +6133,17 @@ function createTicketsCollection(slugs, options) {
|
|
|
5881
6133
|
{ name: "snoozeUntil", type: "date", label: "Snooze jusqu'au", admin: { position: "sidebar", date: { pickerAppearance: "dayAndTime", displayFormat: "dd/MM/yyyy HH:mm" } } },
|
|
5882
6134
|
// Billing sidebar
|
|
5883
6135
|
{ name: "billable", type: "checkbox", defaultValue: true, label: "Facturable", admin: { position: "sidebar" } },
|
|
5884
|
-
{
|
|
6136
|
+
{
|
|
6137
|
+
name: "showTimeToClient",
|
|
6138
|
+
type: "checkbox",
|
|
6139
|
+
defaultValue: true,
|
|
6140
|
+
label: "Afficher le temps au client",
|
|
6141
|
+
admin: {
|
|
6142
|
+
position: "sidebar",
|
|
6143
|
+
description: "Auto-d\xE9sactiv\xE9 en mode forfait",
|
|
6144
|
+
condition: (data) => data?.billingType !== "flat"
|
|
6145
|
+
}
|
|
6146
|
+
},
|
|
5885
6147
|
{ name: "totalTimeMinutes", type: "number", defaultValue: 0, label: "Temps total (minutes)", admin: { readOnly: true, position: "sidebar" } }
|
|
5886
6148
|
],
|
|
5887
6149
|
hooks: {
|
|
@@ -7647,6 +7909,119 @@ function createTicketStatusesCollection(slugs) {
|
|
|
7647
7909
|
};
|
|
7648
7910
|
}
|
|
7649
7911
|
|
|
7912
|
+
// src/collections/ClientSummaries.ts
|
|
7913
|
+
function createClientSummariesCollection(slugs) {
|
|
7914
|
+
return {
|
|
7915
|
+
slug: "client-summaries",
|
|
7916
|
+
labels: { singular: "R\xE9sum\xE9 client", plural: "R\xE9sum\xE9s clients" },
|
|
7917
|
+
admin: {
|
|
7918
|
+
group: "Support",
|
|
7919
|
+
hidden: true,
|
|
7920
|
+
// Not directly editable — managed via API
|
|
7921
|
+
defaultColumns: ["client", "generatedAt", "ticketCount"],
|
|
7922
|
+
useAsTitle: "clientName"
|
|
7923
|
+
},
|
|
7924
|
+
fields: [
|
|
7925
|
+
{
|
|
7926
|
+
name: "client",
|
|
7927
|
+
type: "relationship",
|
|
7928
|
+
relationTo: slugs.supportClients,
|
|
7929
|
+
required: true,
|
|
7930
|
+
unique: true,
|
|
7931
|
+
index: true,
|
|
7932
|
+
label: "Client"
|
|
7933
|
+
},
|
|
7934
|
+
{
|
|
7935
|
+
name: "clientName",
|
|
7936
|
+
type: "text",
|
|
7937
|
+
label: "Nom client",
|
|
7938
|
+
admin: { readOnly: true }
|
|
7939
|
+
},
|
|
7940
|
+
// ── AI-generated content ──
|
|
7941
|
+
{
|
|
7942
|
+
name: "summary",
|
|
7943
|
+
type: "textarea",
|
|
7944
|
+
label: "R\xE9sum\xE9 global",
|
|
7945
|
+
admin: { readOnly: true }
|
|
7946
|
+
},
|
|
7947
|
+
{
|
|
7948
|
+
name: "recurringTopics",
|
|
7949
|
+
type: "json",
|
|
7950
|
+
label: "Sujets r\xE9currents",
|
|
7951
|
+
admin: { readOnly: true }
|
|
7952
|
+
// Array of { topic: string, count: number, lastSeen: string }
|
|
7953
|
+
},
|
|
7954
|
+
{
|
|
7955
|
+
name: "patterns",
|
|
7956
|
+
type: "json",
|
|
7957
|
+
label: "Patterns d\xE9tect\xE9s",
|
|
7958
|
+
admin: { readOnly: true }
|
|
7959
|
+
// Array of strings: "Revient souvent pour X", "Préfère le tutoiement", etc.
|
|
7960
|
+
},
|
|
7961
|
+
{
|
|
7962
|
+
name: "keyFacts",
|
|
7963
|
+
type: "json",
|
|
7964
|
+
label: "Faits cl\xE9s",
|
|
7965
|
+
admin: { readOnly: true }
|
|
7966
|
+
// Array of strings: "Hébergé chez OVH", "Site WordPress", etc.
|
|
7967
|
+
},
|
|
7968
|
+
// ── Stats ──
|
|
7969
|
+
{
|
|
7970
|
+
name: "ticketCount",
|
|
7971
|
+
type: "number",
|
|
7972
|
+
label: "Nombre de tickets analys\xE9s",
|
|
7973
|
+
defaultValue: 0,
|
|
7974
|
+
admin: { readOnly: true }
|
|
7975
|
+
},
|
|
7976
|
+
{
|
|
7977
|
+
name: "messageCount",
|
|
7978
|
+
type: "number",
|
|
7979
|
+
label: "Nombre de messages analys\xE9s",
|
|
7980
|
+
defaultValue: 0,
|
|
7981
|
+
admin: { readOnly: true }
|
|
7982
|
+
},
|
|
7983
|
+
{
|
|
7984
|
+
name: "averageSatisfaction",
|
|
7985
|
+
type: "number",
|
|
7986
|
+
label: "Satisfaction moyenne",
|
|
7987
|
+
admin: { readOnly: true }
|
|
7988
|
+
},
|
|
7989
|
+
{
|
|
7990
|
+
name: "firstTicketAt",
|
|
7991
|
+
type: "date",
|
|
7992
|
+
label: "Premier ticket",
|
|
7993
|
+
admin: { readOnly: true }
|
|
7994
|
+
},
|
|
7995
|
+
{
|
|
7996
|
+
name: "lastTicketAt",
|
|
7997
|
+
type: "date",
|
|
7998
|
+
label: "Dernier ticket",
|
|
7999
|
+
admin: { readOnly: true }
|
|
8000
|
+
},
|
|
8001
|
+
// ── Meta ──
|
|
8002
|
+
{
|
|
8003
|
+
name: "generatedAt",
|
|
8004
|
+
type: "date",
|
|
8005
|
+
label: "G\xE9n\xE9r\xE9 le",
|
|
8006
|
+
admin: { readOnly: true, date: { displayFormat: "dd/MM/yyyy HH:mm" } }
|
|
8007
|
+
},
|
|
8008
|
+
{
|
|
8009
|
+
name: "aiModel",
|
|
8010
|
+
type: "text",
|
|
8011
|
+
label: "Mod\xE8le IA utilis\xE9",
|
|
8012
|
+
admin: { readOnly: true }
|
|
8013
|
+
}
|
|
8014
|
+
],
|
|
8015
|
+
access: {
|
|
8016
|
+
create: ({ req }) => req.user?.collection === "users",
|
|
8017
|
+
read: ({ req }) => req.user?.collection === "users",
|
|
8018
|
+
update: ({ req }) => req.user?.collection === "users",
|
|
8019
|
+
delete: ({ req }) => req.user?.collection === "users"
|
|
8020
|
+
},
|
|
8021
|
+
timestamps: true
|
|
8022
|
+
};
|
|
8023
|
+
}
|
|
8024
|
+
|
|
7650
8025
|
// src/plugin.ts
|
|
7651
8026
|
function viewConfig(component, path) {
|
|
7652
8027
|
return { Component: component, path };
|
|
@@ -7691,6 +8066,7 @@ function supportPlugin(config) {
|
|
|
7691
8066
|
if (features.customStatuses !== false) supportCollections.push(createTicketStatusesCollection(slugs));
|
|
7692
8067
|
if (features.chat) supportCollections.push(createChatMessagesCollection(slugs));
|
|
7693
8068
|
if (features.pendingEmails) supportCollections.push(createPendingEmailsCollection(slugs));
|
|
8069
|
+
if (features.ai !== false) supportCollections.push(createClientSummariesCollection(slugs));
|
|
7694
8070
|
const existingViews = incomingConfig.admin?.components?.views || {};
|
|
7695
8071
|
const supportViews = {
|
|
7696
8072
|
"support-inbox": viewConfig(`${viewsBase}#TicketInboxView`, `${bp}/inbox`),
|