@infuro/cms-core 1.0.31 → 1.0.33
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 +3 -2
- package/dist/admin.cjs +929 -345
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.js +947 -362
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +839 -59
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +833 -53
- package/dist/api.js.map +1 -1
- package/dist/auth.cjs +159 -14
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +37 -22
- package/dist/auth.d.ts +37 -22
- package/dist/auth.js +153 -14
- package/dist/auth.js.map +1 -1
- package/dist/cli.cjs +3 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -1
- package/dist/{index-Bda8D1Rm.d.ts → index-DWd5Gjc4.d.ts} +2 -2
- package/dist/{index-BcMRGNjS.d.cts → index-rZTnpK7y.d.cts} +2 -2
- package/dist/index.cjs +1141 -254
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -30
- package/dist/index.d.ts +41 -30
- package/dist/index.js +1135 -254
- package/dist/index.js.map +1 -1
- package/dist/migrations/1778660000003-AddQrTokenToOrders.ts +32 -0
- package/dist/migrations/1779100000000-ChatConversationLeadEmailSent.ts +16 -0
- package/dist/migrations/1779200000000-LlmAgentScope.ts +72 -0
- package/package.json +2 -1
package/dist/api.cjs
CHANGED
|
@@ -1215,6 +1215,65 @@ function resolveVendorIdForContactCheck(scope, bodyOrRow) {
|
|
|
1215
1215
|
return Number.isFinite(n) ? n : null;
|
|
1216
1216
|
}
|
|
1217
1217
|
|
|
1218
|
+
// src/api/crud.ts
|
|
1219
|
+
var import_crypto = __toESM(require("crypto"), 1);
|
|
1220
|
+
|
|
1221
|
+
// src/plugins/llm/llm-agent-scope.ts
|
|
1222
|
+
var LLM_AGENT_SCOPE_CHATBOT = "chatbot";
|
|
1223
|
+
var LLM_AGENT_SCOPE_BLOG_CREATION = "blog_creation";
|
|
1224
|
+
var LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST = "social_media_post";
|
|
1225
|
+
var LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT = "email_intent_chatbot";
|
|
1226
|
+
var LLM_AGENT_SCOPE_BLOG_METADATA = "blog_metadata";
|
|
1227
|
+
var LLM_AGENT_SCOPES = [
|
|
1228
|
+
LLM_AGENT_SCOPE_CHATBOT,
|
|
1229
|
+
LLM_AGENT_SCOPE_BLOG_CREATION,
|
|
1230
|
+
LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST,
|
|
1231
|
+
LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT,
|
|
1232
|
+
LLM_AGENT_SCOPE_BLOG_METADATA
|
|
1233
|
+
];
|
|
1234
|
+
var LLM_AGENT_DEFAULT_SLUG_BY_SCOPE = {
|
|
1235
|
+
[LLM_AGENT_SCOPE_CHATBOT]: "site-chat-assistant",
|
|
1236
|
+
[LLM_AGENT_SCOPE_BLOG_CREATION]: "blog-generator",
|
|
1237
|
+
[LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]: "blog-generator-social",
|
|
1238
|
+
[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]: "email-intent-chatbot",
|
|
1239
|
+
[LLM_AGENT_SCOPE_BLOG_METADATA]: "blog-generator-metadata"
|
|
1240
|
+
};
|
|
1241
|
+
var LLM_AGENT_DEFAULT_NAME_BY_SCOPE = {
|
|
1242
|
+
[LLM_AGENT_SCOPE_CHATBOT]: "Site Chat Assistant",
|
|
1243
|
+
[LLM_AGENT_SCOPE_BLOG_CREATION]: "Blog Generator",
|
|
1244
|
+
[LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]: "Blog Generator (Social)",
|
|
1245
|
+
[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]: "Chat Lead Intent Classifier",
|
|
1246
|
+
[LLM_AGENT_SCOPE_BLOG_METADATA]: "Blog Generator (Metadata)"
|
|
1247
|
+
};
|
|
1248
|
+
var SITE_CHAT_ASSISTANT_SLUG = LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
|
|
1249
|
+
var SITE_CHAT_ASSISTANT_NAME = LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
|
|
1250
|
+
function isLlmAgentScope(value) {
|
|
1251
|
+
if (!value?.trim()) return false;
|
|
1252
|
+
return LLM_AGENT_SCOPES.includes(value.trim());
|
|
1253
|
+
}
|
|
1254
|
+
var BLOG_LLM_AGENT_SLUGS = [
|
|
1255
|
+
LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_BLOG_CREATION],
|
|
1256
|
+
LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_BLOG_METADATA],
|
|
1257
|
+
LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]
|
|
1258
|
+
];
|
|
1259
|
+
|
|
1260
|
+
// src/plugins/llm/llm-agent-scope-crud.ts
|
|
1261
|
+
async function validateLlmAgentScopeForWrite(dataSource, entityMap, scope, excludeId) {
|
|
1262
|
+
if (scope == null || scope === "") return null;
|
|
1263
|
+
if (typeof scope !== "string" || !isLlmAgentScope(scope)) {
|
|
1264
|
+
return `Invalid agent scope. Allowed: chatbot, blog_creation, social_media_post, email_intent_chatbot, blog_metadata.`;
|
|
1265
|
+
}
|
|
1266
|
+
const entity = entityMap.llm_agents;
|
|
1267
|
+
if (!entity) return null;
|
|
1268
|
+
const repo = dataSource.getRepository(entity);
|
|
1269
|
+
const existing = await repo.findOne({
|
|
1270
|
+
where: { scope, deleted: false }
|
|
1271
|
+
});
|
|
1272
|
+
if (!existing) return null;
|
|
1273
|
+
if (excludeId != null && existing.id === excludeId) return null;
|
|
1274
|
+
return `An active agent already uses scope "${scope}".`;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1218
1277
|
// src/api/crud.ts
|
|
1219
1278
|
var CRUD_LOG = "[cms-crud]";
|
|
1220
1279
|
function logCrudClientError(op, detail) {
|
|
@@ -1431,6 +1490,7 @@ function buildListFilterAndFromSearchParams(repo, searchParams) {
|
|
|
1431
1490
|
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
1432
1491
|
if (!isListStringColumn(col)) continue;
|
|
1433
1492
|
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
1493
|
+
if (name === "scope") continue;
|
|
1434
1494
|
const raw = searchParams.get(name)?.trim();
|
|
1435
1495
|
if (!raw) continue;
|
|
1436
1496
|
and[name] = (0, import_typeorm2.ILike)(`%${raw}%`);
|
|
@@ -1460,6 +1520,10 @@ function buildExactListParamWhere(repo, searchParams) {
|
|
|
1460
1520
|
extraWhere[name] = raw === "true";
|
|
1461
1521
|
}
|
|
1462
1522
|
}
|
|
1523
|
+
const scopeParam = searchParams.get("scope")?.trim();
|
|
1524
|
+
if (scopeParam && columnNames.has("scope")) {
|
|
1525
|
+
extraWhere.scope = scopeParam;
|
|
1526
|
+
}
|
|
1463
1527
|
return extraWhere;
|
|
1464
1528
|
}
|
|
1465
1529
|
function mergeDeletedFalseWhere(repo, where) {
|
|
@@ -1671,6 +1735,12 @@ function discountEvaluateRule(rule, cartLines, cartTotal) {
|
|
|
1671
1735
|
case "minAmount":
|
|
1672
1736
|
return discountCompare(cartTotal, rule.comparisonOperator, ruleValue);
|
|
1673
1737
|
case "quantity": {
|
|
1738
|
+
if (rule.subType === "productId" && rule.value?.productId) {
|
|
1739
|
+
const targetProductId = Number(rule.value.productId);
|
|
1740
|
+
const requiredQty = Number(rule.value.v);
|
|
1741
|
+
const productQty = cartLines.filter((l) => l.productId === targetProductId).reduce((s, l) => s + l.quantity, 0);
|
|
1742
|
+
return discountCompare(productQty, rule.comparisonOperator, requiredQty);
|
|
1743
|
+
}
|
|
1674
1744
|
const totalQty = cartLines.reduce((s, l) => s + l.quantity, 0);
|
|
1675
1745
|
return discountCompare(totalQty, rule.comparisonOperator, ruleValue);
|
|
1676
1746
|
}
|
|
@@ -1734,6 +1804,10 @@ function discountExtractBuyProductId(nodes) {
|
|
|
1734
1804
|
const n = Number(v);
|
|
1735
1805
|
if (Number.isFinite(n) && n > 0) return n;
|
|
1736
1806
|
}
|
|
1807
|
+
if (node.conditionType === "rule" && node.type === "quantity" && node.subType === "productId") {
|
|
1808
|
+
const n = Number(node.value?.productId);
|
|
1809
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
1810
|
+
}
|
|
1737
1811
|
if (node.children?.length) {
|
|
1738
1812
|
const found = discountExtractBuyProductId(node.children);
|
|
1739
1813
|
if (found !== null) return found;
|
|
@@ -2444,6 +2518,25 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
2444
2518
|
if (pe) return pe;
|
|
2445
2519
|
return null;
|
|
2446
2520
|
}
|
|
2521
|
+
async function tryAssignSingleVendorOnAdminCreate(resource, scope, persistBody) {
|
|
2522
|
+
if (!resourceUsesVendorScope(resource) || scope.type !== "all") return;
|
|
2523
|
+
const currentVendorId = Number(persistBody.vendorId);
|
|
2524
|
+
if (Number.isFinite(currentVendorId) && currentVendorId > 0) return;
|
|
2525
|
+
if (!entityMap.vendors) return;
|
|
2526
|
+
const vendorRepo = dataSource.getRepository(entityMap.vendors);
|
|
2527
|
+
const where = mergeDeletedFalseWhere(vendorRepo, {});
|
|
2528
|
+
const rows = await vendorRepo.find({
|
|
2529
|
+
where,
|
|
2530
|
+
order: { id: "ASC" },
|
|
2531
|
+
take: 2
|
|
2532
|
+
});
|
|
2533
|
+
if (rows.length === 1) {
|
|
2534
|
+
const onlyVendorId = Number(rows[0].id);
|
|
2535
|
+
if (Number.isFinite(onlyVendorId) && onlyVendorId > 0) {
|
|
2536
|
+
persistBody.vendorId = onlyVendorId;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2447
2540
|
return {
|
|
2448
2541
|
async GET(req, resource) {
|
|
2449
2542
|
const authError = await authz(req, resource, "read");
|
|
@@ -2959,6 +3052,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
2959
3052
|
const couponCode = String(body.couponCode).trim().toUpperCase();
|
|
2960
3053
|
const persistBody2 = pickColumnUpdates(repo2, { ...body, couponCode });
|
|
2961
3054
|
const scopeDiscount = await resolveScope();
|
|
3055
|
+
await tryAssignSingleVendorOnAdminCreate(resource, scopeDiscount, persistBody2);
|
|
2962
3056
|
const vendorIdCheck2 = requireVendorIdForScopedCreate(
|
|
2963
3057
|
resource,
|
|
2964
3058
|
persistBody2,
|
|
@@ -3036,6 +3130,10 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
3036
3130
|
});
|
|
3037
3131
|
return json({ error: "Invalid request payload" }, { status: 400 });
|
|
3038
3132
|
}
|
|
3133
|
+
if (resource === "llm_agents" && "scope" in persistBody) {
|
|
3134
|
+
const scopeErr = await validateLlmAgentScopeForWrite(dataSource, entityMap, persistBody.scope);
|
|
3135
|
+
if (scopeErr) return json({ error: scopeErr }, { status: 400 });
|
|
3136
|
+
}
|
|
3039
3137
|
if (resource === "products") {
|
|
3040
3138
|
if ("sku" in persistBody) {
|
|
3041
3139
|
const skuNorm = normalizeProductSku(persistBody.sku);
|
|
@@ -3151,6 +3249,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
3151
3249
|
);
|
|
3152
3250
|
}
|
|
3153
3251
|
const scopeCreate = await resolveScope();
|
|
3252
|
+
await tryAssignSingleVendorOnAdminCreate(resource, scopeCreate, persistBody);
|
|
3154
3253
|
const vendorIdCheck = requireVendorIdForScopedCreate(resource, persistBody, scopeCreate, repo, body);
|
|
3155
3254
|
if (!vendorIdCheck.ok) {
|
|
3156
3255
|
return json({ error: vendorIdCheck.error }, { status: vendorIdCheck.status });
|
|
@@ -3193,6 +3292,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
3193
3292
|
const randomPart = Math.floor(1e4 + Math.random() * 9e4);
|
|
3194
3293
|
persistBody.contactId = contact.id;
|
|
3195
3294
|
persistBody.orderNumber = `ORD00${randomPart}`;
|
|
3295
|
+
persistBody.qrToken = import_crypto.default.randomBytes(16).toString("hex");
|
|
3196
3296
|
}
|
|
3197
3297
|
created = await repo.save(repo.create(persistBody));
|
|
3198
3298
|
if (resource === "orders") {
|
|
@@ -3961,6 +4061,15 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
3961
4061
|
const t = updatePayload.type;
|
|
3962
4062
|
if (t === "" || t === "none" || t == null) updatePayload.type = null;
|
|
3963
4063
|
}
|
|
4064
|
+
if (resource === "llm_agents" && "scope" in updatePayload) {
|
|
4065
|
+
const scopeErr = await validateLlmAgentScopeForWrite(
|
|
4066
|
+
dataSource,
|
|
4067
|
+
entityMap,
|
|
4068
|
+
updatePayload.scope,
|
|
4069
|
+
numericId
|
|
4070
|
+
);
|
|
4071
|
+
if (scopeErr) return json({ error: scopeErr }, { status: 400 });
|
|
4072
|
+
}
|
|
3964
4073
|
if ((resource === "orders" || resource === "payments") && "contactId" in updatePayload && updatePayload.contactId != null && entityMap.vendor_customers) {
|
|
3965
4074
|
const existingRow = await repo.findOne({
|
|
3966
4075
|
where: { id: numericId }
|
|
@@ -4170,8 +4279,8 @@ function createForgotPasswordHandler(config) {
|
|
|
4170
4279
|
const user = await userRepo.findOne({ where: { email }, select: ["email"] });
|
|
4171
4280
|
const msg = "If an account exists with this email, you will receive a reset link shortly.";
|
|
4172
4281
|
if (!user) return json({ message: msg }, { status: 200 });
|
|
4173
|
-
const
|
|
4174
|
-
const token =
|
|
4282
|
+
const crypto3 = await import("crypto");
|
|
4283
|
+
const token = crypto3.randomBytes(32).toString("hex");
|
|
4175
4284
|
const expiresAt = new Date(Date.now() + resetExpiryHours * 60 * 60 * 1e3);
|
|
4176
4285
|
const tokenRepo = dataSource.getRepository(entityMap.password_reset_tokens);
|
|
4177
4286
|
await tokenRepo.save(tokenRepo.create({ email: user.email, token, expiresAt }));
|
|
@@ -4324,6 +4433,7 @@ var LlmAgent = class {
|
|
|
4324
4433
|
id;
|
|
4325
4434
|
name;
|
|
4326
4435
|
slug;
|
|
4436
|
+
scope;
|
|
4327
4437
|
systemInstruction;
|
|
4328
4438
|
model;
|
|
4329
4439
|
temperature;
|
|
@@ -4347,6 +4457,9 @@ __decorateClass([
|
|
|
4347
4457
|
__decorateClass([
|
|
4348
4458
|
(0, import_typeorm4.Column)("varchar")
|
|
4349
4459
|
], LlmAgent.prototype, "slug", 2);
|
|
4460
|
+
__decorateClass([
|
|
4461
|
+
(0, import_typeorm4.Column)("varchar", { nullable: true })
|
|
4462
|
+
], LlmAgent.prototype, "scope", 2);
|
|
4350
4463
|
__decorateClass([
|
|
4351
4464
|
(0, import_typeorm4.Column)("text", { name: "system_instruction", default: "" })
|
|
4352
4465
|
], LlmAgent.prototype, "systemInstruction", 2);
|
|
@@ -4398,6 +4511,515 @@ function llmAgentToChatAgentOptions(agent) {
|
|
|
4398
4511
|
};
|
|
4399
4512
|
}
|
|
4400
4513
|
|
|
4514
|
+
// src/plugins/llm/chat-email-intent.ts
|
|
4515
|
+
var NONE_INTENT = "NONE";
|
|
4516
|
+
var CHAT_EMAIL_LOG = "[chat-email-tool]";
|
|
4517
|
+
function logChatEmail(step, data) {
|
|
4518
|
+
if (data && Object.keys(data).length > 0) {
|
|
4519
|
+
console.info(CHAT_EMAIL_LOG, step, data);
|
|
4520
|
+
} else {
|
|
4521
|
+
console.info(CHAT_EMAIL_LOG, step);
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
function normalizeIntentKey(raw) {
|
|
4525
|
+
return raw.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4526
|
+
}
|
|
4527
|
+
function parseIntentsJson(raw) {
|
|
4528
|
+
if (!raw?.trim()) return null;
|
|
4529
|
+
try {
|
|
4530
|
+
const parsed = JSON.parse(raw);
|
|
4531
|
+
if (!Array.isArray(parsed)) return null;
|
|
4532
|
+
const out = [];
|
|
4533
|
+
for (const row of parsed) {
|
|
4534
|
+
if (!row || typeof row !== "object") continue;
|
|
4535
|
+
const o = row;
|
|
4536
|
+
const intent = normalizeIntentKey(String(o.intent ?? o.id ?? ""));
|
|
4537
|
+
const description = String(o.description ?? "").trim();
|
|
4538
|
+
const emailTo = String(o.emailTo ?? o.email ?? "").trim();
|
|
4539
|
+
if (!intent || !description || !emailTo) continue;
|
|
4540
|
+
out.push({ intent, description, emailTo });
|
|
4541
|
+
}
|
|
4542
|
+
return out.length > 0 ? out : null;
|
|
4543
|
+
} catch {
|
|
4544
|
+
return null;
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
function dedupeIntents(intents) {
|
|
4548
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4549
|
+
const out = [];
|
|
4550
|
+
for (const row of intents) {
|
|
4551
|
+
const key = normalizeIntentKey(row.intent);
|
|
4552
|
+
if (!key || seen.has(key)) continue;
|
|
4553
|
+
seen.add(key);
|
|
4554
|
+
out.push({
|
|
4555
|
+
intent: key,
|
|
4556
|
+
description: row.description.trim(),
|
|
4557
|
+
emailTo: row.emailTo.trim()
|
|
4558
|
+
});
|
|
4559
|
+
}
|
|
4560
|
+
return out;
|
|
4561
|
+
}
|
|
4562
|
+
function parseChatEmailToolSettings(map) {
|
|
4563
|
+
const fromJson = parseIntentsJson(map.emailIntents);
|
|
4564
|
+
const intents = dedupeIntents(fromJson ?? []);
|
|
4565
|
+
const legacyPositive = map.emailIntentPrompt?.trim() ?? "";
|
|
4566
|
+
const legacyNegative = map.emailNegativeIntentPrompt?.trim() ?? "";
|
|
4567
|
+
let classifierInstructions = map.emailClassifierInstructions?.trim() ?? "";
|
|
4568
|
+
if (!classifierInstructions && (legacyPositive || legacyNegative)) {
|
|
4569
|
+
const parts = [];
|
|
4570
|
+
if (legacyPositive) parts.push(`Legacy positive signals:
|
|
4571
|
+
${legacyPositive}`);
|
|
4572
|
+
if (legacyNegative) parts.push(`Do NOT assign an intent when:
|
|
4573
|
+
${legacyNegative}`);
|
|
4574
|
+
classifierInstructions = parts.join("\n\n");
|
|
4575
|
+
}
|
|
4576
|
+
return {
|
|
4577
|
+
enabled: map.emailToolEnabled === "true",
|
|
4578
|
+
intents,
|
|
4579
|
+
classifierInstructions,
|
|
4580
|
+
toolPrompt: map.emailToolPrompt ?? ""
|
|
4581
|
+
};
|
|
4582
|
+
}
|
|
4583
|
+
var EMAIL_TOOL_VALIDATION_KEY = "emailTool";
|
|
4584
|
+
function parseEmailToolObject(raw) {
|
|
4585
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4586
|
+
const o = raw;
|
|
4587
|
+
const intentsRaw = o.intents;
|
|
4588
|
+
let intents;
|
|
4589
|
+
if (Array.isArray(intentsRaw)) {
|
|
4590
|
+
const parsed = parseIntentsJson(JSON.stringify(intentsRaw));
|
|
4591
|
+
if (parsed?.length) intents = parsed;
|
|
4592
|
+
}
|
|
4593
|
+
return {
|
|
4594
|
+
enabled: o.enabled === true,
|
|
4595
|
+
classifierInstructions: typeof o.classifierInstructions === "string" ? o.classifierInstructions.trim() : void 0,
|
|
4596
|
+
toolPrompt: typeof o.toolPrompt === "string" ? o.toolPrompt.trim() : void 0,
|
|
4597
|
+
intents
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
function parseEmailToolFromAgentValidationRules(validationRulesText) {
|
|
4601
|
+
const raw = validationRulesText?.trim();
|
|
4602
|
+
if (!raw) return null;
|
|
4603
|
+
try {
|
|
4604
|
+
const parsed = JSON.parse(raw);
|
|
4605
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
4606
|
+
const emailTool = parsed[EMAIL_TOOL_VALIDATION_KEY];
|
|
4607
|
+
return parseEmailToolObject(emailTool);
|
|
4608
|
+
} catch {
|
|
4609
|
+
return null;
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
function resolveChatEmailToolSettings(configMap, sources) {
|
|
4613
|
+
const fromConfig = parseChatEmailToolSettings(configMap);
|
|
4614
|
+
const resolved = typeof sources === "string" || sources == null ? { chatbotValidationRules: typeof sources === "string" ? sources : null } : sources;
|
|
4615
|
+
const fromChatbot = parseEmailToolFromAgentValidationRules(resolved.chatbotValidationRules);
|
|
4616
|
+
const fromNotify = resolved.notifyAgent ? parseEmailToolFromAgentValidationRules(resolved.notifyAgent.validationRules) : null;
|
|
4617
|
+
const notifySystem = resolved.notifyAgent?.systemInstruction?.trim() ?? "";
|
|
4618
|
+
const intents = fromNotify?.intents?.length ? fromNotify.intents : fromChatbot?.intents?.length ? fromChatbot.intents : fromConfig.intents;
|
|
4619
|
+
const classifierParts = [
|
|
4620
|
+
notifySystem,
|
|
4621
|
+
fromNotify?.classifierInstructions?.trim(),
|
|
4622
|
+
fromChatbot?.classifierInstructions?.trim(),
|
|
4623
|
+
fromConfig.classifierInstructions.trim()
|
|
4624
|
+
].filter(Boolean);
|
|
4625
|
+
const toolPrompt = fromNotify?.toolPrompt?.trim() || fromChatbot?.toolPrompt?.trim() || fromConfig.toolPrompt?.trim() || "";
|
|
4626
|
+
return {
|
|
4627
|
+
enabled: fromConfig.enabled,
|
|
4628
|
+
intents,
|
|
4629
|
+
classifierInstructions: classifierParts.join("\n\n"),
|
|
4630
|
+
toolPrompt
|
|
4631
|
+
};
|
|
4632
|
+
}
|
|
4633
|
+
function intentByKey(intents, key) {
|
|
4634
|
+
if (!key) return null;
|
|
4635
|
+
const norm2 = normalizeIntentKey(key);
|
|
4636
|
+
return intents.find((i) => i.intent === norm2) ?? null;
|
|
4637
|
+
}
|
|
4638
|
+
function buildIntentTableForPrompt(intents) {
|
|
4639
|
+
return intents.map((i) => `- ${i.intent}: ${i.description} \u2192 notify ${i.emailTo}`).join("\n");
|
|
4640
|
+
}
|
|
4641
|
+
function buildIntentClassifierSystem(config, agentClassifierInstructions) {
|
|
4642
|
+
const table = buildIntentTableForPrompt(config.intents);
|
|
4643
|
+
const extra = [agentClassifierInstructions?.trim(), config.classifierInstructions.trim()].filter(Boolean).join("\n\n");
|
|
4644
|
+
const intentKeys = config.intents.map((i) => i.intent).join(", ");
|
|
4645
|
+
return `You classify a chat visitor into exactly one lead intent for email routing.
|
|
4646
|
+
|
|
4647
|
+
Available intents (pick the best match):
|
|
4648
|
+
${table}
|
|
4649
|
+
|
|
4650
|
+
If the visitor is not ready for a lead email (general FAQ, greetings only, spam, off-topic, no clear business need), use intent "${NONE_INTENT}".
|
|
4651
|
+
|
|
4652
|
+
Allowed intent values: ${intentKeys}, or ${NONE_INTENT}.
|
|
4653
|
+
|
|
4654
|
+
${extra ? `Additional instructions:
|
|
4655
|
+
${extra}
|
|
4656
|
+
` : ""}
|
|
4657
|
+
Reply with ONLY valid JSON, no markdown:
|
|
4658
|
+
{"intent":"INTENT_KEY","reason":"short explanation"}
|
|
4659
|
+
Use "${NONE_INTENT}" when no intent applies.`;
|
|
4660
|
+
}
|
|
4661
|
+
function buildChatEmailAgentContext(settings) {
|
|
4662
|
+
if (!settings.enabled) return "";
|
|
4663
|
+
const classifier = settings.classifierInstructions.trim();
|
|
4664
|
+
const extra = settings.toolPrompt?.trim() ?? "";
|
|
4665
|
+
const parts = [];
|
|
4666
|
+
if (settings.intents.length > 0) {
|
|
4667
|
+
parts.push(
|
|
4668
|
+
"## Lead email routing",
|
|
4669
|
+
"When a visitor message clearly matches an intent below, the system sends one team notification email (per conversation). Continue the conversation normally in all cases."
|
|
4670
|
+
);
|
|
4671
|
+
if (classifier) parts.push(`### Routing rules
|
|
4672
|
+
${classifier}`);
|
|
4673
|
+
parts.push(`### Intent catalog
|
|
4674
|
+
${buildIntentTableForPrompt(settings.intents)}`);
|
|
4675
|
+
} else if (classifier) {
|
|
4676
|
+
parts.push(`## Lead email routing
|
|
4677
|
+
${classifier}`);
|
|
4678
|
+
}
|
|
4679
|
+
if (extra) parts.push(`### Assistant behavior
|
|
4680
|
+
${extra}`);
|
|
4681
|
+
return parts.join("\n\n");
|
|
4682
|
+
}
|
|
4683
|
+
function buildChatbotSystemPromptWithEmailTool(baseSystemInstruction, guardrailsForPrompt, emailSettings) {
|
|
4684
|
+
let prompt = (baseSystemInstruction ?? "").trim();
|
|
4685
|
+
const guard = guardrailsForPrompt?.trim();
|
|
4686
|
+
if (guard) prompt = [prompt, guard].filter(Boolean).join("\n\n");
|
|
4687
|
+
if (emailSettings.enabled) {
|
|
4688
|
+
const emailCtx = buildChatEmailAgentContext(emailSettings);
|
|
4689
|
+
if (emailCtx) prompt = [prompt, emailCtx].filter(Boolean).join("\n\n");
|
|
4690
|
+
}
|
|
4691
|
+
return prompt;
|
|
4692
|
+
}
|
|
4693
|
+
function formatTranscript(history, latestMessage, maxChars = 4e3) {
|
|
4694
|
+
const lines = [];
|
|
4695
|
+
for (const m of history) {
|
|
4696
|
+
const role = m.role === "assistant" ? "Assistant" : m.role === "user" ? "Visitor" : "System";
|
|
4697
|
+
lines.push(`${role}: ${m.content}`);
|
|
4698
|
+
}
|
|
4699
|
+
const last = history[history.length - 1];
|
|
4700
|
+
if (!last || last.role !== "user" || last.content !== latestMessage) {
|
|
4701
|
+
lines.push(`Visitor: ${latestMessage}`);
|
|
4702
|
+
}
|
|
4703
|
+
let text = lines.join("\n");
|
|
4704
|
+
if (text.length > maxChars) text = text.slice(-maxChars);
|
|
4705
|
+
return text;
|
|
4706
|
+
}
|
|
4707
|
+
function parseIntentJson(raw, intents) {
|
|
4708
|
+
const trimmed = raw.trim();
|
|
4709
|
+
const fence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
4710
|
+
const candidate = (fence?.[1] ?? trimmed).trim();
|
|
4711
|
+
const tryParse = (s) => {
|
|
4712
|
+
const o = JSON.parse(s);
|
|
4713
|
+
let intentRaw = null;
|
|
4714
|
+
if (typeof o.intent === "string") intentRaw = o.intent;
|
|
4715
|
+
else if (o.interested === false) intentRaw = NONE_INTENT;
|
|
4716
|
+
else if (o.interested === true && intents[0]) intentRaw = intents[0].intent;
|
|
4717
|
+
if (intentRaw == null) return null;
|
|
4718
|
+
const reason = typeof o.reason === "string" && o.reason.trim() ? o.reason.trim() : "Classified from conversation";
|
|
4719
|
+
return { intent: normalizeIntentKey(intentRaw), reason };
|
|
4720
|
+
};
|
|
4721
|
+
try {
|
|
4722
|
+
return tryParse(candidate);
|
|
4723
|
+
} catch {
|
|
4724
|
+
const m = candidate.match(/\{[\s\S]*\}/);
|
|
4725
|
+
if (!m) return null;
|
|
4726
|
+
try {
|
|
4727
|
+
return tryParse(m[0]);
|
|
4728
|
+
} catch {
|
|
4729
|
+
return null;
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
}
|
|
4733
|
+
async function detectChatLeadIntent(llm, params) {
|
|
4734
|
+
if (!params.settings.intents.length) {
|
|
4735
|
+
logChatEmail("classify skipped", { reason: "no_intents_configured" });
|
|
4736
|
+
return {
|
|
4737
|
+
intent: null,
|
|
4738
|
+
intentLabel: "NONE",
|
|
4739
|
+
reason: "No lead intents configured",
|
|
4740
|
+
emailTo: null
|
|
4741
|
+
};
|
|
4742
|
+
}
|
|
4743
|
+
logChatEmail("classify start", {
|
|
4744
|
+
intentCount: params.settings.intents.length,
|
|
4745
|
+
intentKeys: params.settings.intents.map((i) => i.intent),
|
|
4746
|
+
model: params.model?.trim() || "(gateway default)",
|
|
4747
|
+
messageChars: params.message.length,
|
|
4748
|
+
historyTurns: params.history.length,
|
|
4749
|
+
hasClassifierInstructions: Boolean(
|
|
4750
|
+
params.agentClassifierInstructions?.trim() || params.settings.classifierInstructions.trim()
|
|
4751
|
+
)
|
|
4752
|
+
});
|
|
4753
|
+
const transcript = formatTranscript(params.history, params.message);
|
|
4754
|
+
const contactLines = [
|
|
4755
|
+
params.contact.name?.trim() ? `Name: ${params.contact.name.trim()}` : null,
|
|
4756
|
+
params.contact.email?.trim() ? `Email: ${params.contact.email.trim()}` : null,
|
|
4757
|
+
params.contact.phone?.trim() ? `Phone: ${params.contact.phone.trim()}` : null
|
|
4758
|
+
].filter(Boolean).join("\n");
|
|
4759
|
+
const userPrompt = `Customer:
|
|
4760
|
+
${contactLines || "(unknown)"}
|
|
4761
|
+
|
|
4762
|
+
Conversation:
|
|
4763
|
+
${transcript}
|
|
4764
|
+
|
|
4765
|
+
Pick the single best intent for the visitor's latest message.`;
|
|
4766
|
+
const res = await llm.chatAgent({
|
|
4767
|
+
systemPrompt: buildIntentClassifierSystem(params.settings, params.agentClassifierInstructions),
|
|
4768
|
+
userPrompt,
|
|
4769
|
+
temperature: 0.1,
|
|
4770
|
+
max_tokens: 256,
|
|
4771
|
+
...params.model?.trim() ? { model: params.model.trim() } : {}
|
|
4772
|
+
});
|
|
4773
|
+
const rawContent = res.content ?? "";
|
|
4774
|
+
logChatEmail("classify llm response", {
|
|
4775
|
+
responseChars: rawContent.length,
|
|
4776
|
+
responsePreview: rawContent.slice(0, 280) + (rawContent.length > 280 ? "\u2026" : "")
|
|
4777
|
+
});
|
|
4778
|
+
const parsed = parseIntentJson(rawContent, params.settings.intents);
|
|
4779
|
+
if (!parsed) {
|
|
4780
|
+
logChatEmail("classify result", {
|
|
4781
|
+
matched: false,
|
|
4782
|
+
outcome: "parse_failed",
|
|
4783
|
+
emailWillSend: false
|
|
4784
|
+
});
|
|
4785
|
+
return {
|
|
4786
|
+
intent: null,
|
|
4787
|
+
intentLabel: "Unclassified",
|
|
4788
|
+
reason: "Could not parse intent classifier response",
|
|
4789
|
+
emailTo: null
|
|
4790
|
+
};
|
|
4791
|
+
}
|
|
4792
|
+
if (parsed.intent === NONE_INTENT) {
|
|
4793
|
+
logChatEmail("classify result", {
|
|
4794
|
+
matched: false,
|
|
4795
|
+
outcome: NONE_INTENT,
|
|
4796
|
+
reason: parsed.reason,
|
|
4797
|
+
emailWillSend: false
|
|
4798
|
+
});
|
|
4799
|
+
return {
|
|
4800
|
+
intent: null,
|
|
4801
|
+
intentLabel: NONE_INTENT,
|
|
4802
|
+
reason: parsed.reason,
|
|
4803
|
+
emailTo: null
|
|
4804
|
+
};
|
|
4805
|
+
}
|
|
4806
|
+
const matched = intentByKey(params.settings.intents, parsed.intent);
|
|
4807
|
+
if (!matched) {
|
|
4808
|
+
logChatEmail("classify result", {
|
|
4809
|
+
matched: false,
|
|
4810
|
+
outcome: "unknown_intent",
|
|
4811
|
+
parsedIntent: parsed.intent,
|
|
4812
|
+
reason: parsed.reason,
|
|
4813
|
+
emailWillSend: false
|
|
4814
|
+
});
|
|
4815
|
+
return {
|
|
4816
|
+
intent: null,
|
|
4817
|
+
intentLabel: parsed.intent,
|
|
4818
|
+
reason: `Unknown intent "${parsed.intent}": ${parsed.reason}`,
|
|
4819
|
+
emailTo: null
|
|
4820
|
+
};
|
|
4821
|
+
}
|
|
4822
|
+
logChatEmail("classify result", {
|
|
4823
|
+
matched: true,
|
|
4824
|
+
outcome: "intent_found",
|
|
4825
|
+
intent: matched.intent,
|
|
4826
|
+
emailTo: matched.emailTo,
|
|
4827
|
+
reason: parsed.reason,
|
|
4828
|
+
emailWillSend: true
|
|
4829
|
+
});
|
|
4830
|
+
return {
|
|
4831
|
+
intent: matched.intent,
|
|
4832
|
+
intentLabel: `${matched.intent} \u2014 ${matched.description}`,
|
|
4833
|
+
reason: parsed.reason,
|
|
4834
|
+
emailTo: matched.emailTo
|
|
4835
|
+
};
|
|
4836
|
+
}
|
|
4837
|
+
function buildTranscriptForLeadEmail(history, latestMessage) {
|
|
4838
|
+
return formatTranscript(history, latestMessage);
|
|
4839
|
+
}
|
|
4840
|
+
function parseIntentRecipientEmails(emailTo) {
|
|
4841
|
+
return emailTo.split(/[,;]+/).map((s) => s.trim()).filter((s) => s.length > 0 && s.includes("@"));
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4844
|
+
// src/plugins/llm/find-llm-agent-by-scope.ts
|
|
4845
|
+
async function findLlmAgentByScope(dataSource, entityMap, scope, options = {}) {
|
|
4846
|
+
const { enabledOnly = true } = options;
|
|
4847
|
+
const entity = entityMap.llm_agents;
|
|
4848
|
+
if (!entity) return null;
|
|
4849
|
+
const repo = dataSource.getRepository(entity);
|
|
4850
|
+
const where = { scope, deleted: false };
|
|
4851
|
+
if (enabledOnly) where.enabled = true;
|
|
4852
|
+
return repo.findOne({ where });
|
|
4853
|
+
}
|
|
4854
|
+
|
|
4855
|
+
// src/plugins/email/templates/types.ts
|
|
4856
|
+
function normalizeSocialLinkItem(o) {
|
|
4857
|
+
const url = String(o.url ?? "").trim();
|
|
4858
|
+
if (!url) return null;
|
|
4859
|
+
let iconUrl = String(o.iconUrl ?? o.icon_image ?? "").trim();
|
|
4860
|
+
let icon = String(o.icon ?? "").trim();
|
|
4861
|
+
if (!iconUrl && /^https?:\/\//i.test(icon)) {
|
|
4862
|
+
iconUrl = icon;
|
|
4863
|
+
icon = "";
|
|
4864
|
+
}
|
|
4865
|
+
const item = { url };
|
|
4866
|
+
if (iconUrl) item.iconUrl = iconUrl;
|
|
4867
|
+
if (icon) item.icon = icon;
|
|
4868
|
+
return item;
|
|
4869
|
+
}
|
|
4870
|
+
function parseSocialLinksJson(raw) {
|
|
4871
|
+
if (raw == null || raw.trim() === "") return void 0;
|
|
4872
|
+
try {
|
|
4873
|
+
const parsed = JSON.parse(raw);
|
|
4874
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
4875
|
+
const out = [];
|
|
4876
|
+
for (const item of parsed) {
|
|
4877
|
+
if (item && typeof item === "object" && "url" in item) {
|
|
4878
|
+
const n = normalizeSocialLinkItem(item);
|
|
4879
|
+
if (n) out.push(n);
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
return out.length ? out : void 0;
|
|
4883
|
+
} catch {
|
|
4884
|
+
return void 0;
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
function mergeEmailLayoutCompanyDetails(branding, emailSettings) {
|
|
4888
|
+
const fromBranding = getCompanyDetailsFromSettings(branding);
|
|
4889
|
+
const pick = (emailVal, fallback) => {
|
|
4890
|
+
const t = emailVal?.trim();
|
|
4891
|
+
return t || fallback?.trim() || void 0;
|
|
4892
|
+
};
|
|
4893
|
+
const logoUrl = pick(emailSettings.logoUrl ?? emailSettings.emailLogoUrl, fromBranding.logoUrl);
|
|
4894
|
+
const companyName = pick(emailSettings.companyName ?? emailSettings.emailCompanyName, fromBranding.companyName);
|
|
4895
|
+
const supportEmail = pick(emailSettings.supportEmail ?? emailSettings.emailSupportEmail, fromBranding.supportEmail);
|
|
4896
|
+
const supportPhone = pick(emailSettings.supportPhone, void 0);
|
|
4897
|
+
const footerDisclaimer = pick(emailSettings.footerDisclaimer, void 0);
|
|
4898
|
+
const followUsTitle = pick(emailSettings.followUsTitle, "Follow Us") || "Follow Us";
|
|
4899
|
+
const socialFromEmail = parseSocialLinksJson(emailSettings.socialLinks);
|
|
4900
|
+
const socialLinks = socialFromEmail?.length ? socialFromEmail : fromBranding.socialLinks;
|
|
4901
|
+
return {
|
|
4902
|
+
logoUrl,
|
|
4903
|
+
companyName,
|
|
4904
|
+
supportEmail,
|
|
4905
|
+
supportPhone,
|
|
4906
|
+
socialLinks,
|
|
4907
|
+
footerDisclaimer,
|
|
4908
|
+
followUsTitle
|
|
4909
|
+
};
|
|
4910
|
+
}
|
|
4911
|
+
function getCompanyDetailsFromSettings(settingsGroup) {
|
|
4912
|
+
const logoUrl = settingsGroup.logo ?? settingsGroup.logoUrl ?? "";
|
|
4913
|
+
const companyName = settingsGroup.companyName ?? settingsGroup.company_name ?? "";
|
|
4914
|
+
const supportEmail = settingsGroup.supportEmail ?? settingsGroup.support_email ?? "";
|
|
4915
|
+
let socialLinks = [];
|
|
4916
|
+
const raw = settingsGroup.socialLinks ?? settingsGroup.social_links;
|
|
4917
|
+
if (typeof raw === "string") {
|
|
4918
|
+
try {
|
|
4919
|
+
const arr = JSON.parse(raw);
|
|
4920
|
+
if (Array.isArray(arr)) {
|
|
4921
|
+
for (const item of arr) {
|
|
4922
|
+
if (item && typeof item === "object") {
|
|
4923
|
+
const n = normalizeSocialLinkItem(item);
|
|
4924
|
+
if (n) socialLinks.push(n);
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
} catch {
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
return { logoUrl: logoUrl || void 0, companyName: companyName || void 0, supportEmail: supportEmail || void 0, socialLinks: socialLinks.length ? socialLinks : void 0 };
|
|
4932
|
+
}
|
|
4933
|
+
|
|
4934
|
+
// src/plugins/email/chat-lead-email.ts
|
|
4935
|
+
init_email_queue();
|
|
4936
|
+
|
|
4937
|
+
// src/lib/email-recipients.ts
|
|
4938
|
+
function parseEmailRecipientsFromConfig(raw) {
|
|
4939
|
+
if (raw == null || raw === "") return [];
|
|
4940
|
+
const trimmed = raw.trim();
|
|
4941
|
+
if (trimmed.startsWith("[")) {
|
|
4942
|
+
try {
|
|
4943
|
+
const parsed = JSON.parse(trimmed);
|
|
4944
|
+
if (Array.isArray(parsed)) {
|
|
4945
|
+
return parsed.map((e) => String(e).trim()).filter(Boolean);
|
|
4946
|
+
}
|
|
4947
|
+
} catch {
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
return trimmed.split(/[,;]+/).map((s) => s.trim()).filter(Boolean);
|
|
4951
|
+
}
|
|
4952
|
+
|
|
4953
|
+
// src/plugins/email/chat-lead-email.ts
|
|
4954
|
+
function resolveLeadRecipients(emailPlugin, emailSettings) {
|
|
4955
|
+
const fromCrm = parseEmailRecipientsFromConfig(
|
|
4956
|
+
emailSettings.crmEmails ?? emailSettings.crmEmail ?? ""
|
|
4957
|
+
);
|
|
4958
|
+
if (fromCrm.length > 0) return fromCrm;
|
|
4959
|
+
const fallback = emailPlugin.getDefaultTo?.() ?? "";
|
|
4960
|
+
if (fallback.trim()) return [fallback.trim()];
|
|
4961
|
+
return [];
|
|
4962
|
+
}
|
|
4963
|
+
async function sendChatLeadEmail(cms, emailSettings, brandingSettings, input) {
|
|
4964
|
+
console.info(CHAT_EMAIL_LOG, "send start", {
|
|
4965
|
+
conversationId: input.conversationId,
|
|
4966
|
+
intentCode: input.intentCode ?? null,
|
|
4967
|
+
contactEmail: input.contactEmail,
|
|
4968
|
+
contactName: input.contactName,
|
|
4969
|
+
explicitRecipients: input.recipients?.length ?? 0
|
|
4970
|
+
});
|
|
4971
|
+
const email = cms.getPlugin("email");
|
|
4972
|
+
if (!email?.send || !email.renderTemplate) {
|
|
4973
|
+
console.warn(CHAT_EMAIL_LOG, "send failed", {
|
|
4974
|
+
conversationId: input.conversationId,
|
|
4975
|
+
reason: "email_plugin_disabled"
|
|
4976
|
+
});
|
|
4977
|
+
return { sent: false, recipients: [], error: "Email plugin is not enabled" };
|
|
4978
|
+
}
|
|
4979
|
+
const recipients = input.recipients?.filter((r) => r.trim().includes("@")).map((r) => r.trim()) ?? resolveLeadRecipients(email, emailSettings);
|
|
4980
|
+
if (recipients.length === 0) {
|
|
4981
|
+
console.warn(CHAT_EMAIL_LOG, "send failed", {
|
|
4982
|
+
conversationId: input.conversationId,
|
|
4983
|
+
reason: "no_recipients"
|
|
4984
|
+
});
|
|
4985
|
+
return {
|
|
4986
|
+
sent: false,
|
|
4987
|
+
recipients: [],
|
|
4988
|
+
error: "No lead email recipients (configure intent Email To, CRM emails, or SMTP default To)"
|
|
4989
|
+
};
|
|
4990
|
+
}
|
|
4991
|
+
console.info(CHAT_EMAIL_LOG, "send recipients resolved", {
|
|
4992
|
+
conversationId: input.conversationId,
|
|
4993
|
+
recipients,
|
|
4994
|
+
source: input.recipients?.length ? "intent_email_to" : "crm_or_smtp_fallback"
|
|
4995
|
+
});
|
|
4996
|
+
const companyDetails = mergeEmailLayoutCompanyDetails(brandingSettings, emailSettings);
|
|
4997
|
+
const ctx = {
|
|
4998
|
+
...input,
|
|
4999
|
+
companyDetails: input.companyDetails ?? companyDetails
|
|
5000
|
+
};
|
|
5001
|
+
let anySent = false;
|
|
5002
|
+
for (const to of recipients) {
|
|
5003
|
+
await queueEmail(cms, {
|
|
5004
|
+
to,
|
|
5005
|
+
templateName: "chatLead",
|
|
5006
|
+
ctx
|
|
5007
|
+
});
|
|
5008
|
+
console.info(CHAT_EMAIL_LOG, "send queued", {
|
|
5009
|
+
conversationId: input.conversationId,
|
|
5010
|
+
to,
|
|
5011
|
+
template: "chatLead"
|
|
5012
|
+
});
|
|
5013
|
+
anySent = true;
|
|
5014
|
+
}
|
|
5015
|
+
console.info(CHAT_EMAIL_LOG, "send complete", {
|
|
5016
|
+
conversationId: input.conversationId,
|
|
5017
|
+
sent: anySent,
|
|
5018
|
+
recipientCount: recipients.length
|
|
5019
|
+
});
|
|
5020
|
+
return { sent: anySent, recipients };
|
|
5021
|
+
}
|
|
5022
|
+
|
|
4401
5023
|
// src/lib/media-folder-path.ts
|
|
4402
5024
|
function sanitizeMediaFolderPath(input) {
|
|
4403
5025
|
if (input == null) return "";
|
|
@@ -5877,16 +6499,19 @@ function normalizeChatModeSetting(raw) {
|
|
|
5877
6499
|
if (raw === "external" || raw === "llm") return raw;
|
|
5878
6500
|
return "whatsapp";
|
|
5879
6501
|
}
|
|
5880
|
-
async function
|
|
6502
|
+
async function loadSettingsGroupMap(dataSource, entityMap, group) {
|
|
5881
6503
|
if (!entityMap.configs) return {};
|
|
5882
6504
|
const repo = dataSource.getRepository(entityMap.configs);
|
|
5883
|
-
const rows = await repo.find({ where: { settings:
|
|
6505
|
+
const rows = await repo.find({ where: { settings: group, deleted: false } });
|
|
5884
6506
|
const out = {};
|
|
5885
6507
|
for (const row of rows) {
|
|
5886
6508
|
out[row.key] = row.value;
|
|
5887
6509
|
}
|
|
5888
6510
|
return out;
|
|
5889
6511
|
}
|
|
6512
|
+
async function loadLlmSettingsMap(dataSource, entityMap) {
|
|
6513
|
+
return loadSettingsGroupMap(dataSource, entityMap, "llm");
|
|
6514
|
+
}
|
|
5890
6515
|
function createChatHandlers(config) {
|
|
5891
6516
|
const { dataSource, entityMap, json, getCms } = config;
|
|
5892
6517
|
const contactRepo = () => dataSource.getRepository(entityMap.contacts);
|
|
@@ -5898,10 +6523,13 @@ function createChatHandlers(config) {
|
|
|
5898
6523
|
try {
|
|
5899
6524
|
const map = await loadLlmSettingsMap(dataSource, entityMap);
|
|
5900
6525
|
const mode = normalizeChatModeSetting(map.chatMode);
|
|
6526
|
+
const chatbotAgent = mode === "llm" && entityMap.llm_agents ? await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_CHATBOT, {
|
|
6527
|
+
enabledOnly: false
|
|
6528
|
+
}) : null;
|
|
5901
6529
|
const body = {
|
|
5902
6530
|
enabled: map.enabled !== "false",
|
|
5903
6531
|
chatMode: mode,
|
|
5904
|
-
agentSlug: mode === "llm" ?
|
|
6532
|
+
agentSlug: mode === "llm" ? chatbotAgent?.slug?.trim() || LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT] : "",
|
|
5905
6533
|
botName: map.botName ?? "",
|
|
5906
6534
|
icon: map.icon ?? "",
|
|
5907
6535
|
iconImageUrl: map.iconImageUrl ?? "",
|
|
@@ -5987,24 +6615,26 @@ function createChatHandlers(config) {
|
|
|
5987
6615
|
if (!llm?.chat) return json({ error: "LLM not configured" }, { status: 503 });
|
|
5988
6616
|
const llmSettings = await loadLlmSettingsMap(dataSource, entityMap);
|
|
5989
6617
|
const supportMode = normalizeChatModeSetting(llmSettings.chatMode);
|
|
5990
|
-
|
|
5991
|
-
if (!effectiveSlug && supportMode === "llm" && entityMap.llm_agents) {
|
|
5992
|
-
effectiveSlug = (llmSettings.attachedAgentSlug ?? "").trim();
|
|
5993
|
-
}
|
|
6618
|
+
const bodyAgentSlug = (body?.agentSlug ?? "").trim();
|
|
5994
6619
|
let agentRow = null;
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6620
|
+
let effectiveSlug = bodyAgentSlug;
|
|
6621
|
+
if (entityMap.llm_agents) {
|
|
6622
|
+
if (bodyAgentSlug) {
|
|
6623
|
+
const agentRepo = dataSource.getRepository(
|
|
6624
|
+
entityMap.llm_agents
|
|
6625
|
+
);
|
|
6626
|
+
agentRow = await agentRepo.findOne({
|
|
6627
|
+
where: { slug: bodyAgentSlug, deleted: false, enabled: true }
|
|
6628
|
+
});
|
|
6629
|
+
if (!agentRow) {
|
|
6630
|
+
return json({ error: "Agent not found or disabled", agentSlug: bodyAgentSlug }, { status: 404 });
|
|
6631
|
+
}
|
|
6632
|
+
} else if (supportMode === "llm") {
|
|
6633
|
+
agentRow = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_CHATBOT);
|
|
6634
|
+
effectiveSlug = agentRow?.slug?.trim() || LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
|
|
6007
6635
|
}
|
|
6636
|
+
} else if (bodyAgentSlug) {
|
|
6637
|
+
return json({ error: "LLM agents are not configured on this deployment" }, { status: 400 });
|
|
6008
6638
|
}
|
|
6009
6639
|
console.info(RAG_LOG, "step 1 | resolve agent", {
|
|
6010
6640
|
agentSlug: effectiveSlug || "(none)",
|
|
@@ -6102,6 +6732,139 @@ function createChatHandlers(config) {
|
|
|
6102
6732
|
}
|
|
6103
6733
|
const historyRaw = (conv.messages ?? []).sort((a, b) => new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime()).map((m) => ({ role: m.role, content: m.content }));
|
|
6104
6734
|
const history = historyBeforeCurrentUser(historyRaw, message);
|
|
6735
|
+
const notifyEmailAgent = await findLlmAgentByScope(
|
|
6736
|
+
dataSource,
|
|
6737
|
+
entityMap,
|
|
6738
|
+
LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT,
|
|
6739
|
+
{ enabledOnly: false }
|
|
6740
|
+
);
|
|
6741
|
+
const emailTool = resolveChatEmailToolSettings(llmSettings, {
|
|
6742
|
+
chatbotValidationRules: agentRow?.validationRules ?? null,
|
|
6743
|
+
notifyAgent: notifyEmailAgent ? {
|
|
6744
|
+
systemInstruction: notifyEmailAgent.systemInstruction,
|
|
6745
|
+
validationRules: notifyEmailAgent.validationRules
|
|
6746
|
+
} : null
|
|
6747
|
+
});
|
|
6748
|
+
const emailPlugin = cms.getPlugin("email");
|
|
6749
|
+
const convLeadSent = conv.leadEmailSentAt;
|
|
6750
|
+
const chatAgentFn = llm.chatAgent?.bind(llm);
|
|
6751
|
+
console.info(CHAT_EMAIL_LOG, "pipeline check", {
|
|
6752
|
+
conversationId,
|
|
6753
|
+
enabled: emailTool.enabled,
|
|
6754
|
+
intentCount: emailTool.intents.length,
|
|
6755
|
+
chatbotAgentId: agentRow?.id ?? null,
|
|
6756
|
+
chatbotAgentSlug: agentRow?.slug ?? null,
|
|
6757
|
+
notifyEmailAgentId: notifyEmailAgent?.id ?? null,
|
|
6758
|
+
notifyEmailAgentSlug: notifyEmailAgent?.slug ?? null,
|
|
6759
|
+
emailPluginPresent: Boolean(emailPlugin),
|
|
6760
|
+
chatAgentFnPresent: Boolean(chatAgentFn),
|
|
6761
|
+
leadEmailAlreadySent: Boolean(convLeadSent),
|
|
6762
|
+
mergedPromptIncludesNotify: Boolean(notifyEmailAgent?.systemInstruction?.trim())
|
|
6763
|
+
});
|
|
6764
|
+
if (!emailTool.enabled) {
|
|
6765
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6766
|
+
conversationId,
|
|
6767
|
+
reason: "email_tool_disabled"
|
|
6768
|
+
});
|
|
6769
|
+
} else if (emailTool.intents.length === 0) {
|
|
6770
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6771
|
+
conversationId,
|
|
6772
|
+
reason: "no_intents_configured"
|
|
6773
|
+
});
|
|
6774
|
+
} else if (!emailPlugin) {
|
|
6775
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6776
|
+
conversationId,
|
|
6777
|
+
reason: "email_plugin_missing"
|
|
6778
|
+
});
|
|
6779
|
+
} else if (!chatAgentFn) {
|
|
6780
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6781
|
+
conversationId,
|
|
6782
|
+
reason: "llm_chat_agent_unavailable"
|
|
6783
|
+
});
|
|
6784
|
+
} else if (convLeadSent) {
|
|
6785
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6786
|
+
conversationId,
|
|
6787
|
+
reason: "lead_email_already_sent",
|
|
6788
|
+
leadEmailSentAt: convLeadSent
|
|
6789
|
+
});
|
|
6790
|
+
} else {
|
|
6791
|
+
const contactId = conv.contactId;
|
|
6792
|
+
const contactRow = await contactRepo().findOne({
|
|
6793
|
+
where: { id: contactId }
|
|
6794
|
+
});
|
|
6795
|
+
if (!contactRow) {
|
|
6796
|
+
console.warn(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6797
|
+
conversationId,
|
|
6798
|
+
reason: "contact_not_found",
|
|
6799
|
+
contactId
|
|
6800
|
+
});
|
|
6801
|
+
} else {
|
|
6802
|
+
const c = contactRow;
|
|
6803
|
+
try {
|
|
6804
|
+
const intent = await detectChatLeadIntent({ chatAgent: chatAgentFn }, {
|
|
6805
|
+
settings: emailTool,
|
|
6806
|
+
message,
|
|
6807
|
+
history,
|
|
6808
|
+
contact: { name: c.name, email: c.email, phone: c.phone },
|
|
6809
|
+
model: agentRow?.model?.trim() || notifyEmailAgent?.model?.trim() || void 0
|
|
6810
|
+
});
|
|
6811
|
+
if (!intent.intent || !intent.emailTo) {
|
|
6812
|
+
console.info(CHAT_EMAIL_LOG, "no email sent", {
|
|
6813
|
+
conversationId,
|
|
6814
|
+
reason: intent.intent ? "missing_email_to" : "no_intent_match",
|
|
6815
|
+
intentLabel: intent.intentLabel,
|
|
6816
|
+
classifierReason: intent.reason
|
|
6817
|
+
});
|
|
6818
|
+
} else {
|
|
6819
|
+
const intentRecipients = parseIntentRecipientEmails(intent.emailTo);
|
|
6820
|
+
console.info(CHAT_EMAIL_LOG, "intent matched \u2014 sending", {
|
|
6821
|
+
conversationId,
|
|
6822
|
+
intent: intent.intent,
|
|
6823
|
+
emailTo: intent.emailTo,
|
|
6824
|
+
parsedRecipientCount: intentRecipients.length,
|
|
6825
|
+
recipients: intentRecipients
|
|
6826
|
+
});
|
|
6827
|
+
const emailSettings = await loadSettingsGroupMap(dataSource, entityMap, "email");
|
|
6828
|
+
const brandingSettings = await loadSettingsGroupMap(dataSource, entityMap, "branding");
|
|
6829
|
+
const companyDetails = mergeEmailLayoutCompanyDetails(brandingSettings, emailSettings);
|
|
6830
|
+
const leadResult = await sendChatLeadEmail(cms, emailSettings, brandingSettings, {
|
|
6831
|
+
contactName: String(c.name ?? "").trim() || "Visitor",
|
|
6832
|
+
contactEmail: String(c.email ?? "").trim(),
|
|
6833
|
+
contactPhone: c.phone ?? null,
|
|
6834
|
+
conversationId,
|
|
6835
|
+
latestMessage: message,
|
|
6836
|
+
intentCode: intent.intent,
|
|
6837
|
+
intentReason: intent.intentLabel || intent.reason,
|
|
6838
|
+
transcript: buildTranscriptForLeadEmail(history, message),
|
|
6839
|
+
companyDetails,
|
|
6840
|
+
recipients: intentRecipients.length > 0 ? intentRecipients : void 0
|
|
6841
|
+
});
|
|
6842
|
+
if (leadResult.sent) {
|
|
6843
|
+
await convRepo().update(conversationId, {
|
|
6844
|
+
leadEmailSentAt: /* @__PURE__ */ new Date()
|
|
6845
|
+
});
|
|
6846
|
+
console.info(CHAT_EMAIL_LOG, "lead email recorded", {
|
|
6847
|
+
conversationId,
|
|
6848
|
+
sent: true,
|
|
6849
|
+
recipients: leadResult.recipients
|
|
6850
|
+
});
|
|
6851
|
+
} else {
|
|
6852
|
+
console.warn(CHAT_EMAIL_LOG, "lead email not sent", {
|
|
6853
|
+
conversationId,
|
|
6854
|
+
sent: false,
|
|
6855
|
+
error: leadResult.error ?? "unknown",
|
|
6856
|
+
recipients: leadResult.recipients
|
|
6857
|
+
});
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
} catch (intentErr) {
|
|
6861
|
+
console.warn(CHAT_EMAIL_LOG, "pipeline error", {
|
|
6862
|
+
conversationId,
|
|
6863
|
+
error: intentErr instanceof Error ? intentErr.message : String(intentErr)
|
|
6864
|
+
});
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6105
6868
|
let content;
|
|
6106
6869
|
const ragContext = contextParts.length > 0 ? contextParts.join("\n\n") : void 0;
|
|
6107
6870
|
console.info(RAG_LOG, "step 7 | final context", {
|
|
@@ -6112,9 +6875,10 @@ function createChatHandlers(config) {
|
|
|
6112
6875
|
});
|
|
6113
6876
|
if (agentRow && llm.chatAgent) {
|
|
6114
6877
|
const fromAgent = llmAgentToChatAgentOptions(agentRow);
|
|
6115
|
-
const systemPrompt =
|
|
6878
|
+
const systemPrompt = buildChatbotSystemPromptWithEmailTool(
|
|
6116
6879
|
fromAgent.systemPrompt,
|
|
6117
|
-
parsedValidation.guardrailsForPrompt
|
|
6880
|
+
parsedValidation.guardrailsForPrompt,
|
|
6881
|
+
emailTool
|
|
6118
6882
|
);
|
|
6119
6883
|
const res = await llm.chatAgent({
|
|
6120
6884
|
...fromAgent,
|
|
@@ -6132,11 +6896,12 @@ ${contextParts.join("\n\n")}` : "";
|
|
|
6132
6896
|
const defaultSystem = "You are a helpful assistant for the company. If you do not have specific information, say so.";
|
|
6133
6897
|
let systemContent;
|
|
6134
6898
|
if (agentRow) {
|
|
6135
|
-
const
|
|
6136
|
-
systemContent =
|
|
6137
|
-
|
|
6138
|
-
parsedValidation.guardrailsForPrompt
|
|
6139
|
-
|
|
6899
|
+
const mergedBase = [agentRow.systemInstruction?.trim(), ragSystem].filter(Boolean).join("\n\n");
|
|
6900
|
+
systemContent = buildChatbotSystemPromptWithEmailTool(
|
|
6901
|
+
mergedBase || defaultSystem,
|
|
6902
|
+
parsedValidation.guardrailsForPrompt,
|
|
6903
|
+
emailTool
|
|
6904
|
+
) || defaultSystem;
|
|
6140
6905
|
} else {
|
|
6141
6906
|
systemContent = ragSystem || defaultSystem;
|
|
6142
6907
|
}
|
|
@@ -8652,6 +9417,7 @@ var Order = class {
|
|
|
8652
9417
|
id;
|
|
8653
9418
|
vendorId;
|
|
8654
9419
|
orderNumber;
|
|
9420
|
+
qrToken;
|
|
8655
9421
|
orderKind;
|
|
8656
9422
|
parentOrderId;
|
|
8657
9423
|
contactId;
|
|
@@ -8689,6 +9455,9 @@ __decorateClass([
|
|
|
8689
9455
|
__decorateClass([
|
|
8690
9456
|
(0, import_typeorm25.Column)("varchar")
|
|
8691
9457
|
], Order.prototype, "orderNumber", 2);
|
|
9458
|
+
__decorateClass([
|
|
9459
|
+
(0, import_typeorm25.Column)("varchar", { unique: true, nullable: true })
|
|
9460
|
+
], Order.prototype, "qrToken", 2);
|
|
8692
9461
|
__decorateClass([
|
|
8693
9462
|
(0, import_typeorm25.Column)("varchar", { default: "sale" })
|
|
8694
9463
|
], Order.prototype, "orderKind", 2);
|
|
@@ -8917,6 +9686,7 @@ var ChatConversation = class {
|
|
|
8917
9686
|
contactId;
|
|
8918
9687
|
createdAt;
|
|
8919
9688
|
updatedAt;
|
|
9689
|
+
leadEmailSentAt;
|
|
8920
9690
|
contact;
|
|
8921
9691
|
messages;
|
|
8922
9692
|
};
|
|
@@ -8932,6 +9702,9 @@ __decorateClass([
|
|
|
8932
9702
|
__decorateClass([
|
|
8933
9703
|
(0, import_typeorm28.Column)({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
|
8934
9704
|
], ChatConversation.prototype, "updatedAt", 2);
|
|
9705
|
+
__decorateClass([
|
|
9706
|
+
(0, import_typeorm28.Column)({ type: "timestamp", nullable: true })
|
|
9707
|
+
], ChatConversation.prototype, "leadEmailSentAt", 2);
|
|
8935
9708
|
__decorateClass([
|
|
8936
9709
|
(0, import_typeorm28.ManyToOne)(() => Contact, (c) => c.chatConversations, { onDelete: "CASCADE" }),
|
|
8937
9710
|
(0, import_typeorm28.JoinColumn)({ name: "contactId" })
|
|
@@ -11135,7 +11908,6 @@ var CMS_ENTITY_MAP = {
|
|
|
11135
11908
|
var import_rss_parser = __toESM(require("rss-parser"), 1);
|
|
11136
11909
|
|
|
11137
11910
|
// src/plugins/blog-generator/blog-generator-agent-defaults.ts
|
|
11138
|
-
var BLOG_GENERATOR_LLM_AGENT_SLUG = "blog-generator";
|
|
11139
11911
|
var BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR = "---BLOG_GENERATOR_NEXT---";
|
|
11140
11912
|
var BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION = `You are a professional financial and business content writer.
|
|
11141
11913
|
|
|
@@ -11215,7 +11987,6 @@ var BLOG_GENERATOR_DEFAULT_VALIDATION_RULES = JSON.stringify(
|
|
|
11215
11987
|
);
|
|
11216
11988
|
|
|
11217
11989
|
// src/plugins/blog-generator/blog-generator-metadata-defaults.ts
|
|
11218
|
-
var BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG = "blog-generator-metadata";
|
|
11219
11990
|
var BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
|
|
11220
11991
|
{
|
|
11221
11992
|
maxUserChars: 5e5,
|
|
@@ -11226,7 +11997,6 @@ var BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
|
|
|
11226
11997
|
);
|
|
11227
11998
|
|
|
11228
11999
|
// src/plugins/blog-generator/blog-generator-social-defaults.ts
|
|
11229
|
-
var BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG = "blog-generator-social";
|
|
11230
12000
|
var BLOG_SOCIAL_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
|
|
11231
12001
|
{
|
|
11232
12002
|
maxUserChars: 5e5,
|
|
@@ -12463,7 +13233,7 @@ ${excerpt}`).trim().slice(0, 2200);
|
|
|
12463
13233
|
}
|
|
12464
13234
|
|
|
12465
13235
|
// src/api/job-schedule-handlers.ts
|
|
12466
|
-
var
|
|
13236
|
+
var import_crypto2 = require("crypto");
|
|
12467
13237
|
|
|
12468
13238
|
// src/plugins/jobs/schedule-cron.ts
|
|
12469
13239
|
function pgBossScheduleNameForId(scheduleId) {
|
|
@@ -12698,7 +13468,7 @@ function createJobScheduleHandlers(apiConfig) {
|
|
|
12698
13468
|
const repo = dataSource.getRepository(ScheduleEntity);
|
|
12699
13469
|
let row = repo.create({
|
|
12700
13470
|
...patch,
|
|
12701
|
-
pgBossScheduleName: `pending-${(0,
|
|
13471
|
+
pgBossScheduleName: `pending-${(0, import_crypto2.randomUUID)()}`
|
|
12702
13472
|
});
|
|
12703
13473
|
row = await repo.save(row);
|
|
12704
13474
|
row.pgBossScheduleName = pgBossScheduleNameForId(row.id);
|
|
@@ -13353,28 +14123,24 @@ function createCmsApiHandler(config) {
|
|
|
13353
14123
|
let socialLlmAgentResolution = null;
|
|
13354
14124
|
let metadataRow = null;
|
|
13355
14125
|
let socialRow = null;
|
|
14126
|
+
let blogCreationRow = null;
|
|
13356
14127
|
if (entityMap.llm_agents) {
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13360
|
-
});
|
|
13361
|
-
if (agentRow) {
|
|
13362
|
-
const o = llmAgentToChatAgentOptions(agentRow);
|
|
14128
|
+
blogCreationRow = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_BLOG_CREATION);
|
|
14129
|
+
if (blogCreationRow) {
|
|
14130
|
+
const o = llmAgentToChatAgentOptions(blogCreationRow);
|
|
13363
14131
|
llmAgentChatOptions = {
|
|
13364
14132
|
model: o.model,
|
|
13365
14133
|
temperature: o.temperature,
|
|
13366
14134
|
max_tokens: o.max_tokens
|
|
13367
14135
|
};
|
|
13368
14136
|
llmAgentResolution = {
|
|
13369
|
-
slug:
|
|
13370
|
-
model:
|
|
13371
|
-
temperature:
|
|
13372
|
-
maxTokens:
|
|
14137
|
+
slug: blogCreationRow.slug,
|
|
14138
|
+
model: blogCreationRow.model?.trim() || null,
|
|
14139
|
+
temperature: blogCreationRow.temperature ?? null,
|
|
14140
|
+
maxTokens: blogCreationRow.maxTokens ?? null
|
|
13373
14141
|
};
|
|
13374
14142
|
}
|
|
13375
|
-
metadataRow = await
|
|
13376
|
-
where: { slug: BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, deleted: false, enabled: true }
|
|
13377
|
-
});
|
|
14143
|
+
metadataRow = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_BLOG_METADATA);
|
|
13378
14144
|
if (metadataRow) {
|
|
13379
14145
|
const mo = llmAgentToChatAgentOptions(metadataRow);
|
|
13380
14146
|
metadataLlmAgentChatOptions = {
|
|
@@ -13383,15 +14149,13 @@ function createCmsApiHandler(config) {
|
|
|
13383
14149
|
max_tokens: mo.max_tokens
|
|
13384
14150
|
};
|
|
13385
14151
|
metadataLlmAgentResolution = {
|
|
13386
|
-
slug:
|
|
14152
|
+
slug: metadataRow.slug,
|
|
13387
14153
|
model: metadataRow.model?.trim() || null,
|
|
13388
14154
|
temperature: metadataRow.temperature ?? null,
|
|
13389
14155
|
maxTokens: metadataRow.maxTokens ?? null
|
|
13390
14156
|
};
|
|
13391
14157
|
}
|
|
13392
|
-
socialRow = await
|
|
13393
|
-
where: { slug: BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG, deleted: false, enabled: true }
|
|
13394
|
-
});
|
|
14158
|
+
socialRow = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST);
|
|
13395
14159
|
if (socialRow) {
|
|
13396
14160
|
const so = llmAgentToChatAgentOptions(socialRow);
|
|
13397
14161
|
socialLlmAgentChatOptions = {
|
|
@@ -13400,7 +14164,7 @@ function createCmsApiHandler(config) {
|
|
|
13400
14164
|
max_tokens: so.max_tokens
|
|
13401
14165
|
};
|
|
13402
14166
|
socialLlmAgentResolution = {
|
|
13403
|
-
slug:
|
|
14167
|
+
slug: socialRow.slug,
|
|
13404
14168
|
model: socialRow.model?.trim() || null,
|
|
13405
14169
|
temperature: socialRow.temperature ?? null,
|
|
13406
14170
|
maxTokens: socialRow.maxTokens ?? null
|
|
@@ -13424,8 +14188,8 @@ function createCmsApiHandler(config) {
|
|
|
13424
14188
|
const out = await svc.generateBlogMarkdownFromRss({
|
|
13425
14189
|
llm,
|
|
13426
14190
|
rssUrls,
|
|
13427
|
-
systemInstruction,
|
|
13428
|
-
validationRules,
|
|
14191
|
+
systemInstruction: systemInstruction ?? blogCreationRow?.systemInstruction?.trim() ?? void 0,
|
|
14192
|
+
validationRules: validationRules ?? blogCreationRow?.validationRules?.trim() ?? void 0,
|
|
13429
14193
|
categoryNamesHint: categoryRows.map((c) => c.name),
|
|
13430
14194
|
tagNamesHint: tagNames,
|
|
13431
14195
|
llmAgentChatOptions,
|
|
@@ -13910,6 +14674,22 @@ function createCmsApiHandler(config) {
|
|
|
13910
14674
|
return config.json({ error: message }, { status: 500 });
|
|
13911
14675
|
}
|
|
13912
14676
|
}
|
|
14677
|
+
if (path2[0] === "track" && path2.length === 2 && m === "GET") {
|
|
14678
|
+
const token = path2[1];
|
|
14679
|
+
if (!token) return config.json({ error: "Token required" }, { status: 400 });
|
|
14680
|
+
try {
|
|
14681
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
14682
|
+
const order = await orderRepo.findOne({
|
|
14683
|
+
where: { qrToken: token, deleted: false },
|
|
14684
|
+
relations: ["contact", "items", "items.product"]
|
|
14685
|
+
});
|
|
14686
|
+
if (!order) return config.json({ error: "Order not found" }, { status: 404 });
|
|
14687
|
+
return config.json(order);
|
|
14688
|
+
} catch (err) {
|
|
14689
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
14690
|
+
return config.json({ error: message }, { status: 500 });
|
|
14691
|
+
}
|
|
14692
|
+
}
|
|
13913
14693
|
if (path2.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
13914
14694
|
const resource = resolveResource(path2[0]);
|
|
13915
14695
|
if (!crudResources.includes(resource)) {
|
|
@@ -14213,7 +14993,7 @@ async function queueSms(cms, payload) {
|
|
|
14213
14993
|
}
|
|
14214
14994
|
|
|
14215
14995
|
// src/lib/otp-challenge.ts
|
|
14216
|
-
var
|
|
14996
|
+
var import_crypto3 = require("crypto");
|
|
14217
14997
|
var import_typeorm62 = require("typeorm");
|
|
14218
14998
|
var OTP_TTL_MS = 10 * 60 * 1e3;
|
|
14219
14999
|
var MAX_SENDS_PER_HOUR = 5;
|
|
@@ -14222,19 +15002,19 @@ function getPepper(explicit) {
|
|
|
14222
15002
|
return (explicit || process.env.OTP_PEPPER || process.env.NEXTAUTH_SECRET || "dev-otp-pepper").trim();
|
|
14223
15003
|
}
|
|
14224
15004
|
function hashOtpCode(code, purpose, identifier, pepper) {
|
|
14225
|
-
return (0,
|
|
15005
|
+
return (0, import_crypto3.createHmac)("sha256", getPepper(pepper)).update(`${purpose}|${identifier}|${code}`).digest("hex");
|
|
14226
15006
|
}
|
|
14227
15007
|
function verifyOtpCodeHash(code, storedHash, purpose, identifier, pepper) {
|
|
14228
15008
|
const h = hashOtpCode(code, purpose, identifier, pepper);
|
|
14229
15009
|
try {
|
|
14230
|
-
return (0,
|
|
15010
|
+
return (0, import_crypto3.timingSafeEqual)(Buffer.from(h, "utf8"), Buffer.from(storedHash, "utf8"));
|
|
14231
15011
|
} catch {
|
|
14232
15012
|
return false;
|
|
14233
15013
|
}
|
|
14234
15014
|
}
|
|
14235
15015
|
function generateNumericOtp(length = 6) {
|
|
14236
15016
|
const max = 10 ** length;
|
|
14237
|
-
return (0,
|
|
15017
|
+
return (0, import_crypto3.randomInt)(0, max).toString().padStart(length, "0");
|
|
14238
15018
|
}
|
|
14239
15019
|
function normalizePhoneE164(raw, defaultCountryCode) {
|
|
14240
15020
|
const t = raw.trim();
|
|
@@ -15111,8 +15891,8 @@ function createStorefrontApiHandler(config) {
|
|
|
15111
15891
|
let emailVerificationSent = false;
|
|
15112
15892
|
if (requireEmailVerification && getCms) {
|
|
15113
15893
|
try {
|
|
15114
|
-
const
|
|
15115
|
-
const rawToken =
|
|
15894
|
+
const crypto3 = await import("crypto");
|
|
15895
|
+
const rawToken = crypto3.randomBytes(32).toString("hex");
|
|
15116
15896
|
const expiresAt = new Date(Date.now() + SIGNUP_VERIFY_EXPIRY_HOURS * 60 * 60 * 1e3);
|
|
15117
15897
|
await tokenRepo().save(
|
|
15118
15898
|
tokenRepo().create({ email, token: rawToken, expiresAt })
|