|
|
@@ -67,14 +67,18 @@ agentToolsRouter.post('/agent-tools/translate-draft', async (req, res, next) =>
|
|
|
'conversationId with at least one incoming customer message is required for auto target language.',
|
|
|
);
|
|
|
}
|
|
|
+ const detectedCustomerLanguage = targetLanguage === 'auto' && firstCustomerMessage
|
|
|
+ ? inferCustomerLanguage(firstCustomerMessage)
|
|
|
+ : null;
|
|
|
const result = await translateWithCache({
|
|
|
text: draftText,
|
|
|
- targetLanguage,
|
|
|
+ targetLanguage: detectedCustomerLanguage ?? targetLanguage,
|
|
|
sourceLanguage: body.sourceLanguage ?? 'pl',
|
|
|
conversationId: body.conversationId,
|
|
|
messageId: body.messageId === undefined ? undefined : String(body.messageId),
|
|
|
mode: 'draft',
|
|
|
firstCustomerMessage,
|
|
|
+ detectedCustomerLanguage,
|
|
|
});
|
|
|
res.json({ ok: true, ...result });
|
|
|
} catch (err) {
|
|
|
@@ -90,6 +94,7 @@ async function translateWithCache(input: {
|
|
|
messageId?: string;
|
|
|
mode: 'message' | 'draft';
|
|
|
firstCustomerMessage?: string | null;
|
|
|
+ detectedCustomerLanguage?: string | null;
|
|
|
}): Promise<{
|
|
|
translatedText: string;
|
|
|
sourceLanguage: string | null;
|
|
|
@@ -125,6 +130,7 @@ async function translateWithCache(input: {
|
|
|
messageId: input.messageId ?? null,
|
|
|
mode: input.mode,
|
|
|
firstCustomerMessage: input.firstCustomerMessage ?? null,
|
|
|
+ detectedCustomerLanguage: input.detectedCustomerLanguage ?? null,
|
|
|
},
|
|
|
});
|
|
|
|
|
|
@@ -163,6 +169,41 @@ function isPublicIncoming(message: ChatwootMessage): boolean {
|
|
|
return message.private !== true && (message.message_type === 'incoming' || message.message_type === 0);
|
|
|
}
|
|
|
|
|
|
+function inferCustomerLanguage(text: string): string {
|
|
|
+ const sample = text.toLowerCase();
|
|
|
+ if (/[α-ωάέήίόύώϊϋΐΰ]/i.test(text)) return 'el';
|
|
|
+ if (/[а-яёіїєґ]/i.test(text)) {
|
|
|
+ return /[іїєґ]/i.test(text) ? 'uk' : 'ru';
|
|
|
+ }
|
|
|
+
|
|
|
+ const scores: Record<string, number> = {
|
|
|
+ en: score(sample, [' the ', ' and ', ' you ', ' what ', ' where ', ' when ', ' have ', ' haven', ' ordered ', ' received ', ' going ', ' hello ', ' hey ', ' can ', ' order ']),
|
|
|
+ de: score(sample, [' der ', ' die ', ' das ', ' und ', ' ich ', ' nicht ', ' eine ', ' einem ', ' guten ', ' brauche ', ' bestellung ', ' paket ']) + diacriticScore(sample, /[äöüß]/g),
|
|
|
+ fr: score(sample, [' le ', ' la ', ' les ', ' des ', ' est ', ' pas ', ' bonjour ', ' commande ', ' colis ', ' avec ', ' pour ']) + diacriticScore(sample, /[àâçéèêëîïôûùüÿœ]/g),
|
|
|
+ es: score(sample, [' el ', ' la ', ' los ', ' las ', ' que ', ' una ', ' para ', ' pedido ', ' hola ', ' gracias ']) + diacriticScore(sample, /[áéíóúñ¿¡]/g),
|
|
|
+ it: score(sample, [' il ', ' la ', ' gli ', ' che ', ' per ', ' ordine ', ' buongiorno ', ' pacco ', ' grazie ']) + diacriticScore(sample, /[àèéìíîòóù]/g),
|
|
|
+ pl: score(sample, [' czy ', ' gdzie ', ' kiedy ', ' proszę ', ' zamówienie ', ' paczka ', ' człowieku ', ' sprawdź ']) + diacriticScore(sample, /[ąćęłńóśźż]/g),
|
|
|
+ cs: score(sample, [' kde ', ' kdy ', ' prosím ', ' objednávka ', ' balík ', ' dobrý ']) + diacriticScore(sample, /[ěščřžýáíéůúňďť]/g),
|
|
|
+ sk: score(sample, [' kde ', ' kedy ', ' prosím ', ' objednávka ', ' balík ', ' dobrý ']) + diacriticScore(sample, /[äôľĺŕšťžýáíéúňď]/g),
|
|
|
+ nl: score(sample, [' de ', ' het ', ' een ', ' niet ', ' waar ', ' bestelling ', ' pakket ', ' goedemorgen ']),
|
|
|
+ ro: score(sample, [' și ', ' este ', ' pentru ', ' comandă ', ' colet ', ' bună ', ' unde ']) + diacriticScore(sample, /[ăâîșț]/g),
|
|
|
+ hu: score(sample, [' és ', ' hogy ', ' nem ', ' rendelés ', ' csomag ', ' kérem ', ' hol ']) + diacriticScore(sample, /[áéíóöőúüű]/g),
|
|
|
+ pt: score(sample, [' que ', ' para ', ' pedido ', ' pacote ', ' olá ', ' obrigado ', ' você ']) + diacriticScore(sample, /[áâãàçéêíóôõú]/g),
|
|
|
+ };
|
|
|
+
|
|
|
+ const best = Object.entries(scores).sort((a, b) => b[1] - a[1])[0];
|
|
|
+ return best && best[1] > 0 ? best[0] : 'en';
|
|
|
+}
|
|
|
+
|
|
|
+function score(sample: string, needles: string[]): number {
|
|
|
+ const padded = ` ${sample.replace(/\s+/g, ' ')} `;
|
|
|
+ return needles.reduce((sum, n) => sum + (padded.includes(n) ? 2 : 0), 0);
|
|
|
+}
|
|
|
+
|
|
|
+function diacriticScore(sample: string, regex: RegExp): number {
|
|
|
+ return (sample.match(regex) ?? []).length;
|
|
|
+}
|
|
|
+
|
|
|
function timestamp(message: ChatwootMessage): number {
|
|
|
if (typeof message.created_at === 'number') return message.created_at;
|
|
|
if (typeof message.created_at === 'string') {
|