@contractspec/lib.support-bot 2.3.0 → 2.5.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/dist/bot/auto-responder.d.ts +2 -0
- package/dist/bot/auto-responder.js +678 -21
- package/dist/bot/feedback-loop.js +652 -2
- package/dist/bot/index.js +681 -23
- package/dist/browser/bot/auto-responder.js +678 -21
- package/dist/browser/bot/feedback-loop.js +652 -2
- package/dist/browser/bot/index.js +681 -23
- package/dist/browser/i18n/catalogs/en.js +191 -0
- package/dist/browser/i18n/catalogs/es.js +191 -0
- package/dist/browser/i18n/catalogs/fr.js +191 -0
- package/dist/browser/i18n/catalogs/index.js +571 -0
- package/dist/browser/i18n/index.js +670 -0
- package/dist/browser/i18n/keys.js +71 -0
- package/dist/browser/i18n/locale.js +13 -0
- package/dist/browser/i18n/messages.js +585 -0
- package/dist/browser/index.js +884 -58
- package/dist/browser/rag/index.js +662 -5
- package/dist/browser/rag/ticket-resolver.js +662 -5
- package/dist/browser/tickets/classifier.js +839 -30
- package/dist/browser/tickets/index.js +839 -30
- package/dist/i18n/catalogs/en.d.ts +8 -0
- package/dist/i18n/catalogs/en.js +192 -0
- package/dist/i18n/catalogs/es.d.ts +6 -0
- package/dist/i18n/catalogs/es.js +192 -0
- package/dist/i18n/catalogs/fr.d.ts +6 -0
- package/dist/i18n/catalogs/fr.js +192 -0
- package/dist/i18n/catalogs/index.d.ts +8 -0
- package/dist/i18n/catalogs/index.js +572 -0
- package/dist/i18n/i18n.test.d.ts +1 -0
- package/dist/i18n/index.d.ts +30 -0
- package/dist/i18n/index.js +671 -0
- package/dist/i18n/keys.d.ts +174 -0
- package/dist/i18n/keys.js +72 -0
- package/dist/i18n/locale.d.ts +8 -0
- package/dist/i18n/locale.js +14 -0
- package/dist/i18n/messages.d.ts +15 -0
- package/dist/i18n/messages.js +586 -0
- package/dist/index.js +884 -58
- package/dist/node/bot/auto-responder.js +678 -21
- package/dist/node/bot/feedback-loop.js +652 -2
- package/dist/node/bot/index.js +681 -23
- package/dist/node/i18n/catalogs/en.js +191 -0
- package/dist/node/i18n/catalogs/es.js +191 -0
- package/dist/node/i18n/catalogs/fr.js +191 -0
- package/dist/node/i18n/catalogs/index.js +571 -0
- package/dist/node/i18n/index.js +670 -0
- package/dist/node/i18n/keys.js +71 -0
- package/dist/node/i18n/locale.js +13 -0
- package/dist/node/i18n/messages.js +585 -0
- package/dist/node/index.js +884 -58
- package/dist/node/rag/index.js +662 -5
- package/dist/node/rag/ticket-resolver.js +662 -5
- package/dist/node/tickets/classifier.js +839 -30
- package/dist/node/tickets/index.js +839 -30
- package/dist/rag/index.js +662 -5
- package/dist/rag/ticket-resolver.d.ts +2 -0
- package/dist/rag/ticket-resolver.js +662 -5
- package/dist/tickets/classifier.d.ts +6 -0
- package/dist/tickets/classifier.js +839 -30
- package/dist/tickets/index.js +839 -30
- package/package.json +148 -8
package/dist/browser/index.js
CHANGED
|
@@ -1,14 +1,666 @@
|
|
|
1
|
+
// src/i18n/catalogs/en.ts
|
|
2
|
+
import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
|
|
3
|
+
var enMessages = defineTranslation({
|
|
4
|
+
meta: {
|
|
5
|
+
key: "support-bot.messages",
|
|
6
|
+
version: "1.0.0",
|
|
7
|
+
domain: "support-bot",
|
|
8
|
+
description: "All user-facing, LLM-facing, and developer-facing strings for the support-bot package",
|
|
9
|
+
owners: ["platform"],
|
|
10
|
+
stability: "experimental"
|
|
11
|
+
},
|
|
12
|
+
locale: "en",
|
|
13
|
+
fallback: "en",
|
|
14
|
+
messages: {
|
|
15
|
+
"prompt.classifier.system": {
|
|
16
|
+
value: "Classify the support ticket.",
|
|
17
|
+
description: "Classifier LLM system prompt"
|
|
18
|
+
},
|
|
19
|
+
"prompt.autoResponder.system": {
|
|
20
|
+
value: "Write empathetic, accurate support replies that cite sources when relevant.",
|
|
21
|
+
description: "Auto-responder LLM system prompt"
|
|
22
|
+
},
|
|
23
|
+
"prompt.autoResponder.user": {
|
|
24
|
+
value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
|
|
25
|
+
Subject: {subject}
|
|
26
|
+
|
|
27
|
+
{body}
|
|
28
|
+
|
|
29
|
+
Knowledge:
|
|
30
|
+
{knowledge}
|
|
31
|
+
|
|
32
|
+
Respond to the customer.`,
|
|
33
|
+
description: "Auto-responder user prompt template",
|
|
34
|
+
placeholders: [
|
|
35
|
+
{ name: "ticketId", type: "string" },
|
|
36
|
+
{ name: "category", type: "string" },
|
|
37
|
+
{ name: "priority", type: "string" },
|
|
38
|
+
{ name: "sentiment", type: "string" },
|
|
39
|
+
{ name: "subject", type: "string" },
|
|
40
|
+
{ name: "body", type: "string" },
|
|
41
|
+
{ name: "knowledge", type: "string" }
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"responder.closing.friendly": {
|
|
45
|
+
value: "We remain available if you need anything else.",
|
|
46
|
+
description: "Friendly closing line for support responses"
|
|
47
|
+
},
|
|
48
|
+
"responder.closing.formal": {
|
|
49
|
+
value: "Please let us know if you require additional assistance.",
|
|
50
|
+
description: "Formal closing line for support responses"
|
|
51
|
+
},
|
|
52
|
+
"responder.greeting.named": {
|
|
53
|
+
value: "Hi {name},",
|
|
54
|
+
description: "Greeting with customer name",
|
|
55
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
56
|
+
},
|
|
57
|
+
"responder.greeting.anonymous": {
|
|
58
|
+
value: "Hi there,",
|
|
59
|
+
description: "Greeting without customer name"
|
|
60
|
+
},
|
|
61
|
+
"responder.intro.thanks": {
|
|
62
|
+
value: 'Thanks for contacting us about "{subject}".',
|
|
63
|
+
description: "Thank-you intro referencing the ticket subject",
|
|
64
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
65
|
+
},
|
|
66
|
+
"responder.signature": {
|
|
67
|
+
value: "— ContractSpec Support",
|
|
68
|
+
description: "Email / response signature"
|
|
69
|
+
},
|
|
70
|
+
"responder.references.header": {
|
|
71
|
+
value: "References:",
|
|
72
|
+
description: "Header for the references section"
|
|
73
|
+
},
|
|
74
|
+
"responder.references.sourceLabel": {
|
|
75
|
+
value: "Source {index}",
|
|
76
|
+
description: "Label for a numbered source reference",
|
|
77
|
+
placeholders: [{ name: "index", type: "number" }]
|
|
78
|
+
},
|
|
79
|
+
"responder.category.billing": {
|
|
80
|
+
value: "I understand billing issues can be stressful, so let me clarify the situation.",
|
|
81
|
+
description: "Category intro for billing tickets"
|
|
82
|
+
},
|
|
83
|
+
"responder.category.technical": {
|
|
84
|
+
value: "I see you encountered a technical issue. Here is what happened and how to fix it.",
|
|
85
|
+
description: "Category intro for technical tickets"
|
|
86
|
+
},
|
|
87
|
+
"responder.category.product": {
|
|
88
|
+
value: "Thanks for sharing feedback about the product. Here are the next steps.",
|
|
89
|
+
description: "Category intro for product tickets"
|
|
90
|
+
},
|
|
91
|
+
"responder.category.account": {
|
|
92
|
+
value: "Account access is critical, so let me walk you through the resolution.",
|
|
93
|
+
description: "Category intro for account tickets"
|
|
94
|
+
},
|
|
95
|
+
"responder.category.compliance": {
|
|
96
|
+
value: "Compliance questions require precision. See the policy-aligned answer below.",
|
|
97
|
+
description: "Category intro for compliance tickets"
|
|
98
|
+
},
|
|
99
|
+
"responder.category.other": {
|
|
100
|
+
value: "Here is what we found after reviewing your request.",
|
|
101
|
+
description: "Category intro for uncategorized tickets"
|
|
102
|
+
},
|
|
103
|
+
"responder.subject.replyPrefix": {
|
|
104
|
+
value: "Re: {subject}",
|
|
105
|
+
description: "Reply subject line prefix",
|
|
106
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
107
|
+
},
|
|
108
|
+
"resolver.question.subjectLabel": {
|
|
109
|
+
value: "Subject: {subject}",
|
|
110
|
+
description: "Subject label in resolver question context",
|
|
111
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
112
|
+
},
|
|
113
|
+
"resolver.question.channelLabel": {
|
|
114
|
+
value: "Channel: {channel}",
|
|
115
|
+
description: "Channel label in resolver question context",
|
|
116
|
+
placeholders: [{ name: "channel", type: "string" }]
|
|
117
|
+
},
|
|
118
|
+
"resolver.question.customerLabel": {
|
|
119
|
+
value: "Customer: {name}",
|
|
120
|
+
description: "Customer label in resolver question context",
|
|
121
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
122
|
+
},
|
|
123
|
+
"resolver.action.escalate": {
|
|
124
|
+
value: "Escalate for human review",
|
|
125
|
+
description: "Action label for escalation"
|
|
126
|
+
},
|
|
127
|
+
"resolver.action.respond": {
|
|
128
|
+
value: "Send automated response",
|
|
129
|
+
description: "Action label for automated response"
|
|
130
|
+
},
|
|
131
|
+
"resolver.escalation.insufficientConfidence": {
|
|
132
|
+
value: "Insufficient confidence or missing knowledge references",
|
|
133
|
+
description: "Escalation reason when confidence is too low"
|
|
134
|
+
},
|
|
135
|
+
"tool.classify.title": {
|
|
136
|
+
value: "support_classify_ticket",
|
|
137
|
+
description: "MCP tool title for ticket classification"
|
|
138
|
+
},
|
|
139
|
+
"tool.classify.description": {
|
|
140
|
+
value: "Classify a ticket for priority, sentiment, and category",
|
|
141
|
+
description: "MCP tool description for ticket classification"
|
|
142
|
+
},
|
|
143
|
+
"tool.resolve.title": {
|
|
144
|
+
value: "support_resolve_ticket",
|
|
145
|
+
description: "MCP tool title for ticket resolution"
|
|
146
|
+
},
|
|
147
|
+
"tool.resolve.description": {
|
|
148
|
+
value: "Generate a knowledge-grounded resolution for a ticket",
|
|
149
|
+
description: "MCP tool description for ticket resolution"
|
|
150
|
+
},
|
|
151
|
+
"tool.draft.title": {
|
|
152
|
+
value: "support_draft_response",
|
|
153
|
+
description: "MCP tool title for response drafting"
|
|
154
|
+
},
|
|
155
|
+
"tool.draft.description": {
|
|
156
|
+
value: "Draft a user-facing reply based on resolution + classification",
|
|
157
|
+
description: "MCP tool description for response drafting"
|
|
158
|
+
},
|
|
159
|
+
"error.inputMustIncludeTicket": {
|
|
160
|
+
value: "Input must include ticket",
|
|
161
|
+
description: "Error when input payload is missing the ticket field"
|
|
162
|
+
},
|
|
163
|
+
"error.ticketMissingId": {
|
|
164
|
+
value: "Ticket is missing id",
|
|
165
|
+
description: "Error when ticket object lacks an id"
|
|
166
|
+
},
|
|
167
|
+
"error.resolutionClassificationRequired": {
|
|
168
|
+
value: "resolution and classification are required",
|
|
169
|
+
description: "Error when draft endpoint is called without resolution and classification"
|
|
170
|
+
},
|
|
171
|
+
"feedback.noRecords": {
|
|
172
|
+
value: "No feedback recorded yet.",
|
|
173
|
+
description: "Placeholder when no feedback entries exist"
|
|
174
|
+
},
|
|
175
|
+
"feedback.status.escalated": {
|
|
176
|
+
value: "Escalated",
|
|
177
|
+
description: "Status label for escalated tickets"
|
|
178
|
+
},
|
|
179
|
+
"feedback.status.autoResolved": {
|
|
180
|
+
value: "Auto-resolved",
|
|
181
|
+
description: "Status label for auto-resolved tickets"
|
|
182
|
+
},
|
|
183
|
+
"spec.instructionsAppendix": {
|
|
184
|
+
value: "Always cite support knowledge sources and flag compliance/billing issues for human review when unsure.",
|
|
185
|
+
description: "Instructions appendix appended to agent spec"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// src/i18n/catalogs/fr.ts
|
|
191
|
+
import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
|
|
192
|
+
var frMessages = defineTranslation2({
|
|
193
|
+
meta: {
|
|
194
|
+
key: "support-bot.messages",
|
|
195
|
+
version: "1.0.0",
|
|
196
|
+
domain: "support-bot",
|
|
197
|
+
description: "French translations for the support-bot package",
|
|
198
|
+
owners: ["platform"],
|
|
199
|
+
stability: "experimental"
|
|
200
|
+
},
|
|
201
|
+
locale: "fr",
|
|
202
|
+
fallback: "en",
|
|
203
|
+
messages: {
|
|
204
|
+
"prompt.classifier.system": {
|
|
205
|
+
value: "Classifiez le ticket de support.",
|
|
206
|
+
description: "Classifier LLM system prompt"
|
|
207
|
+
},
|
|
208
|
+
"prompt.autoResponder.system": {
|
|
209
|
+
value: "Rédigez des réponses de support empathiques et précises en citant les sources lorsque c’est pertinent.",
|
|
210
|
+
description: "Auto-responder LLM system prompt"
|
|
211
|
+
},
|
|
212
|
+
"prompt.autoResponder.user": {
|
|
213
|
+
value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
|
|
214
|
+
Sujet : {subject}
|
|
215
|
+
|
|
216
|
+
{body}
|
|
217
|
+
|
|
218
|
+
Connaissances :
|
|
219
|
+
{knowledge}
|
|
220
|
+
|
|
221
|
+
Répondez au client.`,
|
|
222
|
+
description: "Auto-responder user prompt template",
|
|
223
|
+
placeholders: [
|
|
224
|
+
{ name: "ticketId", type: "string" },
|
|
225
|
+
{ name: "category", type: "string" },
|
|
226
|
+
{ name: "priority", type: "string" },
|
|
227
|
+
{ name: "sentiment", type: "string" },
|
|
228
|
+
{ name: "subject", type: "string" },
|
|
229
|
+
{ name: "body", type: "string" },
|
|
230
|
+
{ name: "knowledge", type: "string" }
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
"responder.closing.friendly": {
|
|
234
|
+
value: "Nous restons à votre disposition si vous avez besoin de quoi que ce soit.",
|
|
235
|
+
description: "Friendly closing line for support responses"
|
|
236
|
+
},
|
|
237
|
+
"responder.closing.formal": {
|
|
238
|
+
value: "Veuillez nous contacter si vous avez besoin d’une assistance supplémentaire.",
|
|
239
|
+
description: "Formal closing line for support responses"
|
|
240
|
+
},
|
|
241
|
+
"responder.greeting.named": {
|
|
242
|
+
value: "Bonjour {name},",
|
|
243
|
+
description: "Greeting with customer name",
|
|
244
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
245
|
+
},
|
|
246
|
+
"responder.greeting.anonymous": {
|
|
247
|
+
value: "Bonjour,",
|
|
248
|
+
description: "Greeting without customer name"
|
|
249
|
+
},
|
|
250
|
+
"responder.intro.thanks": {
|
|
251
|
+
value: "Merci de nous avoir contactés au sujet de « {subject} ».",
|
|
252
|
+
description: "Thank-you intro referencing the ticket subject",
|
|
253
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
254
|
+
},
|
|
255
|
+
"responder.signature": {
|
|
256
|
+
value: "— Support ContractSpec",
|
|
257
|
+
description: "Email / response signature"
|
|
258
|
+
},
|
|
259
|
+
"responder.references.header": {
|
|
260
|
+
value: "Références :",
|
|
261
|
+
description: "Header for the references section"
|
|
262
|
+
},
|
|
263
|
+
"responder.references.sourceLabel": {
|
|
264
|
+
value: "Source {index}",
|
|
265
|
+
description: "Label for a numbered source reference",
|
|
266
|
+
placeholders: [{ name: "index", type: "number" }]
|
|
267
|
+
},
|
|
268
|
+
"responder.category.billing": {
|
|
269
|
+
value: "Je comprends que les problèmes de facturation peuvent être stressants, laissez-moi clarifier la situation.",
|
|
270
|
+
description: "Category intro for billing tickets"
|
|
271
|
+
},
|
|
272
|
+
"responder.category.technical": {
|
|
273
|
+
value: "Je vois que vous avez rencontré un problème technique. Voici ce qui s’est passé et comment le résoudre.",
|
|
274
|
+
description: "Category intro for technical tickets"
|
|
275
|
+
},
|
|
276
|
+
"responder.category.product": {
|
|
277
|
+
value: "Merci d’avoir partagé vos retours sur le produit. Voici les prochaines étapes.",
|
|
278
|
+
description: "Category intro for product tickets"
|
|
279
|
+
},
|
|
280
|
+
"responder.category.account": {
|
|
281
|
+
value: "L’accès au compte est essentiel, laissez-moi vous guider vers la résolution.",
|
|
282
|
+
description: "Category intro for account tickets"
|
|
283
|
+
},
|
|
284
|
+
"responder.category.compliance": {
|
|
285
|
+
value: "Les questions de conformité exigent de la précision. Consultez la réponse conforme aux politiques ci-dessous.",
|
|
286
|
+
description: "Category intro for compliance tickets"
|
|
287
|
+
},
|
|
288
|
+
"responder.category.other": {
|
|
289
|
+
value: "Voici ce que nous avons trouvé après examen de votre demande.",
|
|
290
|
+
description: "Category intro for uncategorized tickets"
|
|
291
|
+
},
|
|
292
|
+
"responder.subject.replyPrefix": {
|
|
293
|
+
value: "Re : {subject}",
|
|
294
|
+
description: "Reply subject line prefix",
|
|
295
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
296
|
+
},
|
|
297
|
+
"resolver.question.subjectLabel": {
|
|
298
|
+
value: "Sujet : {subject}",
|
|
299
|
+
description: "Subject label in resolver question context",
|
|
300
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
301
|
+
},
|
|
302
|
+
"resolver.question.channelLabel": {
|
|
303
|
+
value: "Canal : {channel}",
|
|
304
|
+
description: "Channel label in resolver question context",
|
|
305
|
+
placeholders: [{ name: "channel", type: "string" }]
|
|
306
|
+
},
|
|
307
|
+
"resolver.question.customerLabel": {
|
|
308
|
+
value: "Client : {name}",
|
|
309
|
+
description: "Customer label in resolver question context",
|
|
310
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
311
|
+
},
|
|
312
|
+
"resolver.action.escalate": {
|
|
313
|
+
value: "Escalader pour révision humaine",
|
|
314
|
+
description: "Action label for escalation"
|
|
315
|
+
},
|
|
316
|
+
"resolver.action.respond": {
|
|
317
|
+
value: "Envoyer une réponse automatique",
|
|
318
|
+
description: "Action label for automated response"
|
|
319
|
+
},
|
|
320
|
+
"resolver.escalation.insufficientConfidence": {
|
|
321
|
+
value: "Confiance insuffisante ou références de connaissances manquantes",
|
|
322
|
+
description: "Escalation reason when confidence is too low"
|
|
323
|
+
},
|
|
324
|
+
"tool.classify.title": {
|
|
325
|
+
value: "support_classify_ticket",
|
|
326
|
+
description: "MCP tool title for ticket classification"
|
|
327
|
+
},
|
|
328
|
+
"tool.classify.description": {
|
|
329
|
+
value: "Classifier un ticket par priorité, sentiment et catégorie",
|
|
330
|
+
description: "MCP tool description for ticket classification"
|
|
331
|
+
},
|
|
332
|
+
"tool.resolve.title": {
|
|
333
|
+
value: "support_resolve_ticket",
|
|
334
|
+
description: "MCP tool title for ticket resolution"
|
|
335
|
+
},
|
|
336
|
+
"tool.resolve.description": {
|
|
337
|
+
value: "Générer une résolution fondée sur la base de connaissances pour un ticket",
|
|
338
|
+
description: "MCP tool description for ticket resolution"
|
|
339
|
+
},
|
|
340
|
+
"tool.draft.title": {
|
|
341
|
+
value: "support_draft_response",
|
|
342
|
+
description: "MCP tool title for response drafting"
|
|
343
|
+
},
|
|
344
|
+
"tool.draft.description": {
|
|
345
|
+
value: "Rédiger une réponse client basée sur la résolution et la classification",
|
|
346
|
+
description: "MCP tool description for response drafting"
|
|
347
|
+
},
|
|
348
|
+
"error.inputMustIncludeTicket": {
|
|
349
|
+
value: "L’entrée doit inclure un ticket",
|
|
350
|
+
description: "Error when input payload is missing the ticket field"
|
|
351
|
+
},
|
|
352
|
+
"error.ticketMissingId": {
|
|
353
|
+
value: "Le ticket n’a pas d’identifiant",
|
|
354
|
+
description: "Error when ticket object lacks an id"
|
|
355
|
+
},
|
|
356
|
+
"error.resolutionClassificationRequired": {
|
|
357
|
+
value: "la résolution et la classification sont requises",
|
|
358
|
+
description: "Error when draft endpoint is called without resolution and classification"
|
|
359
|
+
},
|
|
360
|
+
"feedback.noRecords": {
|
|
361
|
+
value: "Aucun retour enregistré pour le moment.",
|
|
362
|
+
description: "Placeholder when no feedback entries exist"
|
|
363
|
+
},
|
|
364
|
+
"feedback.status.escalated": {
|
|
365
|
+
value: "Escaladé",
|
|
366
|
+
description: "Status label for escalated tickets"
|
|
367
|
+
},
|
|
368
|
+
"feedback.status.autoResolved": {
|
|
369
|
+
value: "Résolu automatiquement",
|
|
370
|
+
description: "Status label for auto-resolved tickets"
|
|
371
|
+
},
|
|
372
|
+
"spec.instructionsAppendix": {
|
|
373
|
+
value: "Citez toujours les sources de connaissances du support et signalez les problèmes de conformité/facturation pour révision humaine en cas de doute.",
|
|
374
|
+
description: "Instructions appendix appended to agent spec"
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// src/i18n/catalogs/es.ts
|
|
380
|
+
import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
|
|
381
|
+
var esMessages = defineTranslation3({
|
|
382
|
+
meta: {
|
|
383
|
+
key: "support-bot.messages",
|
|
384
|
+
version: "1.0.0",
|
|
385
|
+
domain: "support-bot",
|
|
386
|
+
description: "Spanish translations for the support-bot package",
|
|
387
|
+
owners: ["platform"],
|
|
388
|
+
stability: "experimental"
|
|
389
|
+
},
|
|
390
|
+
locale: "es",
|
|
391
|
+
fallback: "en",
|
|
392
|
+
messages: {
|
|
393
|
+
"prompt.classifier.system": {
|
|
394
|
+
value: "Clasifica el ticket de soporte.",
|
|
395
|
+
description: "Classifier LLM system prompt"
|
|
396
|
+
},
|
|
397
|
+
"prompt.autoResponder.system": {
|
|
398
|
+
value: "Redacta respuestas de soporte empáticas y precisas citando fuentes cuando sea relevante.",
|
|
399
|
+
description: "Auto-responder LLM system prompt"
|
|
400
|
+
},
|
|
401
|
+
"prompt.autoResponder.user": {
|
|
402
|
+
value: `Ticket #{ticketId} ({category}, {priority}, {sentiment})
|
|
403
|
+
Asunto: {subject}
|
|
404
|
+
|
|
405
|
+
{body}
|
|
406
|
+
|
|
407
|
+
Conocimientos:
|
|
408
|
+
{knowledge}
|
|
409
|
+
|
|
410
|
+
Responde al cliente.`,
|
|
411
|
+
description: "Auto-responder user prompt template",
|
|
412
|
+
placeholders: [
|
|
413
|
+
{ name: "ticketId", type: "string" },
|
|
414
|
+
{ name: "category", type: "string" },
|
|
415
|
+
{ name: "priority", type: "string" },
|
|
416
|
+
{ name: "sentiment", type: "string" },
|
|
417
|
+
{ name: "subject", type: "string" },
|
|
418
|
+
{ name: "body", type: "string" },
|
|
419
|
+
{ name: "knowledge", type: "string" }
|
|
420
|
+
]
|
|
421
|
+
},
|
|
422
|
+
"responder.closing.friendly": {
|
|
423
|
+
value: "Quedamos a su disposición si necesita cualquier otra cosa.",
|
|
424
|
+
description: "Friendly closing line for support responses"
|
|
425
|
+
},
|
|
426
|
+
"responder.closing.formal": {
|
|
427
|
+
value: "No dude en contactarnos si requiere asistencia adicional.",
|
|
428
|
+
description: "Formal closing line for support responses"
|
|
429
|
+
},
|
|
430
|
+
"responder.greeting.named": {
|
|
431
|
+
value: "Hola {name},",
|
|
432
|
+
description: "Greeting with customer name",
|
|
433
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
434
|
+
},
|
|
435
|
+
"responder.greeting.anonymous": {
|
|
436
|
+
value: "Hola,",
|
|
437
|
+
description: "Greeting without customer name"
|
|
438
|
+
},
|
|
439
|
+
"responder.intro.thanks": {
|
|
440
|
+
value: "Gracias por contactarnos sobre «{subject}».",
|
|
441
|
+
description: "Thank-you intro referencing the ticket subject",
|
|
442
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
443
|
+
},
|
|
444
|
+
"responder.signature": {
|
|
445
|
+
value: "— Soporte ContractSpec",
|
|
446
|
+
description: "Email / response signature"
|
|
447
|
+
},
|
|
448
|
+
"responder.references.header": {
|
|
449
|
+
value: "Referencias:",
|
|
450
|
+
description: "Header for the references section"
|
|
451
|
+
},
|
|
452
|
+
"responder.references.sourceLabel": {
|
|
453
|
+
value: "Fuente {index}",
|
|
454
|
+
description: "Label for a numbered source reference",
|
|
455
|
+
placeholders: [{ name: "index", type: "number" }]
|
|
456
|
+
},
|
|
457
|
+
"responder.category.billing": {
|
|
458
|
+
value: "Entiendo que los problemas de facturación pueden ser estresantes, permítame aclarar la situación.",
|
|
459
|
+
description: "Category intro for billing tickets"
|
|
460
|
+
},
|
|
461
|
+
"responder.category.technical": {
|
|
462
|
+
value: "Veo que encontró un problema técnico. Esto es lo que sucedió y cómo solucionarlo.",
|
|
463
|
+
description: "Category intro for technical tickets"
|
|
464
|
+
},
|
|
465
|
+
"responder.category.product": {
|
|
466
|
+
value: "Gracias por compartir sus comentarios sobre el producto. Estos son los próximos pasos.",
|
|
467
|
+
description: "Category intro for product tickets"
|
|
468
|
+
},
|
|
469
|
+
"responder.category.account": {
|
|
470
|
+
value: "El acceso a la cuenta es fundamental, permítame guiarle hacia la resolución.",
|
|
471
|
+
description: "Category intro for account tickets"
|
|
472
|
+
},
|
|
473
|
+
"responder.category.compliance": {
|
|
474
|
+
value: "Las preguntas de cumplimiento requieren precisión. Consulte la respuesta alineada con las políticas a continuación.",
|
|
475
|
+
description: "Category intro for compliance tickets"
|
|
476
|
+
},
|
|
477
|
+
"responder.category.other": {
|
|
478
|
+
value: "Esto es lo que encontramos tras revisar su solicitud.",
|
|
479
|
+
description: "Category intro for uncategorized tickets"
|
|
480
|
+
},
|
|
481
|
+
"responder.subject.replyPrefix": {
|
|
482
|
+
value: "Re: {subject}",
|
|
483
|
+
description: "Reply subject line prefix",
|
|
484
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
485
|
+
},
|
|
486
|
+
"resolver.question.subjectLabel": {
|
|
487
|
+
value: "Asunto: {subject}",
|
|
488
|
+
description: "Subject label in resolver question context",
|
|
489
|
+
placeholders: [{ name: "subject", type: "string" }]
|
|
490
|
+
},
|
|
491
|
+
"resolver.question.channelLabel": {
|
|
492
|
+
value: "Canal: {channel}",
|
|
493
|
+
description: "Channel label in resolver question context",
|
|
494
|
+
placeholders: [{ name: "channel", type: "string" }]
|
|
495
|
+
},
|
|
496
|
+
"resolver.question.customerLabel": {
|
|
497
|
+
value: "Cliente: {name}",
|
|
498
|
+
description: "Customer label in resolver question context",
|
|
499
|
+
placeholders: [{ name: "name", type: "string" }]
|
|
500
|
+
},
|
|
501
|
+
"resolver.action.escalate": {
|
|
502
|
+
value: "Escalar para revisión humana",
|
|
503
|
+
description: "Action label for escalation"
|
|
504
|
+
},
|
|
505
|
+
"resolver.action.respond": {
|
|
506
|
+
value: "Enviar respuesta automática",
|
|
507
|
+
description: "Action label for automated response"
|
|
508
|
+
},
|
|
509
|
+
"resolver.escalation.insufficientConfidence": {
|
|
510
|
+
value: "Confianza insuficiente o referencias de conocimiento faltantes",
|
|
511
|
+
description: "Escalation reason when confidence is too low"
|
|
512
|
+
},
|
|
513
|
+
"tool.classify.title": {
|
|
514
|
+
value: "support_classify_ticket",
|
|
515
|
+
description: "MCP tool title for ticket classification"
|
|
516
|
+
},
|
|
517
|
+
"tool.classify.description": {
|
|
518
|
+
value: "Clasificar un ticket por prioridad, sentimiento y categoría",
|
|
519
|
+
description: "MCP tool description for ticket classification"
|
|
520
|
+
},
|
|
521
|
+
"tool.resolve.title": {
|
|
522
|
+
value: "support_resolve_ticket",
|
|
523
|
+
description: "MCP tool title for ticket resolution"
|
|
524
|
+
},
|
|
525
|
+
"tool.resolve.description": {
|
|
526
|
+
value: "Generar una resolución basada en conocimientos para un ticket",
|
|
527
|
+
description: "MCP tool description for ticket resolution"
|
|
528
|
+
},
|
|
529
|
+
"tool.draft.title": {
|
|
530
|
+
value: "support_draft_response",
|
|
531
|
+
description: "MCP tool title for response drafting"
|
|
532
|
+
},
|
|
533
|
+
"tool.draft.description": {
|
|
534
|
+
value: "Redactar una respuesta al cliente basada en la resolución y la clasificación",
|
|
535
|
+
description: "MCP tool description for response drafting"
|
|
536
|
+
},
|
|
537
|
+
"error.inputMustIncludeTicket": {
|
|
538
|
+
value: "La entrada debe incluir un ticket",
|
|
539
|
+
description: "Error when input payload is missing the ticket field"
|
|
540
|
+
},
|
|
541
|
+
"error.ticketMissingId": {
|
|
542
|
+
value: "El ticket no tiene identificador",
|
|
543
|
+
description: "Error when ticket object lacks an id"
|
|
544
|
+
},
|
|
545
|
+
"error.resolutionClassificationRequired": {
|
|
546
|
+
value: "la resolución y la clasificación son obligatorias",
|
|
547
|
+
description: "Error when draft endpoint is called without resolution and classification"
|
|
548
|
+
},
|
|
549
|
+
"feedback.noRecords": {
|
|
550
|
+
value: "No hay comentarios registrados aún.",
|
|
551
|
+
description: "Placeholder when no feedback entries exist"
|
|
552
|
+
},
|
|
553
|
+
"feedback.status.escalated": {
|
|
554
|
+
value: "Escalado",
|
|
555
|
+
description: "Status label for escalated tickets"
|
|
556
|
+
},
|
|
557
|
+
"feedback.status.autoResolved": {
|
|
558
|
+
value: "Resuelto automáticamente",
|
|
559
|
+
description: "Status label for auto-resolved tickets"
|
|
560
|
+
},
|
|
561
|
+
"spec.instructionsAppendix": {
|
|
562
|
+
value: "Cite siempre las fuentes de conocimiento de soporte y señale los problemas de cumplimiento/facturación para revisión humana en caso de duda.",
|
|
563
|
+
description: "Instructions appendix appended to agent spec"
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// src/i18n/messages.ts
|
|
569
|
+
import {
|
|
570
|
+
createI18nFactory
|
|
571
|
+
} from "@contractspec/lib.contracts-spec/translations";
|
|
572
|
+
import { interpolate } from "@contractspec/lib.contracts-spec/translations";
|
|
573
|
+
var factory = createI18nFactory({
|
|
574
|
+
specKey: "support-bot.messages",
|
|
575
|
+
catalogs: [enMessages, frMessages, esMessages]
|
|
576
|
+
});
|
|
577
|
+
var createSupportBotI18n = factory.create;
|
|
578
|
+
var getDefaultI18n = factory.getDefault;
|
|
579
|
+
var resetI18nRegistry = factory.resetRegistry;
|
|
580
|
+
|
|
581
|
+
// src/i18n/locale.ts
|
|
582
|
+
import {
|
|
583
|
+
DEFAULT_LOCALE,
|
|
584
|
+
SUPPORTED_LOCALES,
|
|
585
|
+
resolveLocale,
|
|
586
|
+
isSupportedLocale
|
|
587
|
+
} from "@contractspec/lib.contracts-spec/translations";
|
|
588
|
+
|
|
589
|
+
// src/i18n/keys.ts
|
|
590
|
+
var PROMPT_KEYS = {
|
|
591
|
+
"prompt.classifier.system": "prompt.classifier.system",
|
|
592
|
+
"prompt.autoResponder.system": "prompt.autoResponder.system",
|
|
593
|
+
"prompt.autoResponder.user": "prompt.autoResponder.user"
|
|
594
|
+
};
|
|
595
|
+
var RESPONDER_KEYS = {
|
|
596
|
+
"responder.closing.friendly": "responder.closing.friendly",
|
|
597
|
+
"responder.closing.formal": "responder.closing.formal",
|
|
598
|
+
"responder.greeting.named": "responder.greeting.named",
|
|
599
|
+
"responder.greeting.anonymous": "responder.greeting.anonymous",
|
|
600
|
+
"responder.intro.thanks": "responder.intro.thanks",
|
|
601
|
+
"responder.signature": "responder.signature",
|
|
602
|
+
"responder.references.header": "responder.references.header",
|
|
603
|
+
"responder.references.sourceLabel": "responder.references.sourceLabel",
|
|
604
|
+
"responder.category.billing": "responder.category.billing",
|
|
605
|
+
"responder.category.technical": "responder.category.technical",
|
|
606
|
+
"responder.category.product": "responder.category.product",
|
|
607
|
+
"responder.category.account": "responder.category.account",
|
|
608
|
+
"responder.category.compliance": "responder.category.compliance",
|
|
609
|
+
"responder.category.other": "responder.category.other",
|
|
610
|
+
"responder.subject.replyPrefix": "responder.subject.replyPrefix"
|
|
611
|
+
};
|
|
612
|
+
var RESOLVER_KEYS = {
|
|
613
|
+
"resolver.question.subjectLabel": "resolver.question.subjectLabel",
|
|
614
|
+
"resolver.question.channelLabel": "resolver.question.channelLabel",
|
|
615
|
+
"resolver.question.customerLabel": "resolver.question.customerLabel",
|
|
616
|
+
"resolver.action.escalate": "resolver.action.escalate",
|
|
617
|
+
"resolver.action.respond": "resolver.action.respond",
|
|
618
|
+
"resolver.escalation.insufficientConfidence": "resolver.escalation.insufficientConfidence"
|
|
619
|
+
};
|
|
620
|
+
var TOOL_KEYS = {
|
|
621
|
+
"tool.classify.title": "tool.classify.title",
|
|
622
|
+
"tool.classify.description": "tool.classify.description",
|
|
623
|
+
"tool.resolve.title": "tool.resolve.title",
|
|
624
|
+
"tool.resolve.description": "tool.resolve.description",
|
|
625
|
+
"tool.draft.title": "tool.draft.title",
|
|
626
|
+
"tool.draft.description": "tool.draft.description"
|
|
627
|
+
};
|
|
628
|
+
var ERROR_KEYS = {
|
|
629
|
+
"error.inputMustIncludeTicket": "error.inputMustIncludeTicket",
|
|
630
|
+
"error.ticketMissingId": "error.ticketMissingId",
|
|
631
|
+
"error.resolutionClassificationRequired": "error.resolutionClassificationRequired"
|
|
632
|
+
};
|
|
633
|
+
var FEEDBACK_KEYS = {
|
|
634
|
+
"feedback.noRecords": "feedback.noRecords",
|
|
635
|
+
"feedback.status.escalated": "feedback.status.escalated",
|
|
636
|
+
"feedback.status.autoResolved": "feedback.status.autoResolved"
|
|
637
|
+
};
|
|
638
|
+
var SPEC_KEYS = {
|
|
639
|
+
"spec.instructionsAppendix": "spec.instructionsAppendix"
|
|
640
|
+
};
|
|
641
|
+
var I18N_KEYS = {
|
|
642
|
+
...PROMPT_KEYS,
|
|
643
|
+
...RESPONDER_KEYS,
|
|
644
|
+
...RESOLVER_KEYS,
|
|
645
|
+
...TOOL_KEYS,
|
|
646
|
+
...ERROR_KEYS,
|
|
647
|
+
...FEEDBACK_KEYS,
|
|
648
|
+
...SPEC_KEYS
|
|
649
|
+
};
|
|
1
650
|
// src/bot/auto-responder.ts
|
|
2
651
|
class AutoResponder {
|
|
3
652
|
llm;
|
|
4
653
|
model;
|
|
5
654
|
tone;
|
|
6
655
|
closing;
|
|
656
|
+
i18n;
|
|
7
657
|
constructor(options) {
|
|
8
658
|
this.llm = options?.llm;
|
|
9
659
|
this.model = options?.model;
|
|
10
660
|
this.tone = options?.tone ?? "friendly";
|
|
11
|
-
this.
|
|
661
|
+
this.i18n = createSupportBotI18n(options?.locale);
|
|
662
|
+
const { t } = this.i18n;
|
|
663
|
+
this.closing = options?.closing ?? (this.tone === "friendly" ? t("responder.closing.friendly") : t("responder.closing.formal"));
|
|
12
664
|
}
|
|
13
665
|
async draft(ticket, resolution, classification) {
|
|
14
666
|
if (this.llm) {
|
|
@@ -17,21 +669,23 @@ class AutoResponder {
|
|
|
17
669
|
return this.generateTemplate(ticket, resolution, classification);
|
|
18
670
|
}
|
|
19
671
|
async generateWithLLM(ticket, resolution, classification) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
672
|
+
const { t } = this.i18n;
|
|
673
|
+
const prompt = t("prompt.autoResponder.user", {
|
|
674
|
+
tone: this.tone,
|
|
675
|
+
subject: ticket.subject,
|
|
676
|
+
body: ticket.body,
|
|
677
|
+
category: classification.category,
|
|
678
|
+
priority: classification.priority,
|
|
679
|
+
answer: resolution.answer,
|
|
680
|
+
citations: resolution.citations.map((c) => c.label).join(", ")
|
|
681
|
+
});
|
|
28
682
|
const response = await this.llm.chat([
|
|
29
683
|
{
|
|
30
684
|
role: "system",
|
|
31
685
|
content: [
|
|
32
686
|
{
|
|
33
687
|
type: "text",
|
|
34
|
-
text: "
|
|
688
|
+
text: t("prompt.autoResponder.system")
|
|
35
689
|
}
|
|
36
690
|
]
|
|
37
691
|
},
|
|
@@ -44,17 +698,18 @@ Citations: ${resolution.citations.map((c) => c.label).join(", ")}`;
|
|
|
44
698
|
return this.buildDraft(ticket, resolution, classification, body);
|
|
45
699
|
}
|
|
46
700
|
generateTemplate(ticket, resolution, classification) {
|
|
47
|
-
const
|
|
701
|
+
const { t } = this.i18n;
|
|
702
|
+
const greeting = ticket.customerName ? t("responder.greeting.named", { name: ticket.customerName }) : t("responder.greeting.anonymous");
|
|
48
703
|
const body = `${greeting}
|
|
49
704
|
|
|
50
|
-
|
|
705
|
+
${t("responder.intro.thanks", { subject: ticket.subject })} ${this.renderCategoryIntro(classification)}
|
|
51
706
|
|
|
52
707
|
${resolution.answer}
|
|
53
708
|
|
|
54
709
|
${this.renderCitations(resolution)}
|
|
55
710
|
${this.closing}
|
|
56
711
|
|
|
57
|
-
|
|
712
|
+
${t("responder.signature")}`;
|
|
58
713
|
return this.buildDraft(ticket, resolution, classification, body);
|
|
59
714
|
}
|
|
60
715
|
buildDraft(ticket, resolution, classification, body) {
|
|
@@ -68,30 +723,32 @@ ${this.closing}
|
|
|
68
723
|
};
|
|
69
724
|
}
|
|
70
725
|
renderCategoryIntro(classification) {
|
|
726
|
+
const { t } = this.i18n;
|
|
71
727
|
switch (classification.category) {
|
|
72
728
|
case "billing":
|
|
73
|
-
return "
|
|
729
|
+
return t("responder.category.billing");
|
|
74
730
|
case "technical":
|
|
75
|
-
return "
|
|
731
|
+
return t("responder.category.technical");
|
|
76
732
|
case "product":
|
|
77
|
-
return "
|
|
733
|
+
return t("responder.category.product");
|
|
78
734
|
case "account":
|
|
79
|
-
return "
|
|
735
|
+
return t("responder.category.account");
|
|
80
736
|
case "compliance":
|
|
81
|
-
return "
|
|
737
|
+
return t("responder.category.compliance");
|
|
82
738
|
default:
|
|
83
|
-
return "
|
|
739
|
+
return t("responder.category.other");
|
|
84
740
|
}
|
|
85
741
|
}
|
|
86
742
|
renderCitations(resolution) {
|
|
743
|
+
const { t } = this.i18n;
|
|
87
744
|
if (!resolution.citations.length)
|
|
88
745
|
return "";
|
|
89
746
|
const lines = resolution.citations.map((citation, index) => {
|
|
90
|
-
const label = citation.label ||
|
|
747
|
+
const label = citation.label || t("responder.references.sourceLabel", { index: index + 1 });
|
|
91
748
|
const link = citation.url ? ` (${citation.url})` : "";
|
|
92
749
|
return `- ${label}${link}`;
|
|
93
750
|
});
|
|
94
|
-
return
|
|
751
|
+
return `${t("responder.references.header")}
|
|
95
752
|
${lines.join(`
|
|
96
753
|
`)}`;
|
|
97
754
|
}
|
|
@@ -122,11 +779,12 @@ class SupportFeedbackLoop {
|
|
|
122
779
|
};
|
|
123
780
|
}
|
|
124
781
|
feedbackSummary(limit = 5) {
|
|
782
|
+
const { t } = getDefaultI18n();
|
|
125
783
|
const recent = this.history.slice(-limit);
|
|
126
784
|
if (!recent.length)
|
|
127
|
-
return "
|
|
785
|
+
return t("feedback.noRecords");
|
|
128
786
|
return recent.map((entry) => {
|
|
129
|
-
const status = entry.resolution.actions.some((action) => action.type === "escalate") ? "
|
|
787
|
+
const status = entry.resolution.actions.some((action) => action.type === "escalate") ? t("feedback.status.escalated") : t("feedback.status.autoResolved");
|
|
130
788
|
return `${entry.ticket.subject} – ${status} (confidence: ${entry.resolution.confidence})`;
|
|
131
789
|
}).join(`
|
|
132
790
|
`);
|
|
@@ -297,10 +955,12 @@ class TicketResolver {
|
|
|
297
955
|
knowledge;
|
|
298
956
|
minConfidence;
|
|
299
957
|
prependPrompt;
|
|
958
|
+
i18n;
|
|
300
959
|
constructor(options) {
|
|
301
960
|
this.knowledge = options.knowledge;
|
|
302
961
|
this.minConfidence = options.minConfidence ?? 0.65;
|
|
303
962
|
this.prependPrompt = options.prependPrompt;
|
|
963
|
+
this.i18n = createSupportBotI18n(options.locale);
|
|
304
964
|
}
|
|
305
965
|
async resolve(ticket) {
|
|
306
966
|
const question = this.buildQuestion(ticket);
|
|
@@ -308,9 +968,14 @@ class TicketResolver {
|
|
|
308
968
|
return this.toResolution(ticket, answer);
|
|
309
969
|
}
|
|
310
970
|
buildQuestion(ticket) {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
971
|
+
const { t } = this.i18n;
|
|
972
|
+
const header = [
|
|
973
|
+
t("resolver.question.subjectLabel", { subject: ticket.subject }),
|
|
974
|
+
t("resolver.question.channelLabel", { channel: ticket.channel })
|
|
975
|
+
];
|
|
976
|
+
if (ticket.customerName) {
|
|
977
|
+
header.push(t("resolver.question.customerLabel", { name: ticket.customerName }));
|
|
978
|
+
}
|
|
314
979
|
const sections = [
|
|
315
980
|
this.prependPrompt,
|
|
316
981
|
header.join(`
|
|
@@ -331,6 +996,7 @@ class TicketResolver {
|
|
|
331
996
|
score: ref.score
|
|
332
997
|
};
|
|
333
998
|
});
|
|
999
|
+
const { t } = this.i18n;
|
|
334
1000
|
const confidence = this.deriveConfidence(answer);
|
|
335
1001
|
const escalate = confidence < this.minConfidence || citations.length === 0;
|
|
336
1002
|
return {
|
|
@@ -339,9 +1005,9 @@ class TicketResolver {
|
|
|
339
1005
|
confidence,
|
|
340
1006
|
citations,
|
|
341
1007
|
actions: [
|
|
342
|
-
escalate ? { type: "escalate", label: "
|
|
1008
|
+
escalate ? { type: "escalate", label: t("resolver.action.escalate") } : { type: "respond", label: t("resolver.action.respond") }
|
|
343
1009
|
],
|
|
344
|
-
escalationReason: escalate ? "
|
|
1010
|
+
escalationReason: escalate ? t("resolver.escalation.insufficientConfidence") : undefined,
|
|
345
1011
|
knowledgeUpdates: escalate ? [ticket.body.slice(0, 200)] : undefined
|
|
346
1012
|
};
|
|
347
1013
|
}
|
|
@@ -356,35 +1022,193 @@ class TicketResolver {
|
|
|
356
1022
|
}
|
|
357
1023
|
// src/tickets/classifier.ts
|
|
358
1024
|
var CATEGORY_KEYWORDS = {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1025
|
+
en: {
|
|
1026
|
+
billing: ["invoice", "payout", "refund", "charge", "billing", "payment"],
|
|
1027
|
+
technical: ["bug", "error", "crash", "issue", "failed", "timeout"],
|
|
1028
|
+
product: ["feature", "roadmap", "idea", "request", "feedback"],
|
|
1029
|
+
account: ["login", "password", "2fa", "account", "profile", "email change"],
|
|
1030
|
+
compliance: ["kyc", "aml", "compliance", "regulation", "gdpr"],
|
|
1031
|
+
other: []
|
|
1032
|
+
},
|
|
1033
|
+
fr: {
|
|
1034
|
+
billing: [
|
|
1035
|
+
"facture",
|
|
1036
|
+
"versement",
|
|
1037
|
+
"remboursement",
|
|
1038
|
+
"frais",
|
|
1039
|
+
"facturation",
|
|
1040
|
+
"paiement"
|
|
1041
|
+
],
|
|
1042
|
+
technical: [
|
|
1043
|
+
"bogue",
|
|
1044
|
+
"erreur",
|
|
1045
|
+
"plantage",
|
|
1046
|
+
"problème",
|
|
1047
|
+
"échoué",
|
|
1048
|
+
"délai dépassé"
|
|
1049
|
+
],
|
|
1050
|
+
product: [
|
|
1051
|
+
"fonctionnalité",
|
|
1052
|
+
"feuille de route",
|
|
1053
|
+
"idée",
|
|
1054
|
+
"demande",
|
|
1055
|
+
"retour"
|
|
1056
|
+
],
|
|
1057
|
+
account: [
|
|
1058
|
+
"connexion",
|
|
1059
|
+
"mot de passe",
|
|
1060
|
+
"2fa",
|
|
1061
|
+
"compte",
|
|
1062
|
+
"profil",
|
|
1063
|
+
"changement email"
|
|
1064
|
+
],
|
|
1065
|
+
compliance: ["kyc", "aml", "conformité", "réglementation", "rgpd"],
|
|
1066
|
+
other: []
|
|
1067
|
+
},
|
|
1068
|
+
es: {
|
|
1069
|
+
billing: [
|
|
1070
|
+
"factura",
|
|
1071
|
+
"desembolso",
|
|
1072
|
+
"reembolso",
|
|
1073
|
+
"cargo",
|
|
1074
|
+
"facturación",
|
|
1075
|
+
"pago"
|
|
1076
|
+
],
|
|
1077
|
+
technical: [
|
|
1078
|
+
"error",
|
|
1079
|
+
"fallo",
|
|
1080
|
+
"caída",
|
|
1081
|
+
"problema",
|
|
1082
|
+
"fallido",
|
|
1083
|
+
"tiempo agotado"
|
|
1084
|
+
],
|
|
1085
|
+
product: [
|
|
1086
|
+
"funcionalidad",
|
|
1087
|
+
"hoja de ruta",
|
|
1088
|
+
"idea",
|
|
1089
|
+
"solicitud",
|
|
1090
|
+
"comentario"
|
|
1091
|
+
],
|
|
1092
|
+
account: [
|
|
1093
|
+
"inicio de sesión",
|
|
1094
|
+
"contraseña",
|
|
1095
|
+
"2fa",
|
|
1096
|
+
"cuenta",
|
|
1097
|
+
"perfil",
|
|
1098
|
+
"cambio de email"
|
|
1099
|
+
],
|
|
1100
|
+
compliance: ["kyc", "aml", "cumplimiento", "regulación", "rgpd"],
|
|
1101
|
+
other: []
|
|
1102
|
+
}
|
|
365
1103
|
};
|
|
366
1104
|
var PRIORITY_HINTS = {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
1105
|
+
en: {
|
|
1106
|
+
urgent: ["urgent", "asap", "immediately", "today", "right away"],
|
|
1107
|
+
high: ["high priority", "blocking", "major", "critical"],
|
|
1108
|
+
medium: ["soon", "next few days"],
|
|
1109
|
+
low: ["nice to have", "when possible", "later"]
|
|
1110
|
+
},
|
|
1111
|
+
fr: {
|
|
1112
|
+
urgent: [
|
|
1113
|
+
"urgent",
|
|
1114
|
+
"dès que possible",
|
|
1115
|
+
"immédiatement",
|
|
1116
|
+
"aujourd'hui",
|
|
1117
|
+
"tout de suite"
|
|
1118
|
+
],
|
|
1119
|
+
high: ["haute priorité", "bloquant", "majeur", "critique"],
|
|
1120
|
+
medium: ["bientôt", "prochains jours"],
|
|
1121
|
+
low: ["ce serait bien", "quand possible", "plus tard"]
|
|
1122
|
+
},
|
|
1123
|
+
es: {
|
|
1124
|
+
urgent: [
|
|
1125
|
+
"urgente",
|
|
1126
|
+
"lo antes posible",
|
|
1127
|
+
"inmediatamente",
|
|
1128
|
+
"hoy",
|
|
1129
|
+
"ahora mismo"
|
|
1130
|
+
],
|
|
1131
|
+
high: ["alta prioridad", "bloqueante", "mayor", "crítico"],
|
|
1132
|
+
medium: ["pronto", "próximos días"],
|
|
1133
|
+
low: ["sería bueno", "cuando sea posible", "más tarde"]
|
|
1134
|
+
}
|
|
371
1135
|
};
|
|
372
1136
|
var SENTIMENT_HINTS = {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
1137
|
+
en: {
|
|
1138
|
+
positive: ["love", "great", "awesome", "thank you"],
|
|
1139
|
+
neutral: ["question", "wonder", "curious"],
|
|
1140
|
+
negative: ["unhappy", "bad", "terrible", "awful", "angry"],
|
|
1141
|
+
frustrated: ["furious", "frustrated", "fed up", "ridiculous"]
|
|
1142
|
+
},
|
|
1143
|
+
fr: {
|
|
1144
|
+
positive: ["adore", "super", "formidable", "merci"],
|
|
1145
|
+
neutral: ["question", "demande", "curieux"],
|
|
1146
|
+
negative: ["mécontent", "mauvais", "terrible", "affreux", "en colère"],
|
|
1147
|
+
frustrated: ["furieux", "frustré", "ras le bol", "ridicule"]
|
|
1148
|
+
},
|
|
1149
|
+
es: {
|
|
1150
|
+
positive: ["encanta", "genial", "increíble", "gracias"],
|
|
1151
|
+
neutral: ["pregunta", "duda", "curioso"],
|
|
1152
|
+
negative: ["descontento", "malo", "terrible", "horrible", "enojado"],
|
|
1153
|
+
frustrated: ["furioso", "frustrado", "harto", "ridículo"]
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
var INTENT_KEYWORDS = {
|
|
1157
|
+
en: [
|
|
1158
|
+
{ keyword: "refund", intent: "refund" },
|
|
1159
|
+
{ keyword: "chargeback", intent: "refund" },
|
|
1160
|
+
{ keyword: "payout", intent: "payout" },
|
|
1161
|
+
{ keyword: "login", intent: "login-help" },
|
|
1162
|
+
{ keyword: "feature", intent: "feature-request" },
|
|
1163
|
+
{ keyword: "bug", intent: "bug-report" },
|
|
1164
|
+
{ keyword: "error", intent: "bug-report" }
|
|
1165
|
+
],
|
|
1166
|
+
fr: [
|
|
1167
|
+
{ keyword: "remboursement", intent: "refund" },
|
|
1168
|
+
{ keyword: "rétrofacturation", intent: "refund" },
|
|
1169
|
+
{ keyword: "versement", intent: "payout" },
|
|
1170
|
+
{ keyword: "connexion", intent: "login-help" },
|
|
1171
|
+
{ keyword: "fonctionnalité", intent: "feature-request" },
|
|
1172
|
+
{ keyword: "bogue", intent: "bug-report" },
|
|
1173
|
+
{ keyword: "erreur", intent: "bug-report" }
|
|
1174
|
+
],
|
|
1175
|
+
es: [
|
|
1176
|
+
{ keyword: "reembolso", intent: "refund" },
|
|
1177
|
+
{ keyword: "contracargo", intent: "refund" },
|
|
1178
|
+
{ keyword: "desembolso", intent: "payout" },
|
|
1179
|
+
{ keyword: "inicio de sesión", intent: "login-help" },
|
|
1180
|
+
{ keyword: "funcionalidad", intent: "feature-request" },
|
|
1181
|
+
{ keyword: "error", intent: "bug-report" },
|
|
1182
|
+
{ keyword: "fallo", intent: "bug-report" }
|
|
1183
|
+
]
|
|
377
1184
|
};
|
|
1185
|
+
function resolveBaseLocale(locale) {
|
|
1186
|
+
if (!locale)
|
|
1187
|
+
return "en";
|
|
1188
|
+
const base = locale.split("-")[0]?.toLowerCase() ?? "en";
|
|
1189
|
+
if (base === "en" || base === "fr" || base === "es") {
|
|
1190
|
+
return base;
|
|
1191
|
+
}
|
|
1192
|
+
return "en";
|
|
1193
|
+
}
|
|
378
1194
|
|
|
379
1195
|
class TicketClassifier {
|
|
380
1196
|
keywords;
|
|
1197
|
+
priorityHints;
|
|
1198
|
+
sentimentHints;
|
|
1199
|
+
intentKeywords;
|
|
381
1200
|
llm;
|
|
382
1201
|
llmModel;
|
|
1202
|
+
locale;
|
|
383
1203
|
constructor(options) {
|
|
1204
|
+
this.locale = resolveBaseLocale(options?.locale);
|
|
384
1205
|
this.keywords = {
|
|
385
|
-
...CATEGORY_KEYWORDS,
|
|
1206
|
+
...CATEGORY_KEYWORDS[this.locale],
|
|
386
1207
|
...options?.keywords ?? {}
|
|
387
1208
|
};
|
|
1209
|
+
this.priorityHints = PRIORITY_HINTS[this.locale];
|
|
1210
|
+
this.sentimentHints = SENTIMENT_HINTS[this.locale];
|
|
1211
|
+
this.intentKeywords = INTENT_KEYWORDS[this.locale];
|
|
388
1212
|
this.llm = options?.llm;
|
|
389
1213
|
this.llmModel = options?.llmModel;
|
|
390
1214
|
}
|
|
@@ -396,7 +1220,12 @@ class TicketClassifier {
|
|
|
396
1220
|
const llmResult = await this.llm.chat([
|
|
397
1221
|
{
|
|
398
1222
|
role: "system",
|
|
399
|
-
content: [
|
|
1223
|
+
content: [
|
|
1224
|
+
{
|
|
1225
|
+
type: "text",
|
|
1226
|
+
text: createSupportBotI18n(this.locale).t("prompt.classifier.system")
|
|
1227
|
+
}
|
|
1228
|
+
]
|
|
400
1229
|
},
|
|
401
1230
|
{
|
|
402
1231
|
role: "user",
|
|
@@ -463,7 +1292,8 @@ ${ticket.body}`.toLowerCase();
|
|
|
463
1292
|
"medium",
|
|
464
1293
|
"low"
|
|
465
1294
|
]) {
|
|
466
|
-
|
|
1295
|
+
const hints = this.priorityHints[priority];
|
|
1296
|
+
if (hints?.some((word) => text.includes(word))) {
|
|
467
1297
|
return priority;
|
|
468
1298
|
}
|
|
469
1299
|
}
|
|
@@ -476,25 +1306,21 @@ ${ticket.body}`.toLowerCase();
|
|
|
476
1306
|
"neutral",
|
|
477
1307
|
"positive"
|
|
478
1308
|
]) {
|
|
479
|
-
|
|
1309
|
+
const hints = this.sentimentHints[sentiment];
|
|
1310
|
+
if (hints?.some((word) => text.includes(word))) {
|
|
480
1311
|
return sentiment;
|
|
481
1312
|
}
|
|
482
1313
|
}
|
|
483
1314
|
return "neutral";
|
|
484
1315
|
}
|
|
485
1316
|
extractIntents(text) {
|
|
486
|
-
const intents =
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (text.includes("feature"))
|
|
494
|
-
intents.push("feature-request");
|
|
495
|
-
if (text.includes("bug") || text.includes("error"))
|
|
496
|
-
intents.push("bug-report");
|
|
497
|
-
return intents.length ? intents : ["general"];
|
|
1317
|
+
const intents = new Set;
|
|
1318
|
+
for (const { keyword, intent } of this.intentKeywords) {
|
|
1319
|
+
if (text.includes(keyword)) {
|
|
1320
|
+
intents.add(intent);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return intents.size > 0 ? [...intents] : ["general"];
|
|
498
1324
|
}
|
|
499
1325
|
estimateConfidence(category, priority, sentiment) {
|
|
500
1326
|
let base = 0.6;
|