@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.js
CHANGED
|
@@ -1157,6 +1157,65 @@ function resolveVendorIdForContactCheck(scope, bodyOrRow) {
|
|
|
1157
1157
|
return Number.isFinite(n) ? n : null;
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
+
// src/api/crud.ts
|
|
1161
|
+
import crypto2 from "crypto";
|
|
1162
|
+
|
|
1163
|
+
// src/plugins/llm/llm-agent-scope.ts
|
|
1164
|
+
var LLM_AGENT_SCOPE_CHATBOT = "chatbot";
|
|
1165
|
+
var LLM_AGENT_SCOPE_BLOG_CREATION = "blog_creation";
|
|
1166
|
+
var LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST = "social_media_post";
|
|
1167
|
+
var LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT = "email_intent_chatbot";
|
|
1168
|
+
var LLM_AGENT_SCOPE_BLOG_METADATA = "blog_metadata";
|
|
1169
|
+
var LLM_AGENT_SCOPES = [
|
|
1170
|
+
LLM_AGENT_SCOPE_CHATBOT,
|
|
1171
|
+
LLM_AGENT_SCOPE_BLOG_CREATION,
|
|
1172
|
+
LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST,
|
|
1173
|
+
LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT,
|
|
1174
|
+
LLM_AGENT_SCOPE_BLOG_METADATA
|
|
1175
|
+
];
|
|
1176
|
+
var LLM_AGENT_DEFAULT_SLUG_BY_SCOPE = {
|
|
1177
|
+
[LLM_AGENT_SCOPE_CHATBOT]: "site-chat-assistant",
|
|
1178
|
+
[LLM_AGENT_SCOPE_BLOG_CREATION]: "blog-generator",
|
|
1179
|
+
[LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]: "blog-generator-social",
|
|
1180
|
+
[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]: "email-intent-chatbot",
|
|
1181
|
+
[LLM_AGENT_SCOPE_BLOG_METADATA]: "blog-generator-metadata"
|
|
1182
|
+
};
|
|
1183
|
+
var LLM_AGENT_DEFAULT_NAME_BY_SCOPE = {
|
|
1184
|
+
[LLM_AGENT_SCOPE_CHATBOT]: "Site Chat Assistant",
|
|
1185
|
+
[LLM_AGENT_SCOPE_BLOG_CREATION]: "Blog Generator",
|
|
1186
|
+
[LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]: "Blog Generator (Social)",
|
|
1187
|
+
[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]: "Chat Lead Intent Classifier",
|
|
1188
|
+
[LLM_AGENT_SCOPE_BLOG_METADATA]: "Blog Generator (Metadata)"
|
|
1189
|
+
};
|
|
1190
|
+
var SITE_CHAT_ASSISTANT_SLUG = LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
|
|
1191
|
+
var SITE_CHAT_ASSISTANT_NAME = LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
|
|
1192
|
+
function isLlmAgentScope(value) {
|
|
1193
|
+
if (!value?.trim()) return false;
|
|
1194
|
+
return LLM_AGENT_SCOPES.includes(value.trim());
|
|
1195
|
+
}
|
|
1196
|
+
var BLOG_LLM_AGENT_SLUGS = [
|
|
1197
|
+
LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_BLOG_CREATION],
|
|
1198
|
+
LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_BLOG_METADATA],
|
|
1199
|
+
LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]
|
|
1200
|
+
];
|
|
1201
|
+
|
|
1202
|
+
// src/plugins/llm/llm-agent-scope-crud.ts
|
|
1203
|
+
async function validateLlmAgentScopeForWrite(dataSource, entityMap, scope, excludeId) {
|
|
1204
|
+
if (scope == null || scope === "") return null;
|
|
1205
|
+
if (typeof scope !== "string" || !isLlmAgentScope(scope)) {
|
|
1206
|
+
return `Invalid agent scope. Allowed: chatbot, blog_creation, social_media_post, email_intent_chatbot, blog_metadata.`;
|
|
1207
|
+
}
|
|
1208
|
+
const entity = entityMap.llm_agents;
|
|
1209
|
+
if (!entity) return null;
|
|
1210
|
+
const repo = dataSource.getRepository(entity);
|
|
1211
|
+
const existing = await repo.findOne({
|
|
1212
|
+
where: { scope, deleted: false }
|
|
1213
|
+
});
|
|
1214
|
+
if (!existing) return null;
|
|
1215
|
+
if (excludeId != null && existing.id === excludeId) return null;
|
|
1216
|
+
return `An active agent already uses scope "${scope}".`;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1160
1219
|
// src/api/crud.ts
|
|
1161
1220
|
var CRUD_LOG = "[cms-crud]";
|
|
1162
1221
|
function logCrudClientError(op, detail) {
|
|
@@ -1373,6 +1432,7 @@ function buildListFilterAndFromSearchParams(repo, searchParams) {
|
|
|
1373
1432
|
if (name === "deleted" || name === "deletedAt" || name === "deletedBy") continue;
|
|
1374
1433
|
if (!isListStringColumn(col)) continue;
|
|
1375
1434
|
if (Object.prototype.hasOwnProperty.call(and, name)) continue;
|
|
1435
|
+
if (name === "scope") continue;
|
|
1376
1436
|
const raw = searchParams.get(name)?.trim();
|
|
1377
1437
|
if (!raw) continue;
|
|
1378
1438
|
and[name] = ILike(`%${raw}%`);
|
|
@@ -1402,6 +1462,10 @@ function buildExactListParamWhere(repo, searchParams) {
|
|
|
1402
1462
|
extraWhere[name] = raw === "true";
|
|
1403
1463
|
}
|
|
1404
1464
|
}
|
|
1465
|
+
const scopeParam = searchParams.get("scope")?.trim();
|
|
1466
|
+
if (scopeParam && columnNames.has("scope")) {
|
|
1467
|
+
extraWhere.scope = scopeParam;
|
|
1468
|
+
}
|
|
1405
1469
|
return extraWhere;
|
|
1406
1470
|
}
|
|
1407
1471
|
function mergeDeletedFalseWhere(repo, where) {
|
|
@@ -1613,6 +1677,12 @@ function discountEvaluateRule(rule, cartLines, cartTotal) {
|
|
|
1613
1677
|
case "minAmount":
|
|
1614
1678
|
return discountCompare(cartTotal, rule.comparisonOperator, ruleValue);
|
|
1615
1679
|
case "quantity": {
|
|
1680
|
+
if (rule.subType === "productId" && rule.value?.productId) {
|
|
1681
|
+
const targetProductId = Number(rule.value.productId);
|
|
1682
|
+
const requiredQty = Number(rule.value.v);
|
|
1683
|
+
const productQty = cartLines.filter((l) => l.productId === targetProductId).reduce((s, l) => s + l.quantity, 0);
|
|
1684
|
+
return discountCompare(productQty, rule.comparisonOperator, requiredQty);
|
|
1685
|
+
}
|
|
1616
1686
|
const totalQty = cartLines.reduce((s, l) => s + l.quantity, 0);
|
|
1617
1687
|
return discountCompare(totalQty, rule.comparisonOperator, ruleValue);
|
|
1618
1688
|
}
|
|
@@ -1676,6 +1746,10 @@ function discountExtractBuyProductId(nodes) {
|
|
|
1676
1746
|
const n = Number(v);
|
|
1677
1747
|
if (Number.isFinite(n) && n > 0) return n;
|
|
1678
1748
|
}
|
|
1749
|
+
if (node.conditionType === "rule" && node.type === "quantity" && node.subType === "productId") {
|
|
1750
|
+
const n = Number(node.value?.productId);
|
|
1751
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
1752
|
+
}
|
|
1679
1753
|
if (node.children?.length) {
|
|
1680
1754
|
const found = discountExtractBuyProductId(node.children);
|
|
1681
1755
|
if (found !== null) return found;
|
|
@@ -2386,6 +2460,25 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
2386
2460
|
if (pe) return pe;
|
|
2387
2461
|
return null;
|
|
2388
2462
|
}
|
|
2463
|
+
async function tryAssignSingleVendorOnAdminCreate(resource, scope, persistBody) {
|
|
2464
|
+
if (!resourceUsesVendorScope(resource) || scope.type !== "all") return;
|
|
2465
|
+
const currentVendorId = Number(persistBody.vendorId);
|
|
2466
|
+
if (Number.isFinite(currentVendorId) && currentVendorId > 0) return;
|
|
2467
|
+
if (!entityMap.vendors) return;
|
|
2468
|
+
const vendorRepo = dataSource.getRepository(entityMap.vendors);
|
|
2469
|
+
const where = mergeDeletedFalseWhere(vendorRepo, {});
|
|
2470
|
+
const rows = await vendorRepo.find({
|
|
2471
|
+
where,
|
|
2472
|
+
order: { id: "ASC" },
|
|
2473
|
+
take: 2
|
|
2474
|
+
});
|
|
2475
|
+
if (rows.length === 1) {
|
|
2476
|
+
const onlyVendorId = Number(rows[0].id);
|
|
2477
|
+
if (Number.isFinite(onlyVendorId) && onlyVendorId > 0) {
|
|
2478
|
+
persistBody.vendorId = onlyVendorId;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2389
2482
|
return {
|
|
2390
2483
|
async GET(req, resource) {
|
|
2391
2484
|
const authError = await authz(req, resource, "read");
|
|
@@ -2901,6 +2994,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
2901
2994
|
const couponCode = String(body.couponCode).trim().toUpperCase();
|
|
2902
2995
|
const persistBody2 = pickColumnUpdates(repo2, { ...body, couponCode });
|
|
2903
2996
|
const scopeDiscount = await resolveScope();
|
|
2997
|
+
await tryAssignSingleVendorOnAdminCreate(resource, scopeDiscount, persistBody2);
|
|
2904
2998
|
const vendorIdCheck2 = requireVendorIdForScopedCreate(
|
|
2905
2999
|
resource,
|
|
2906
3000
|
persistBody2,
|
|
@@ -2978,6 +3072,10 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
2978
3072
|
});
|
|
2979
3073
|
return json({ error: "Invalid request payload" }, { status: 400 });
|
|
2980
3074
|
}
|
|
3075
|
+
if (resource === "llm_agents" && "scope" in persistBody) {
|
|
3076
|
+
const scopeErr = await validateLlmAgentScopeForWrite(dataSource, entityMap, persistBody.scope);
|
|
3077
|
+
if (scopeErr) return json({ error: scopeErr }, { status: 400 });
|
|
3078
|
+
}
|
|
2981
3079
|
if (resource === "products") {
|
|
2982
3080
|
if ("sku" in persistBody) {
|
|
2983
3081
|
const skuNorm = normalizeProductSku(persistBody.sku);
|
|
@@ -3093,6 +3191,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
3093
3191
|
);
|
|
3094
3192
|
}
|
|
3095
3193
|
const scopeCreate = await resolveScope();
|
|
3194
|
+
await tryAssignSingleVendorOnAdminCreate(resource, scopeCreate, persistBody);
|
|
3096
3195
|
const vendorIdCheck = requireVendorIdForScopedCreate(resource, persistBody, scopeCreate, repo, body);
|
|
3097
3196
|
if (!vendorIdCheck.ok) {
|
|
3098
3197
|
return json({ error: vendorIdCheck.error }, { status: vendorIdCheck.status });
|
|
@@ -3135,6 +3234,7 @@ function createCrudHandler(dataSource, entityMap, options) {
|
|
|
3135
3234
|
const randomPart = Math.floor(1e4 + Math.random() * 9e4);
|
|
3136
3235
|
persistBody.contactId = contact.id;
|
|
3137
3236
|
persistBody.orderNumber = `ORD00${randomPart}`;
|
|
3237
|
+
persistBody.qrToken = crypto2.randomBytes(16).toString("hex");
|
|
3138
3238
|
}
|
|
3139
3239
|
created = await repo.save(repo.create(persistBody));
|
|
3140
3240
|
if (resource === "orders") {
|
|
@@ -3903,6 +4003,15 @@ function createCrudByIdHandler(dataSource, entityMap, options) {
|
|
|
3903
4003
|
const t = updatePayload.type;
|
|
3904
4004
|
if (t === "" || t === "none" || t == null) updatePayload.type = null;
|
|
3905
4005
|
}
|
|
4006
|
+
if (resource === "llm_agents" && "scope" in updatePayload) {
|
|
4007
|
+
const scopeErr = await validateLlmAgentScopeForWrite(
|
|
4008
|
+
dataSource,
|
|
4009
|
+
entityMap,
|
|
4010
|
+
updatePayload.scope,
|
|
4011
|
+
numericId
|
|
4012
|
+
);
|
|
4013
|
+
if (scopeErr) return json({ error: scopeErr }, { status: 400 });
|
|
4014
|
+
}
|
|
3906
4015
|
if ((resource === "orders" || resource === "payments") && "contactId" in updatePayload && updatePayload.contactId != null && entityMap.vendor_customers) {
|
|
3907
4016
|
const existingRow = await repo.findOne({
|
|
3908
4017
|
where: { id: numericId }
|
|
@@ -4112,8 +4221,8 @@ function createForgotPasswordHandler(config) {
|
|
|
4112
4221
|
const user = await userRepo.findOne({ where: { email }, select: ["email"] });
|
|
4113
4222
|
const msg = "If an account exists with this email, you will receive a reset link shortly.";
|
|
4114
4223
|
if (!user) return json({ message: msg }, { status: 200 });
|
|
4115
|
-
const
|
|
4116
|
-
const token =
|
|
4224
|
+
const crypto3 = await import("crypto");
|
|
4225
|
+
const token = crypto3.randomBytes(32).toString("hex");
|
|
4117
4226
|
const expiresAt = new Date(Date.now() + resetExpiryHours * 60 * 60 * 1e3);
|
|
4118
4227
|
const tokenRepo = dataSource.getRepository(entityMap.password_reset_tokens);
|
|
4119
4228
|
await tokenRepo.save(tokenRepo.create({ email: user.email, token, expiresAt }));
|
|
@@ -4266,6 +4375,7 @@ var LlmAgent = class {
|
|
|
4266
4375
|
id;
|
|
4267
4376
|
name;
|
|
4268
4377
|
slug;
|
|
4378
|
+
scope;
|
|
4269
4379
|
systemInstruction;
|
|
4270
4380
|
model;
|
|
4271
4381
|
temperature;
|
|
@@ -4289,6 +4399,9 @@ __decorateClass([
|
|
|
4289
4399
|
__decorateClass([
|
|
4290
4400
|
Column("varchar")
|
|
4291
4401
|
], LlmAgent.prototype, "slug", 2);
|
|
4402
|
+
__decorateClass([
|
|
4403
|
+
Column("varchar", { nullable: true })
|
|
4404
|
+
], LlmAgent.prototype, "scope", 2);
|
|
4292
4405
|
__decorateClass([
|
|
4293
4406
|
Column("text", { name: "system_instruction", default: "" })
|
|
4294
4407
|
], LlmAgent.prototype, "systemInstruction", 2);
|
|
@@ -4340,6 +4453,515 @@ function llmAgentToChatAgentOptions(agent) {
|
|
|
4340
4453
|
};
|
|
4341
4454
|
}
|
|
4342
4455
|
|
|
4456
|
+
// src/plugins/llm/chat-email-intent.ts
|
|
4457
|
+
var NONE_INTENT = "NONE";
|
|
4458
|
+
var CHAT_EMAIL_LOG = "[chat-email-tool]";
|
|
4459
|
+
function logChatEmail(step, data) {
|
|
4460
|
+
if (data && Object.keys(data).length > 0) {
|
|
4461
|
+
console.info(CHAT_EMAIL_LOG, step, data);
|
|
4462
|
+
} else {
|
|
4463
|
+
console.info(CHAT_EMAIL_LOG, step);
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
function normalizeIntentKey(raw) {
|
|
4467
|
+
return raw.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
4468
|
+
}
|
|
4469
|
+
function parseIntentsJson(raw) {
|
|
4470
|
+
if (!raw?.trim()) return null;
|
|
4471
|
+
try {
|
|
4472
|
+
const parsed = JSON.parse(raw);
|
|
4473
|
+
if (!Array.isArray(parsed)) return null;
|
|
4474
|
+
const out = [];
|
|
4475
|
+
for (const row of parsed) {
|
|
4476
|
+
if (!row || typeof row !== "object") continue;
|
|
4477
|
+
const o = row;
|
|
4478
|
+
const intent = normalizeIntentKey(String(o.intent ?? o.id ?? ""));
|
|
4479
|
+
const description = String(o.description ?? "").trim();
|
|
4480
|
+
const emailTo = String(o.emailTo ?? o.email ?? "").trim();
|
|
4481
|
+
if (!intent || !description || !emailTo) continue;
|
|
4482
|
+
out.push({ intent, description, emailTo });
|
|
4483
|
+
}
|
|
4484
|
+
return out.length > 0 ? out : null;
|
|
4485
|
+
} catch {
|
|
4486
|
+
return null;
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
function dedupeIntents(intents) {
|
|
4490
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4491
|
+
const out = [];
|
|
4492
|
+
for (const row of intents) {
|
|
4493
|
+
const key = normalizeIntentKey(row.intent);
|
|
4494
|
+
if (!key || seen.has(key)) continue;
|
|
4495
|
+
seen.add(key);
|
|
4496
|
+
out.push({
|
|
4497
|
+
intent: key,
|
|
4498
|
+
description: row.description.trim(),
|
|
4499
|
+
emailTo: row.emailTo.trim()
|
|
4500
|
+
});
|
|
4501
|
+
}
|
|
4502
|
+
return out;
|
|
4503
|
+
}
|
|
4504
|
+
function parseChatEmailToolSettings(map) {
|
|
4505
|
+
const fromJson = parseIntentsJson(map.emailIntents);
|
|
4506
|
+
const intents = dedupeIntents(fromJson ?? []);
|
|
4507
|
+
const legacyPositive = map.emailIntentPrompt?.trim() ?? "";
|
|
4508
|
+
const legacyNegative = map.emailNegativeIntentPrompt?.trim() ?? "";
|
|
4509
|
+
let classifierInstructions = map.emailClassifierInstructions?.trim() ?? "";
|
|
4510
|
+
if (!classifierInstructions && (legacyPositive || legacyNegative)) {
|
|
4511
|
+
const parts = [];
|
|
4512
|
+
if (legacyPositive) parts.push(`Legacy positive signals:
|
|
4513
|
+
${legacyPositive}`);
|
|
4514
|
+
if (legacyNegative) parts.push(`Do NOT assign an intent when:
|
|
4515
|
+
${legacyNegative}`);
|
|
4516
|
+
classifierInstructions = parts.join("\n\n");
|
|
4517
|
+
}
|
|
4518
|
+
return {
|
|
4519
|
+
enabled: map.emailToolEnabled === "true",
|
|
4520
|
+
intents,
|
|
4521
|
+
classifierInstructions,
|
|
4522
|
+
toolPrompt: map.emailToolPrompt ?? ""
|
|
4523
|
+
};
|
|
4524
|
+
}
|
|
4525
|
+
var EMAIL_TOOL_VALIDATION_KEY = "emailTool";
|
|
4526
|
+
function parseEmailToolObject(raw) {
|
|
4527
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
4528
|
+
const o = raw;
|
|
4529
|
+
const intentsRaw = o.intents;
|
|
4530
|
+
let intents;
|
|
4531
|
+
if (Array.isArray(intentsRaw)) {
|
|
4532
|
+
const parsed = parseIntentsJson(JSON.stringify(intentsRaw));
|
|
4533
|
+
if (parsed?.length) intents = parsed;
|
|
4534
|
+
}
|
|
4535
|
+
return {
|
|
4536
|
+
enabled: o.enabled === true,
|
|
4537
|
+
classifierInstructions: typeof o.classifierInstructions === "string" ? o.classifierInstructions.trim() : void 0,
|
|
4538
|
+
toolPrompt: typeof o.toolPrompt === "string" ? o.toolPrompt.trim() : void 0,
|
|
4539
|
+
intents
|
|
4540
|
+
};
|
|
4541
|
+
}
|
|
4542
|
+
function parseEmailToolFromAgentValidationRules(validationRulesText) {
|
|
4543
|
+
const raw = validationRulesText?.trim();
|
|
4544
|
+
if (!raw) return null;
|
|
4545
|
+
try {
|
|
4546
|
+
const parsed = JSON.parse(raw);
|
|
4547
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
4548
|
+
const emailTool = parsed[EMAIL_TOOL_VALIDATION_KEY];
|
|
4549
|
+
return parseEmailToolObject(emailTool);
|
|
4550
|
+
} catch {
|
|
4551
|
+
return null;
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
function resolveChatEmailToolSettings(configMap, sources) {
|
|
4555
|
+
const fromConfig = parseChatEmailToolSettings(configMap);
|
|
4556
|
+
const resolved = typeof sources === "string" || sources == null ? { chatbotValidationRules: typeof sources === "string" ? sources : null } : sources;
|
|
4557
|
+
const fromChatbot = parseEmailToolFromAgentValidationRules(resolved.chatbotValidationRules);
|
|
4558
|
+
const fromNotify = resolved.notifyAgent ? parseEmailToolFromAgentValidationRules(resolved.notifyAgent.validationRules) : null;
|
|
4559
|
+
const notifySystem = resolved.notifyAgent?.systemInstruction?.trim() ?? "";
|
|
4560
|
+
const intents = fromNotify?.intents?.length ? fromNotify.intents : fromChatbot?.intents?.length ? fromChatbot.intents : fromConfig.intents;
|
|
4561
|
+
const classifierParts = [
|
|
4562
|
+
notifySystem,
|
|
4563
|
+
fromNotify?.classifierInstructions?.trim(),
|
|
4564
|
+
fromChatbot?.classifierInstructions?.trim(),
|
|
4565
|
+
fromConfig.classifierInstructions.trim()
|
|
4566
|
+
].filter(Boolean);
|
|
4567
|
+
const toolPrompt = fromNotify?.toolPrompt?.trim() || fromChatbot?.toolPrompt?.trim() || fromConfig.toolPrompt?.trim() || "";
|
|
4568
|
+
return {
|
|
4569
|
+
enabled: fromConfig.enabled,
|
|
4570
|
+
intents,
|
|
4571
|
+
classifierInstructions: classifierParts.join("\n\n"),
|
|
4572
|
+
toolPrompt
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
function intentByKey(intents, key) {
|
|
4576
|
+
if (!key) return null;
|
|
4577
|
+
const norm2 = normalizeIntentKey(key);
|
|
4578
|
+
return intents.find((i) => i.intent === norm2) ?? null;
|
|
4579
|
+
}
|
|
4580
|
+
function buildIntentTableForPrompt(intents) {
|
|
4581
|
+
return intents.map((i) => `- ${i.intent}: ${i.description} \u2192 notify ${i.emailTo}`).join("\n");
|
|
4582
|
+
}
|
|
4583
|
+
function buildIntentClassifierSystem(config, agentClassifierInstructions) {
|
|
4584
|
+
const table = buildIntentTableForPrompt(config.intents);
|
|
4585
|
+
const extra = [agentClassifierInstructions?.trim(), config.classifierInstructions.trim()].filter(Boolean).join("\n\n");
|
|
4586
|
+
const intentKeys = config.intents.map((i) => i.intent).join(", ");
|
|
4587
|
+
return `You classify a chat visitor into exactly one lead intent for email routing.
|
|
4588
|
+
|
|
4589
|
+
Available intents (pick the best match):
|
|
4590
|
+
${table}
|
|
4591
|
+
|
|
4592
|
+
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}".
|
|
4593
|
+
|
|
4594
|
+
Allowed intent values: ${intentKeys}, or ${NONE_INTENT}.
|
|
4595
|
+
|
|
4596
|
+
${extra ? `Additional instructions:
|
|
4597
|
+
${extra}
|
|
4598
|
+
` : ""}
|
|
4599
|
+
Reply with ONLY valid JSON, no markdown:
|
|
4600
|
+
{"intent":"INTENT_KEY","reason":"short explanation"}
|
|
4601
|
+
Use "${NONE_INTENT}" when no intent applies.`;
|
|
4602
|
+
}
|
|
4603
|
+
function buildChatEmailAgentContext(settings) {
|
|
4604
|
+
if (!settings.enabled) return "";
|
|
4605
|
+
const classifier = settings.classifierInstructions.trim();
|
|
4606
|
+
const extra = settings.toolPrompt?.trim() ?? "";
|
|
4607
|
+
const parts = [];
|
|
4608
|
+
if (settings.intents.length > 0) {
|
|
4609
|
+
parts.push(
|
|
4610
|
+
"## Lead email routing",
|
|
4611
|
+
"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."
|
|
4612
|
+
);
|
|
4613
|
+
if (classifier) parts.push(`### Routing rules
|
|
4614
|
+
${classifier}`);
|
|
4615
|
+
parts.push(`### Intent catalog
|
|
4616
|
+
${buildIntentTableForPrompt(settings.intents)}`);
|
|
4617
|
+
} else if (classifier) {
|
|
4618
|
+
parts.push(`## Lead email routing
|
|
4619
|
+
${classifier}`);
|
|
4620
|
+
}
|
|
4621
|
+
if (extra) parts.push(`### Assistant behavior
|
|
4622
|
+
${extra}`);
|
|
4623
|
+
return parts.join("\n\n");
|
|
4624
|
+
}
|
|
4625
|
+
function buildChatbotSystemPromptWithEmailTool(baseSystemInstruction, guardrailsForPrompt, emailSettings) {
|
|
4626
|
+
let prompt = (baseSystemInstruction ?? "").trim();
|
|
4627
|
+
const guard = guardrailsForPrompt?.trim();
|
|
4628
|
+
if (guard) prompt = [prompt, guard].filter(Boolean).join("\n\n");
|
|
4629
|
+
if (emailSettings.enabled) {
|
|
4630
|
+
const emailCtx = buildChatEmailAgentContext(emailSettings);
|
|
4631
|
+
if (emailCtx) prompt = [prompt, emailCtx].filter(Boolean).join("\n\n");
|
|
4632
|
+
}
|
|
4633
|
+
return prompt;
|
|
4634
|
+
}
|
|
4635
|
+
function formatTranscript(history, latestMessage, maxChars = 4e3) {
|
|
4636
|
+
const lines = [];
|
|
4637
|
+
for (const m of history) {
|
|
4638
|
+
const role = m.role === "assistant" ? "Assistant" : m.role === "user" ? "Visitor" : "System";
|
|
4639
|
+
lines.push(`${role}: ${m.content}`);
|
|
4640
|
+
}
|
|
4641
|
+
const last = history[history.length - 1];
|
|
4642
|
+
if (!last || last.role !== "user" || last.content !== latestMessage) {
|
|
4643
|
+
lines.push(`Visitor: ${latestMessage}`);
|
|
4644
|
+
}
|
|
4645
|
+
let text = lines.join("\n");
|
|
4646
|
+
if (text.length > maxChars) text = text.slice(-maxChars);
|
|
4647
|
+
return text;
|
|
4648
|
+
}
|
|
4649
|
+
function parseIntentJson(raw, intents) {
|
|
4650
|
+
const trimmed = raw.trim();
|
|
4651
|
+
const fence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
4652
|
+
const candidate = (fence?.[1] ?? trimmed).trim();
|
|
4653
|
+
const tryParse = (s) => {
|
|
4654
|
+
const o = JSON.parse(s);
|
|
4655
|
+
let intentRaw = null;
|
|
4656
|
+
if (typeof o.intent === "string") intentRaw = o.intent;
|
|
4657
|
+
else if (o.interested === false) intentRaw = NONE_INTENT;
|
|
4658
|
+
else if (o.interested === true && intents[0]) intentRaw = intents[0].intent;
|
|
4659
|
+
if (intentRaw == null) return null;
|
|
4660
|
+
const reason = typeof o.reason === "string" && o.reason.trim() ? o.reason.trim() : "Classified from conversation";
|
|
4661
|
+
return { intent: normalizeIntentKey(intentRaw), reason };
|
|
4662
|
+
};
|
|
4663
|
+
try {
|
|
4664
|
+
return tryParse(candidate);
|
|
4665
|
+
} catch {
|
|
4666
|
+
const m = candidate.match(/\{[\s\S]*\}/);
|
|
4667
|
+
if (!m) return null;
|
|
4668
|
+
try {
|
|
4669
|
+
return tryParse(m[0]);
|
|
4670
|
+
} catch {
|
|
4671
|
+
return null;
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
}
|
|
4675
|
+
async function detectChatLeadIntent(llm, params) {
|
|
4676
|
+
if (!params.settings.intents.length) {
|
|
4677
|
+
logChatEmail("classify skipped", { reason: "no_intents_configured" });
|
|
4678
|
+
return {
|
|
4679
|
+
intent: null,
|
|
4680
|
+
intentLabel: "NONE",
|
|
4681
|
+
reason: "No lead intents configured",
|
|
4682
|
+
emailTo: null
|
|
4683
|
+
};
|
|
4684
|
+
}
|
|
4685
|
+
logChatEmail("classify start", {
|
|
4686
|
+
intentCount: params.settings.intents.length,
|
|
4687
|
+
intentKeys: params.settings.intents.map((i) => i.intent),
|
|
4688
|
+
model: params.model?.trim() || "(gateway default)",
|
|
4689
|
+
messageChars: params.message.length,
|
|
4690
|
+
historyTurns: params.history.length,
|
|
4691
|
+
hasClassifierInstructions: Boolean(
|
|
4692
|
+
params.agentClassifierInstructions?.trim() || params.settings.classifierInstructions.trim()
|
|
4693
|
+
)
|
|
4694
|
+
});
|
|
4695
|
+
const transcript = formatTranscript(params.history, params.message);
|
|
4696
|
+
const contactLines = [
|
|
4697
|
+
params.contact.name?.trim() ? `Name: ${params.contact.name.trim()}` : null,
|
|
4698
|
+
params.contact.email?.trim() ? `Email: ${params.contact.email.trim()}` : null,
|
|
4699
|
+
params.contact.phone?.trim() ? `Phone: ${params.contact.phone.trim()}` : null
|
|
4700
|
+
].filter(Boolean).join("\n");
|
|
4701
|
+
const userPrompt = `Customer:
|
|
4702
|
+
${contactLines || "(unknown)"}
|
|
4703
|
+
|
|
4704
|
+
Conversation:
|
|
4705
|
+
${transcript}
|
|
4706
|
+
|
|
4707
|
+
Pick the single best intent for the visitor's latest message.`;
|
|
4708
|
+
const res = await llm.chatAgent({
|
|
4709
|
+
systemPrompt: buildIntentClassifierSystem(params.settings, params.agentClassifierInstructions),
|
|
4710
|
+
userPrompt,
|
|
4711
|
+
temperature: 0.1,
|
|
4712
|
+
max_tokens: 256,
|
|
4713
|
+
...params.model?.trim() ? { model: params.model.trim() } : {}
|
|
4714
|
+
});
|
|
4715
|
+
const rawContent = res.content ?? "";
|
|
4716
|
+
logChatEmail("classify llm response", {
|
|
4717
|
+
responseChars: rawContent.length,
|
|
4718
|
+
responsePreview: rawContent.slice(0, 280) + (rawContent.length > 280 ? "\u2026" : "")
|
|
4719
|
+
});
|
|
4720
|
+
const parsed = parseIntentJson(rawContent, params.settings.intents);
|
|
4721
|
+
if (!parsed) {
|
|
4722
|
+
logChatEmail("classify result", {
|
|
4723
|
+
matched: false,
|
|
4724
|
+
outcome: "parse_failed",
|
|
4725
|
+
emailWillSend: false
|
|
4726
|
+
});
|
|
4727
|
+
return {
|
|
4728
|
+
intent: null,
|
|
4729
|
+
intentLabel: "Unclassified",
|
|
4730
|
+
reason: "Could not parse intent classifier response",
|
|
4731
|
+
emailTo: null
|
|
4732
|
+
};
|
|
4733
|
+
}
|
|
4734
|
+
if (parsed.intent === NONE_INTENT) {
|
|
4735
|
+
logChatEmail("classify result", {
|
|
4736
|
+
matched: false,
|
|
4737
|
+
outcome: NONE_INTENT,
|
|
4738
|
+
reason: parsed.reason,
|
|
4739
|
+
emailWillSend: false
|
|
4740
|
+
});
|
|
4741
|
+
return {
|
|
4742
|
+
intent: null,
|
|
4743
|
+
intentLabel: NONE_INTENT,
|
|
4744
|
+
reason: parsed.reason,
|
|
4745
|
+
emailTo: null
|
|
4746
|
+
};
|
|
4747
|
+
}
|
|
4748
|
+
const matched = intentByKey(params.settings.intents, parsed.intent);
|
|
4749
|
+
if (!matched) {
|
|
4750
|
+
logChatEmail("classify result", {
|
|
4751
|
+
matched: false,
|
|
4752
|
+
outcome: "unknown_intent",
|
|
4753
|
+
parsedIntent: parsed.intent,
|
|
4754
|
+
reason: parsed.reason,
|
|
4755
|
+
emailWillSend: false
|
|
4756
|
+
});
|
|
4757
|
+
return {
|
|
4758
|
+
intent: null,
|
|
4759
|
+
intentLabel: parsed.intent,
|
|
4760
|
+
reason: `Unknown intent "${parsed.intent}": ${parsed.reason}`,
|
|
4761
|
+
emailTo: null
|
|
4762
|
+
};
|
|
4763
|
+
}
|
|
4764
|
+
logChatEmail("classify result", {
|
|
4765
|
+
matched: true,
|
|
4766
|
+
outcome: "intent_found",
|
|
4767
|
+
intent: matched.intent,
|
|
4768
|
+
emailTo: matched.emailTo,
|
|
4769
|
+
reason: parsed.reason,
|
|
4770
|
+
emailWillSend: true
|
|
4771
|
+
});
|
|
4772
|
+
return {
|
|
4773
|
+
intent: matched.intent,
|
|
4774
|
+
intentLabel: `${matched.intent} \u2014 ${matched.description}`,
|
|
4775
|
+
reason: parsed.reason,
|
|
4776
|
+
emailTo: matched.emailTo
|
|
4777
|
+
};
|
|
4778
|
+
}
|
|
4779
|
+
function buildTranscriptForLeadEmail(history, latestMessage) {
|
|
4780
|
+
return formatTranscript(history, latestMessage);
|
|
4781
|
+
}
|
|
4782
|
+
function parseIntentRecipientEmails(emailTo) {
|
|
4783
|
+
return emailTo.split(/[,;]+/).map((s) => s.trim()).filter((s) => s.length > 0 && s.includes("@"));
|
|
4784
|
+
}
|
|
4785
|
+
|
|
4786
|
+
// src/plugins/llm/find-llm-agent-by-scope.ts
|
|
4787
|
+
async function findLlmAgentByScope(dataSource, entityMap, scope, options = {}) {
|
|
4788
|
+
const { enabledOnly = true } = options;
|
|
4789
|
+
const entity = entityMap.llm_agents;
|
|
4790
|
+
if (!entity) return null;
|
|
4791
|
+
const repo = dataSource.getRepository(entity);
|
|
4792
|
+
const where = { scope, deleted: false };
|
|
4793
|
+
if (enabledOnly) where.enabled = true;
|
|
4794
|
+
return repo.findOne({ where });
|
|
4795
|
+
}
|
|
4796
|
+
|
|
4797
|
+
// src/plugins/email/templates/types.ts
|
|
4798
|
+
function normalizeSocialLinkItem(o) {
|
|
4799
|
+
const url = String(o.url ?? "").trim();
|
|
4800
|
+
if (!url) return null;
|
|
4801
|
+
let iconUrl = String(o.iconUrl ?? o.icon_image ?? "").trim();
|
|
4802
|
+
let icon = String(o.icon ?? "").trim();
|
|
4803
|
+
if (!iconUrl && /^https?:\/\//i.test(icon)) {
|
|
4804
|
+
iconUrl = icon;
|
|
4805
|
+
icon = "";
|
|
4806
|
+
}
|
|
4807
|
+
const item = { url };
|
|
4808
|
+
if (iconUrl) item.iconUrl = iconUrl;
|
|
4809
|
+
if (icon) item.icon = icon;
|
|
4810
|
+
return item;
|
|
4811
|
+
}
|
|
4812
|
+
function parseSocialLinksJson(raw) {
|
|
4813
|
+
if (raw == null || raw.trim() === "") return void 0;
|
|
4814
|
+
try {
|
|
4815
|
+
const parsed = JSON.parse(raw);
|
|
4816
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
4817
|
+
const out = [];
|
|
4818
|
+
for (const item of parsed) {
|
|
4819
|
+
if (item && typeof item === "object" && "url" in item) {
|
|
4820
|
+
const n = normalizeSocialLinkItem(item);
|
|
4821
|
+
if (n) out.push(n);
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
return out.length ? out : void 0;
|
|
4825
|
+
} catch {
|
|
4826
|
+
return void 0;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
function mergeEmailLayoutCompanyDetails(branding, emailSettings) {
|
|
4830
|
+
const fromBranding = getCompanyDetailsFromSettings(branding);
|
|
4831
|
+
const pick = (emailVal, fallback) => {
|
|
4832
|
+
const t = emailVal?.trim();
|
|
4833
|
+
return t || fallback?.trim() || void 0;
|
|
4834
|
+
};
|
|
4835
|
+
const logoUrl = pick(emailSettings.logoUrl ?? emailSettings.emailLogoUrl, fromBranding.logoUrl);
|
|
4836
|
+
const companyName = pick(emailSettings.companyName ?? emailSettings.emailCompanyName, fromBranding.companyName);
|
|
4837
|
+
const supportEmail = pick(emailSettings.supportEmail ?? emailSettings.emailSupportEmail, fromBranding.supportEmail);
|
|
4838
|
+
const supportPhone = pick(emailSettings.supportPhone, void 0);
|
|
4839
|
+
const footerDisclaimer = pick(emailSettings.footerDisclaimer, void 0);
|
|
4840
|
+
const followUsTitle = pick(emailSettings.followUsTitle, "Follow Us") || "Follow Us";
|
|
4841
|
+
const socialFromEmail = parseSocialLinksJson(emailSettings.socialLinks);
|
|
4842
|
+
const socialLinks = socialFromEmail?.length ? socialFromEmail : fromBranding.socialLinks;
|
|
4843
|
+
return {
|
|
4844
|
+
logoUrl,
|
|
4845
|
+
companyName,
|
|
4846
|
+
supportEmail,
|
|
4847
|
+
supportPhone,
|
|
4848
|
+
socialLinks,
|
|
4849
|
+
footerDisclaimer,
|
|
4850
|
+
followUsTitle
|
|
4851
|
+
};
|
|
4852
|
+
}
|
|
4853
|
+
function getCompanyDetailsFromSettings(settingsGroup) {
|
|
4854
|
+
const logoUrl = settingsGroup.logo ?? settingsGroup.logoUrl ?? "";
|
|
4855
|
+
const companyName = settingsGroup.companyName ?? settingsGroup.company_name ?? "";
|
|
4856
|
+
const supportEmail = settingsGroup.supportEmail ?? settingsGroup.support_email ?? "";
|
|
4857
|
+
let socialLinks = [];
|
|
4858
|
+
const raw = settingsGroup.socialLinks ?? settingsGroup.social_links;
|
|
4859
|
+
if (typeof raw === "string") {
|
|
4860
|
+
try {
|
|
4861
|
+
const arr = JSON.parse(raw);
|
|
4862
|
+
if (Array.isArray(arr)) {
|
|
4863
|
+
for (const item of arr) {
|
|
4864
|
+
if (item && typeof item === "object") {
|
|
4865
|
+
const n = normalizeSocialLinkItem(item);
|
|
4866
|
+
if (n) socialLinks.push(n);
|
|
4867
|
+
}
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
} catch {
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
return { logoUrl: logoUrl || void 0, companyName: companyName || void 0, supportEmail: supportEmail || void 0, socialLinks: socialLinks.length ? socialLinks : void 0 };
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
// src/plugins/email/chat-lead-email.ts
|
|
4877
|
+
init_email_queue();
|
|
4878
|
+
|
|
4879
|
+
// src/lib/email-recipients.ts
|
|
4880
|
+
function parseEmailRecipientsFromConfig(raw) {
|
|
4881
|
+
if (raw == null || raw === "") return [];
|
|
4882
|
+
const trimmed = raw.trim();
|
|
4883
|
+
if (trimmed.startsWith("[")) {
|
|
4884
|
+
try {
|
|
4885
|
+
const parsed = JSON.parse(trimmed);
|
|
4886
|
+
if (Array.isArray(parsed)) {
|
|
4887
|
+
return parsed.map((e) => String(e).trim()).filter(Boolean);
|
|
4888
|
+
}
|
|
4889
|
+
} catch {
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
return trimmed.split(/[,;]+/).map((s) => s.trim()).filter(Boolean);
|
|
4893
|
+
}
|
|
4894
|
+
|
|
4895
|
+
// src/plugins/email/chat-lead-email.ts
|
|
4896
|
+
function resolveLeadRecipients(emailPlugin, emailSettings) {
|
|
4897
|
+
const fromCrm = parseEmailRecipientsFromConfig(
|
|
4898
|
+
emailSettings.crmEmails ?? emailSettings.crmEmail ?? ""
|
|
4899
|
+
);
|
|
4900
|
+
if (fromCrm.length > 0) return fromCrm;
|
|
4901
|
+
const fallback = emailPlugin.getDefaultTo?.() ?? "";
|
|
4902
|
+
if (fallback.trim()) return [fallback.trim()];
|
|
4903
|
+
return [];
|
|
4904
|
+
}
|
|
4905
|
+
async function sendChatLeadEmail(cms, emailSettings, brandingSettings, input) {
|
|
4906
|
+
console.info(CHAT_EMAIL_LOG, "send start", {
|
|
4907
|
+
conversationId: input.conversationId,
|
|
4908
|
+
intentCode: input.intentCode ?? null,
|
|
4909
|
+
contactEmail: input.contactEmail,
|
|
4910
|
+
contactName: input.contactName,
|
|
4911
|
+
explicitRecipients: input.recipients?.length ?? 0
|
|
4912
|
+
});
|
|
4913
|
+
const email = cms.getPlugin("email");
|
|
4914
|
+
if (!email?.send || !email.renderTemplate) {
|
|
4915
|
+
console.warn(CHAT_EMAIL_LOG, "send failed", {
|
|
4916
|
+
conversationId: input.conversationId,
|
|
4917
|
+
reason: "email_plugin_disabled"
|
|
4918
|
+
});
|
|
4919
|
+
return { sent: false, recipients: [], error: "Email plugin is not enabled" };
|
|
4920
|
+
}
|
|
4921
|
+
const recipients = input.recipients?.filter((r) => r.trim().includes("@")).map((r) => r.trim()) ?? resolveLeadRecipients(email, emailSettings);
|
|
4922
|
+
if (recipients.length === 0) {
|
|
4923
|
+
console.warn(CHAT_EMAIL_LOG, "send failed", {
|
|
4924
|
+
conversationId: input.conversationId,
|
|
4925
|
+
reason: "no_recipients"
|
|
4926
|
+
});
|
|
4927
|
+
return {
|
|
4928
|
+
sent: false,
|
|
4929
|
+
recipients: [],
|
|
4930
|
+
error: "No lead email recipients (configure intent Email To, CRM emails, or SMTP default To)"
|
|
4931
|
+
};
|
|
4932
|
+
}
|
|
4933
|
+
console.info(CHAT_EMAIL_LOG, "send recipients resolved", {
|
|
4934
|
+
conversationId: input.conversationId,
|
|
4935
|
+
recipients,
|
|
4936
|
+
source: input.recipients?.length ? "intent_email_to" : "crm_or_smtp_fallback"
|
|
4937
|
+
});
|
|
4938
|
+
const companyDetails = mergeEmailLayoutCompanyDetails(brandingSettings, emailSettings);
|
|
4939
|
+
const ctx = {
|
|
4940
|
+
...input,
|
|
4941
|
+
companyDetails: input.companyDetails ?? companyDetails
|
|
4942
|
+
};
|
|
4943
|
+
let anySent = false;
|
|
4944
|
+
for (const to of recipients) {
|
|
4945
|
+
await queueEmail(cms, {
|
|
4946
|
+
to,
|
|
4947
|
+
templateName: "chatLead",
|
|
4948
|
+
ctx
|
|
4949
|
+
});
|
|
4950
|
+
console.info(CHAT_EMAIL_LOG, "send queued", {
|
|
4951
|
+
conversationId: input.conversationId,
|
|
4952
|
+
to,
|
|
4953
|
+
template: "chatLead"
|
|
4954
|
+
});
|
|
4955
|
+
anySent = true;
|
|
4956
|
+
}
|
|
4957
|
+
console.info(CHAT_EMAIL_LOG, "send complete", {
|
|
4958
|
+
conversationId: input.conversationId,
|
|
4959
|
+
sent: anySent,
|
|
4960
|
+
recipientCount: recipients.length
|
|
4961
|
+
});
|
|
4962
|
+
return { sent: anySent, recipients };
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4343
4965
|
// src/lib/media-folder-path.ts
|
|
4344
4966
|
function sanitizeMediaFolderPath(input) {
|
|
4345
4967
|
if (input == null) return "";
|
|
@@ -5819,16 +6441,19 @@ function normalizeChatModeSetting(raw) {
|
|
|
5819
6441
|
if (raw === "external" || raw === "llm") return raw;
|
|
5820
6442
|
return "whatsapp";
|
|
5821
6443
|
}
|
|
5822
|
-
async function
|
|
6444
|
+
async function loadSettingsGroupMap(dataSource, entityMap, group) {
|
|
5823
6445
|
if (!entityMap.configs) return {};
|
|
5824
6446
|
const repo = dataSource.getRepository(entityMap.configs);
|
|
5825
|
-
const rows = await repo.find({ where: { settings:
|
|
6447
|
+
const rows = await repo.find({ where: { settings: group, deleted: false } });
|
|
5826
6448
|
const out = {};
|
|
5827
6449
|
for (const row of rows) {
|
|
5828
6450
|
out[row.key] = row.value;
|
|
5829
6451
|
}
|
|
5830
6452
|
return out;
|
|
5831
6453
|
}
|
|
6454
|
+
async function loadLlmSettingsMap(dataSource, entityMap) {
|
|
6455
|
+
return loadSettingsGroupMap(dataSource, entityMap, "llm");
|
|
6456
|
+
}
|
|
5832
6457
|
function createChatHandlers(config) {
|
|
5833
6458
|
const { dataSource, entityMap, json, getCms } = config;
|
|
5834
6459
|
const contactRepo = () => dataSource.getRepository(entityMap.contacts);
|
|
@@ -5840,10 +6465,13 @@ function createChatHandlers(config) {
|
|
|
5840
6465
|
try {
|
|
5841
6466
|
const map = await loadLlmSettingsMap(dataSource, entityMap);
|
|
5842
6467
|
const mode = normalizeChatModeSetting(map.chatMode);
|
|
6468
|
+
const chatbotAgent = mode === "llm" && entityMap.llm_agents ? await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_CHATBOT, {
|
|
6469
|
+
enabledOnly: false
|
|
6470
|
+
}) : null;
|
|
5843
6471
|
const body = {
|
|
5844
6472
|
enabled: map.enabled !== "false",
|
|
5845
6473
|
chatMode: mode,
|
|
5846
|
-
agentSlug: mode === "llm" ?
|
|
6474
|
+
agentSlug: mode === "llm" ? chatbotAgent?.slug?.trim() || LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT] : "",
|
|
5847
6475
|
botName: map.botName ?? "",
|
|
5848
6476
|
icon: map.icon ?? "",
|
|
5849
6477
|
iconImageUrl: map.iconImageUrl ?? "",
|
|
@@ -5929,24 +6557,26 @@ function createChatHandlers(config) {
|
|
|
5929
6557
|
if (!llm?.chat) return json({ error: "LLM not configured" }, { status: 503 });
|
|
5930
6558
|
const llmSettings = await loadLlmSettingsMap(dataSource, entityMap);
|
|
5931
6559
|
const supportMode = normalizeChatModeSetting(llmSettings.chatMode);
|
|
5932
|
-
|
|
5933
|
-
if (!effectiveSlug && supportMode === "llm" && entityMap.llm_agents) {
|
|
5934
|
-
effectiveSlug = (llmSettings.attachedAgentSlug ?? "").trim();
|
|
5935
|
-
}
|
|
6560
|
+
const bodyAgentSlug = (body?.agentSlug ?? "").trim();
|
|
5936
6561
|
let agentRow = null;
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
6562
|
+
let effectiveSlug = bodyAgentSlug;
|
|
6563
|
+
if (entityMap.llm_agents) {
|
|
6564
|
+
if (bodyAgentSlug) {
|
|
6565
|
+
const agentRepo = dataSource.getRepository(
|
|
6566
|
+
entityMap.llm_agents
|
|
6567
|
+
);
|
|
6568
|
+
agentRow = await agentRepo.findOne({
|
|
6569
|
+
where: { slug: bodyAgentSlug, deleted: false, enabled: true }
|
|
6570
|
+
});
|
|
6571
|
+
if (!agentRow) {
|
|
6572
|
+
return json({ error: "Agent not found or disabled", agentSlug: bodyAgentSlug }, { status: 404 });
|
|
6573
|
+
}
|
|
6574
|
+
} else if (supportMode === "llm") {
|
|
6575
|
+
agentRow = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_CHATBOT);
|
|
6576
|
+
effectiveSlug = agentRow?.slug?.trim() || LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
|
|
5949
6577
|
}
|
|
6578
|
+
} else if (bodyAgentSlug) {
|
|
6579
|
+
return json({ error: "LLM agents are not configured on this deployment" }, { status: 400 });
|
|
5950
6580
|
}
|
|
5951
6581
|
console.info(RAG_LOG, "step 1 | resolve agent", {
|
|
5952
6582
|
agentSlug: effectiveSlug || "(none)",
|
|
@@ -6044,6 +6674,139 @@ function createChatHandlers(config) {
|
|
|
6044
6674
|
}
|
|
6045
6675
|
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 }));
|
|
6046
6676
|
const history = historyBeforeCurrentUser(historyRaw, message);
|
|
6677
|
+
const notifyEmailAgent = await findLlmAgentByScope(
|
|
6678
|
+
dataSource,
|
|
6679
|
+
entityMap,
|
|
6680
|
+
LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT,
|
|
6681
|
+
{ enabledOnly: false }
|
|
6682
|
+
);
|
|
6683
|
+
const emailTool = resolveChatEmailToolSettings(llmSettings, {
|
|
6684
|
+
chatbotValidationRules: agentRow?.validationRules ?? null,
|
|
6685
|
+
notifyAgent: notifyEmailAgent ? {
|
|
6686
|
+
systemInstruction: notifyEmailAgent.systemInstruction,
|
|
6687
|
+
validationRules: notifyEmailAgent.validationRules
|
|
6688
|
+
} : null
|
|
6689
|
+
});
|
|
6690
|
+
const emailPlugin = cms.getPlugin("email");
|
|
6691
|
+
const convLeadSent = conv.leadEmailSentAt;
|
|
6692
|
+
const chatAgentFn = llm.chatAgent?.bind(llm);
|
|
6693
|
+
console.info(CHAT_EMAIL_LOG, "pipeline check", {
|
|
6694
|
+
conversationId,
|
|
6695
|
+
enabled: emailTool.enabled,
|
|
6696
|
+
intentCount: emailTool.intents.length,
|
|
6697
|
+
chatbotAgentId: agentRow?.id ?? null,
|
|
6698
|
+
chatbotAgentSlug: agentRow?.slug ?? null,
|
|
6699
|
+
notifyEmailAgentId: notifyEmailAgent?.id ?? null,
|
|
6700
|
+
notifyEmailAgentSlug: notifyEmailAgent?.slug ?? null,
|
|
6701
|
+
emailPluginPresent: Boolean(emailPlugin),
|
|
6702
|
+
chatAgentFnPresent: Boolean(chatAgentFn),
|
|
6703
|
+
leadEmailAlreadySent: Boolean(convLeadSent),
|
|
6704
|
+
mergedPromptIncludesNotify: Boolean(notifyEmailAgent?.systemInstruction?.trim())
|
|
6705
|
+
});
|
|
6706
|
+
if (!emailTool.enabled) {
|
|
6707
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6708
|
+
conversationId,
|
|
6709
|
+
reason: "email_tool_disabled"
|
|
6710
|
+
});
|
|
6711
|
+
} else if (emailTool.intents.length === 0) {
|
|
6712
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6713
|
+
conversationId,
|
|
6714
|
+
reason: "no_intents_configured"
|
|
6715
|
+
});
|
|
6716
|
+
} else if (!emailPlugin) {
|
|
6717
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6718
|
+
conversationId,
|
|
6719
|
+
reason: "email_plugin_missing"
|
|
6720
|
+
});
|
|
6721
|
+
} else if (!chatAgentFn) {
|
|
6722
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6723
|
+
conversationId,
|
|
6724
|
+
reason: "llm_chat_agent_unavailable"
|
|
6725
|
+
});
|
|
6726
|
+
} else if (convLeadSent) {
|
|
6727
|
+
console.info(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6728
|
+
conversationId,
|
|
6729
|
+
reason: "lead_email_already_sent",
|
|
6730
|
+
leadEmailSentAt: convLeadSent
|
|
6731
|
+
});
|
|
6732
|
+
} else {
|
|
6733
|
+
const contactId = conv.contactId;
|
|
6734
|
+
const contactRow = await contactRepo().findOne({
|
|
6735
|
+
where: { id: contactId }
|
|
6736
|
+
});
|
|
6737
|
+
if (!contactRow) {
|
|
6738
|
+
console.warn(CHAT_EMAIL_LOG, "pipeline skipped", {
|
|
6739
|
+
conversationId,
|
|
6740
|
+
reason: "contact_not_found",
|
|
6741
|
+
contactId
|
|
6742
|
+
});
|
|
6743
|
+
} else {
|
|
6744
|
+
const c = contactRow;
|
|
6745
|
+
try {
|
|
6746
|
+
const intent = await detectChatLeadIntent({ chatAgent: chatAgentFn }, {
|
|
6747
|
+
settings: emailTool,
|
|
6748
|
+
message,
|
|
6749
|
+
history,
|
|
6750
|
+
contact: { name: c.name, email: c.email, phone: c.phone },
|
|
6751
|
+
model: agentRow?.model?.trim() || notifyEmailAgent?.model?.trim() || void 0
|
|
6752
|
+
});
|
|
6753
|
+
if (!intent.intent || !intent.emailTo) {
|
|
6754
|
+
console.info(CHAT_EMAIL_LOG, "no email sent", {
|
|
6755
|
+
conversationId,
|
|
6756
|
+
reason: intent.intent ? "missing_email_to" : "no_intent_match",
|
|
6757
|
+
intentLabel: intent.intentLabel,
|
|
6758
|
+
classifierReason: intent.reason
|
|
6759
|
+
});
|
|
6760
|
+
} else {
|
|
6761
|
+
const intentRecipients = parseIntentRecipientEmails(intent.emailTo);
|
|
6762
|
+
console.info(CHAT_EMAIL_LOG, "intent matched \u2014 sending", {
|
|
6763
|
+
conversationId,
|
|
6764
|
+
intent: intent.intent,
|
|
6765
|
+
emailTo: intent.emailTo,
|
|
6766
|
+
parsedRecipientCount: intentRecipients.length,
|
|
6767
|
+
recipients: intentRecipients
|
|
6768
|
+
});
|
|
6769
|
+
const emailSettings = await loadSettingsGroupMap(dataSource, entityMap, "email");
|
|
6770
|
+
const brandingSettings = await loadSettingsGroupMap(dataSource, entityMap, "branding");
|
|
6771
|
+
const companyDetails = mergeEmailLayoutCompanyDetails(brandingSettings, emailSettings);
|
|
6772
|
+
const leadResult = await sendChatLeadEmail(cms, emailSettings, brandingSettings, {
|
|
6773
|
+
contactName: String(c.name ?? "").trim() || "Visitor",
|
|
6774
|
+
contactEmail: String(c.email ?? "").trim(),
|
|
6775
|
+
contactPhone: c.phone ?? null,
|
|
6776
|
+
conversationId,
|
|
6777
|
+
latestMessage: message,
|
|
6778
|
+
intentCode: intent.intent,
|
|
6779
|
+
intentReason: intent.intentLabel || intent.reason,
|
|
6780
|
+
transcript: buildTranscriptForLeadEmail(history, message),
|
|
6781
|
+
companyDetails,
|
|
6782
|
+
recipients: intentRecipients.length > 0 ? intentRecipients : void 0
|
|
6783
|
+
});
|
|
6784
|
+
if (leadResult.sent) {
|
|
6785
|
+
await convRepo().update(conversationId, {
|
|
6786
|
+
leadEmailSentAt: /* @__PURE__ */ new Date()
|
|
6787
|
+
});
|
|
6788
|
+
console.info(CHAT_EMAIL_LOG, "lead email recorded", {
|
|
6789
|
+
conversationId,
|
|
6790
|
+
sent: true,
|
|
6791
|
+
recipients: leadResult.recipients
|
|
6792
|
+
});
|
|
6793
|
+
} else {
|
|
6794
|
+
console.warn(CHAT_EMAIL_LOG, "lead email not sent", {
|
|
6795
|
+
conversationId,
|
|
6796
|
+
sent: false,
|
|
6797
|
+
error: leadResult.error ?? "unknown",
|
|
6798
|
+
recipients: leadResult.recipients
|
|
6799
|
+
});
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6802
|
+
} catch (intentErr) {
|
|
6803
|
+
console.warn(CHAT_EMAIL_LOG, "pipeline error", {
|
|
6804
|
+
conversationId,
|
|
6805
|
+
error: intentErr instanceof Error ? intentErr.message : String(intentErr)
|
|
6806
|
+
});
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
}
|
|
6047
6810
|
let content;
|
|
6048
6811
|
const ragContext = contextParts.length > 0 ? contextParts.join("\n\n") : void 0;
|
|
6049
6812
|
console.info(RAG_LOG, "step 7 | final context", {
|
|
@@ -6054,9 +6817,10 @@ function createChatHandlers(config) {
|
|
|
6054
6817
|
});
|
|
6055
6818
|
if (agentRow && llm.chatAgent) {
|
|
6056
6819
|
const fromAgent = llmAgentToChatAgentOptions(agentRow);
|
|
6057
|
-
const systemPrompt =
|
|
6820
|
+
const systemPrompt = buildChatbotSystemPromptWithEmailTool(
|
|
6058
6821
|
fromAgent.systemPrompt,
|
|
6059
|
-
parsedValidation.guardrailsForPrompt
|
|
6822
|
+
parsedValidation.guardrailsForPrompt,
|
|
6823
|
+
emailTool
|
|
6060
6824
|
);
|
|
6061
6825
|
const res = await llm.chatAgent({
|
|
6062
6826
|
...fromAgent,
|
|
@@ -6074,11 +6838,12 @@ ${contextParts.join("\n\n")}` : "";
|
|
|
6074
6838
|
const defaultSystem = "You are a helpful assistant for the company. If you do not have specific information, say so.";
|
|
6075
6839
|
let systemContent;
|
|
6076
6840
|
if (agentRow) {
|
|
6077
|
-
const
|
|
6078
|
-
systemContent =
|
|
6079
|
-
|
|
6080
|
-
parsedValidation.guardrailsForPrompt
|
|
6081
|
-
|
|
6841
|
+
const mergedBase = [agentRow.systemInstruction?.trim(), ragSystem].filter(Boolean).join("\n\n");
|
|
6842
|
+
systemContent = buildChatbotSystemPromptWithEmailTool(
|
|
6843
|
+
mergedBase || defaultSystem,
|
|
6844
|
+
parsedValidation.guardrailsForPrompt,
|
|
6845
|
+
emailTool
|
|
6846
|
+
) || defaultSystem;
|
|
6082
6847
|
} else {
|
|
6083
6848
|
systemContent = ragSystem || defaultSystem;
|
|
6084
6849
|
}
|
|
@@ -8603,6 +9368,7 @@ var Order = class {
|
|
|
8603
9368
|
id;
|
|
8604
9369
|
vendorId;
|
|
8605
9370
|
orderNumber;
|
|
9371
|
+
qrToken;
|
|
8606
9372
|
orderKind;
|
|
8607
9373
|
parentOrderId;
|
|
8608
9374
|
contactId;
|
|
@@ -8640,6 +9406,9 @@ __decorateClass([
|
|
|
8640
9406
|
__decorateClass([
|
|
8641
9407
|
Column18("varchar")
|
|
8642
9408
|
], Order.prototype, "orderNumber", 2);
|
|
9409
|
+
__decorateClass([
|
|
9410
|
+
Column18("varchar", { unique: true, nullable: true })
|
|
9411
|
+
], Order.prototype, "qrToken", 2);
|
|
8643
9412
|
__decorateClass([
|
|
8644
9413
|
Column18("varchar", { default: "sale" })
|
|
8645
9414
|
], Order.prototype, "orderKind", 2);
|
|
@@ -8868,6 +9637,7 @@ var ChatConversation = class {
|
|
|
8868
9637
|
contactId;
|
|
8869
9638
|
createdAt;
|
|
8870
9639
|
updatedAt;
|
|
9640
|
+
leadEmailSentAt;
|
|
8871
9641
|
contact;
|
|
8872
9642
|
messages;
|
|
8873
9643
|
};
|
|
@@ -8883,6 +9653,9 @@ __decorateClass([
|
|
|
8883
9653
|
__decorateClass([
|
|
8884
9654
|
Column21({ type: "timestamp", default: () => "CURRENT_TIMESTAMP" })
|
|
8885
9655
|
], ChatConversation.prototype, "updatedAt", 2);
|
|
9656
|
+
__decorateClass([
|
|
9657
|
+
Column21({ type: "timestamp", nullable: true })
|
|
9658
|
+
], ChatConversation.prototype, "leadEmailSentAt", 2);
|
|
8886
9659
|
__decorateClass([
|
|
8887
9660
|
ManyToOne12(() => Contact, (c) => c.chatConversations, { onDelete: "CASCADE" }),
|
|
8888
9661
|
JoinColumn12({ name: "contactId" })
|
|
@@ -11123,7 +11896,6 @@ var CMS_ENTITY_MAP = {
|
|
|
11123
11896
|
import Parser from "rss-parser";
|
|
11124
11897
|
|
|
11125
11898
|
// src/plugins/blog-generator/blog-generator-agent-defaults.ts
|
|
11126
|
-
var BLOG_GENERATOR_LLM_AGENT_SLUG = "blog-generator";
|
|
11127
11899
|
var BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR = "---BLOG_GENERATOR_NEXT---";
|
|
11128
11900
|
var BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION = `You are a professional financial and business content writer.
|
|
11129
11901
|
|
|
@@ -11203,7 +11975,6 @@ var BLOG_GENERATOR_DEFAULT_VALIDATION_RULES = JSON.stringify(
|
|
|
11203
11975
|
);
|
|
11204
11976
|
|
|
11205
11977
|
// src/plugins/blog-generator/blog-generator-metadata-defaults.ts
|
|
11206
|
-
var BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG = "blog-generator-metadata";
|
|
11207
11978
|
var BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
|
|
11208
11979
|
{
|
|
11209
11980
|
maxUserChars: 5e5,
|
|
@@ -11214,7 +11985,6 @@ var BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
|
|
|
11214
11985
|
);
|
|
11215
11986
|
|
|
11216
11987
|
// src/plugins/blog-generator/blog-generator-social-defaults.ts
|
|
11217
|
-
var BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG = "blog-generator-social";
|
|
11218
11988
|
var BLOG_SOCIAL_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
|
|
11219
11989
|
{
|
|
11220
11990
|
maxUserChars: 5e5,
|
|
@@ -13341,28 +14111,24 @@ function createCmsApiHandler(config) {
|
|
|
13341
14111
|
let socialLlmAgentResolution = null;
|
|
13342
14112
|
let metadataRow = null;
|
|
13343
14113
|
let socialRow = null;
|
|
14114
|
+
let blogCreationRow = null;
|
|
13344
14115
|
if (entityMap.llm_agents) {
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
});
|
|
13349
|
-
if (agentRow) {
|
|
13350
|
-
const o = llmAgentToChatAgentOptions(agentRow);
|
|
14116
|
+
blogCreationRow = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_BLOG_CREATION);
|
|
14117
|
+
if (blogCreationRow) {
|
|
14118
|
+
const o = llmAgentToChatAgentOptions(blogCreationRow);
|
|
13351
14119
|
llmAgentChatOptions = {
|
|
13352
14120
|
model: o.model,
|
|
13353
14121
|
temperature: o.temperature,
|
|
13354
14122
|
max_tokens: o.max_tokens
|
|
13355
14123
|
};
|
|
13356
14124
|
llmAgentResolution = {
|
|
13357
|
-
slug:
|
|
13358
|
-
model:
|
|
13359
|
-
temperature:
|
|
13360
|
-
maxTokens:
|
|
14125
|
+
slug: blogCreationRow.slug,
|
|
14126
|
+
model: blogCreationRow.model?.trim() || null,
|
|
14127
|
+
temperature: blogCreationRow.temperature ?? null,
|
|
14128
|
+
maxTokens: blogCreationRow.maxTokens ?? null
|
|
13361
14129
|
};
|
|
13362
14130
|
}
|
|
13363
|
-
metadataRow = await
|
|
13364
|
-
where: { slug: BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, deleted: false, enabled: true }
|
|
13365
|
-
});
|
|
14131
|
+
metadataRow = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_BLOG_METADATA);
|
|
13366
14132
|
if (metadataRow) {
|
|
13367
14133
|
const mo = llmAgentToChatAgentOptions(metadataRow);
|
|
13368
14134
|
metadataLlmAgentChatOptions = {
|
|
@@ -13371,15 +14137,13 @@ function createCmsApiHandler(config) {
|
|
|
13371
14137
|
max_tokens: mo.max_tokens
|
|
13372
14138
|
};
|
|
13373
14139
|
metadataLlmAgentResolution = {
|
|
13374
|
-
slug:
|
|
14140
|
+
slug: metadataRow.slug,
|
|
13375
14141
|
model: metadataRow.model?.trim() || null,
|
|
13376
14142
|
temperature: metadataRow.temperature ?? null,
|
|
13377
14143
|
maxTokens: metadataRow.maxTokens ?? null
|
|
13378
14144
|
};
|
|
13379
14145
|
}
|
|
13380
|
-
socialRow = await
|
|
13381
|
-
where: { slug: BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG, deleted: false, enabled: true }
|
|
13382
|
-
});
|
|
14146
|
+
socialRow = await findLlmAgentByScope(dataSource, entityMap, LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST);
|
|
13383
14147
|
if (socialRow) {
|
|
13384
14148
|
const so = llmAgentToChatAgentOptions(socialRow);
|
|
13385
14149
|
socialLlmAgentChatOptions = {
|
|
@@ -13388,7 +14152,7 @@ function createCmsApiHandler(config) {
|
|
|
13388
14152
|
max_tokens: so.max_tokens
|
|
13389
14153
|
};
|
|
13390
14154
|
socialLlmAgentResolution = {
|
|
13391
|
-
slug:
|
|
14155
|
+
slug: socialRow.slug,
|
|
13392
14156
|
model: socialRow.model?.trim() || null,
|
|
13393
14157
|
temperature: socialRow.temperature ?? null,
|
|
13394
14158
|
maxTokens: socialRow.maxTokens ?? null
|
|
@@ -13412,8 +14176,8 @@ function createCmsApiHandler(config) {
|
|
|
13412
14176
|
const out = await svc.generateBlogMarkdownFromRss({
|
|
13413
14177
|
llm,
|
|
13414
14178
|
rssUrls,
|
|
13415
|
-
systemInstruction,
|
|
13416
|
-
validationRules,
|
|
14179
|
+
systemInstruction: systemInstruction ?? blogCreationRow?.systemInstruction?.trim() ?? void 0,
|
|
14180
|
+
validationRules: validationRules ?? blogCreationRow?.validationRules?.trim() ?? void 0,
|
|
13417
14181
|
categoryNamesHint: categoryRows.map((c) => c.name),
|
|
13418
14182
|
tagNamesHint: tagNames,
|
|
13419
14183
|
llmAgentChatOptions,
|
|
@@ -13898,6 +14662,22 @@ function createCmsApiHandler(config) {
|
|
|
13898
14662
|
return config.json({ error: message }, { status: 500 });
|
|
13899
14663
|
}
|
|
13900
14664
|
}
|
|
14665
|
+
if (path2[0] === "track" && path2.length === 2 && m === "GET") {
|
|
14666
|
+
const token = path2[1];
|
|
14667
|
+
if (!token) return config.json({ error: "Token required" }, { status: 400 });
|
|
14668
|
+
try {
|
|
14669
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
14670
|
+
const order = await orderRepo.findOne({
|
|
14671
|
+
where: { qrToken: token, deleted: false },
|
|
14672
|
+
relations: ["contact", "items", "items.product"]
|
|
14673
|
+
});
|
|
14674
|
+
if (!order) return config.json({ error: "Order not found" }, { status: 404 });
|
|
14675
|
+
return config.json(order);
|
|
14676
|
+
} catch (err) {
|
|
14677
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
14678
|
+
return config.json({ error: message }, { status: 500 });
|
|
14679
|
+
}
|
|
14680
|
+
}
|
|
13901
14681
|
if (path2.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
13902
14682
|
const resource = resolveResource(path2[0]);
|
|
13903
14683
|
if (!crudResources.includes(resource)) {
|
|
@@ -15099,8 +15879,8 @@ function createStorefrontApiHandler(config) {
|
|
|
15099
15879
|
let emailVerificationSent = false;
|
|
15100
15880
|
if (requireEmailVerification && getCms) {
|
|
15101
15881
|
try {
|
|
15102
|
-
const
|
|
15103
|
-
const rawToken =
|
|
15882
|
+
const crypto3 = await import("crypto");
|
|
15883
|
+
const rawToken = crypto3.randomBytes(32).toString("hex");
|
|
15104
15884
|
const expiresAt = new Date(Date.now() + SIGNUP_VERIFY_EXPIRY_HOURS * 60 * 60 * 1e3);
|
|
15105
15885
|
await tokenRepo().save(
|
|
15106
15886
|
tokenRepo().create({ email, token: rawToken, expiresAt })
|