@infuro/cms-core 1.0.7 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.cjs +270 -41
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +3 -1
- package/dist/admin.d.ts +3 -1
- package/dist/admin.js +304 -75
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +131 -1
- 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 +131 -1
- package/dist/api.js.map +1 -1
- package/dist/{index-BPnATEXW.d.cts → index-P5ajDo8-.d.cts} +9 -0
- package/dist/{index-BPnATEXW.d.ts → index-P5ajDo8-.d.ts} +9 -0
- package/dist/index.cjs +699 -263
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -3
- package/dist/index.d.ts +91 -3
- package/dist/index.js +682 -252
- package/dist/index.js.map +1 -1
- package/dist/migrations/1731800000000-ChatAndKnowledgeBase.ts +39 -0
- package/dist/migrations/1731900000000-KnowledgeBaseVector.ts +17 -0
- package/package.json +1 -1
package/dist/api.cjs
CHANGED
|
@@ -1271,6 +1271,129 @@ function createSettingsApiHandlers(config) {
|
|
|
1271
1271
|
}
|
|
1272
1272
|
};
|
|
1273
1273
|
}
|
|
1274
|
+
var KB_CHUNK_LIMIT = 10;
|
|
1275
|
+
var KB_CONTEXT_MAX_CHARS = 4e3;
|
|
1276
|
+
function getQueryTerms(message) {
|
|
1277
|
+
return message.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 6);
|
|
1278
|
+
}
|
|
1279
|
+
function createChatHandlers(config) {
|
|
1280
|
+
const { dataSource, entityMap, json, getCms } = config;
|
|
1281
|
+
const contactRepo = () => dataSource.getRepository(entityMap.contacts);
|
|
1282
|
+
const convRepo = () => dataSource.getRepository(entityMap.chat_conversations);
|
|
1283
|
+
const msgRepo = () => dataSource.getRepository(entityMap.chat_messages);
|
|
1284
|
+
const chunkRepo = () => dataSource.getRepository(entityMap.knowledge_base_chunks);
|
|
1285
|
+
return {
|
|
1286
|
+
async identify(req) {
|
|
1287
|
+
try {
|
|
1288
|
+
const body = await req.json();
|
|
1289
|
+
const name = body?.name?.trim();
|
|
1290
|
+
const email = body?.email?.trim();
|
|
1291
|
+
if (!name || !email) return json({ error: "name and email required" }, { status: 400 });
|
|
1292
|
+
const repo = contactRepo();
|
|
1293
|
+
let contact = await repo.findOne({ where: { email, deleted: false } });
|
|
1294
|
+
if (!contact) {
|
|
1295
|
+
const created = repo.create({ name, email, phone: body.phone?.trim() || null });
|
|
1296
|
+
contact = await repo.save(created);
|
|
1297
|
+
}
|
|
1298
|
+
const convRepoInst = convRepo();
|
|
1299
|
+
const conv = await convRepoInst.save(convRepoInst.create({ contactId: contact.id }));
|
|
1300
|
+
return json({
|
|
1301
|
+
contactId: contact.id,
|
|
1302
|
+
conversationId: conv.id
|
|
1303
|
+
});
|
|
1304
|
+
} catch (err) {
|
|
1305
|
+
const message = err instanceof Error ? err.message : "Failed to identify";
|
|
1306
|
+
return json({ error: "Failed to identify", detail: message }, { status: 500 });
|
|
1307
|
+
}
|
|
1308
|
+
},
|
|
1309
|
+
async getMessages(req, conversationId) {
|
|
1310
|
+
try {
|
|
1311
|
+
const conv = await convRepo().findOne({
|
|
1312
|
+
where: { id: parseInt(conversationId, 10) },
|
|
1313
|
+
relations: ["messages"]
|
|
1314
|
+
});
|
|
1315
|
+
if (!conv) return json({ error: "Conversation not found" }, { status: 404 });
|
|
1316
|
+
const messages = (conv.messages ?? []).sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()).map((m) => ({ role: m.role, content: m.content }));
|
|
1317
|
+
return json({ messages });
|
|
1318
|
+
} catch {
|
|
1319
|
+
return json({ error: "Failed to fetch messages" }, { status: 500 });
|
|
1320
|
+
}
|
|
1321
|
+
},
|
|
1322
|
+
async postMessage(req) {
|
|
1323
|
+
try {
|
|
1324
|
+
const body = await req.json();
|
|
1325
|
+
const conversationId = body?.conversationId;
|
|
1326
|
+
const message = body?.message?.trim();
|
|
1327
|
+
if (!conversationId || !message) return json({ error: "conversationId and message required" }, { status: 400 });
|
|
1328
|
+
const conv = await convRepo().findOne({
|
|
1329
|
+
where: { id: conversationId },
|
|
1330
|
+
relations: ["messages"]
|
|
1331
|
+
});
|
|
1332
|
+
if (!conv) return json({ error: "Conversation not found" }, { status: 404 });
|
|
1333
|
+
const msgRepoInst = msgRepo();
|
|
1334
|
+
await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "user", content: message }));
|
|
1335
|
+
const cms = await getCms();
|
|
1336
|
+
const llm = cms.getPlugin("llm");
|
|
1337
|
+
if (!llm?.chat) return json({ error: "LLM not configured" }, { status: 503 });
|
|
1338
|
+
let contextParts = [];
|
|
1339
|
+
const queryEmbedding = llm.embed ? await llm.embed(message) : null;
|
|
1340
|
+
if (queryEmbedding && queryEmbedding.length > 0) {
|
|
1341
|
+
const vectorStr = "[" + queryEmbedding.join(",") + "]";
|
|
1342
|
+
try {
|
|
1343
|
+
const rows = await dataSource.query(
|
|
1344
|
+
`SELECT id, content FROM knowledge_base_chunks WHERE embedding IS NOT NULL ORDER BY embedding <=> $1::vector LIMIT $2`,
|
|
1345
|
+
[vectorStr, KB_CHUNK_LIMIT]
|
|
1346
|
+
);
|
|
1347
|
+
let totalLen = 0;
|
|
1348
|
+
for (const r of rows) {
|
|
1349
|
+
const text = (r.content ?? "").trim();
|
|
1350
|
+
if (!text || totalLen + text.length > KB_CONTEXT_MAX_CHARS) continue;
|
|
1351
|
+
contextParts.push(text);
|
|
1352
|
+
totalLen += text.length;
|
|
1353
|
+
}
|
|
1354
|
+
} catch {
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (contextParts.length === 0) {
|
|
1358
|
+
const terms = getQueryTerms(message);
|
|
1359
|
+
if (terms.length > 0) {
|
|
1360
|
+
const conditions = terms.map((t) => ({ content: (0, import_typeorm2.ILike)(`%${t}%`) }));
|
|
1361
|
+
const chunks = await chunkRepo().find({
|
|
1362
|
+
where: conditions,
|
|
1363
|
+
take: KB_CHUNK_LIMIT,
|
|
1364
|
+
order: { id: "ASC" }
|
|
1365
|
+
});
|
|
1366
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1367
|
+
let totalLen = 0;
|
|
1368
|
+
for (const c of chunks) {
|
|
1369
|
+
const text = c.content.trim();
|
|
1370
|
+
if (seen.has(text) || totalLen + text.length > KB_CONTEXT_MAX_CHARS) continue;
|
|
1371
|
+
seen.add(text);
|
|
1372
|
+
contextParts.push(text);
|
|
1373
|
+
totalLen += text.length;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
const history = (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 }));
|
|
1378
|
+
const systemContent = contextParts.length > 0 ? `Use the following context about the company and its products to answer. If the answer is not in the context, say so.
|
|
1379
|
+
|
|
1380
|
+
Context:
|
|
1381
|
+
${contextParts.join("\n\n")}` : "You are a helpful assistant for the company. If you do not have specific information, say so.";
|
|
1382
|
+
const messages = [
|
|
1383
|
+
{ role: "system", content: systemContent },
|
|
1384
|
+
...history,
|
|
1385
|
+
{ role: "user", content: message }
|
|
1386
|
+
];
|
|
1387
|
+
const { content } = await llm.chat(messages);
|
|
1388
|
+
await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "assistant", content }));
|
|
1389
|
+
return json({ content });
|
|
1390
|
+
} catch (err) {
|
|
1391
|
+
const msg = err instanceof Error ? err.message : "";
|
|
1392
|
+
return json({ error: msg || "Failed to send message" }, { status: 500 });
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1274
1397
|
|
|
1275
1398
|
// src/api/cms-api-handler.ts
|
|
1276
1399
|
var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set(["users", "password_reset_tokens", "user_groups", "permissions", "comments", "form_fields", "configs"]);
|
|
@@ -1293,7 +1416,8 @@ function createCmsApiHandler(config) {
|
|
|
1293
1416
|
usersApi,
|
|
1294
1417
|
userAvatar,
|
|
1295
1418
|
userProfile,
|
|
1296
|
-
settings: settingsConfig
|
|
1419
|
+
settings: settingsConfig,
|
|
1420
|
+
chat: chatConfig
|
|
1297
1421
|
} = config;
|
|
1298
1422
|
const analytics = analyticsConfig ?? (getCms ? {
|
|
1299
1423
|
json: config.json,
|
|
@@ -1338,6 +1462,7 @@ function createCmsApiHandler(config) {
|
|
|
1338
1462
|
const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
|
|
1339
1463
|
const profilePut = userProfile ? createUserProfileHandler(userProfile) : null;
|
|
1340
1464
|
const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
|
|
1465
|
+
const chatHandlers = chatConfig ? createChatHandlers(chatConfig) : null;
|
|
1341
1466
|
function resolveResource(segment) {
|
|
1342
1467
|
const model = pathToModel(segment);
|
|
1343
1468
|
return crudResources.includes(model) ? model : segment;
|
|
@@ -1397,6 +1522,11 @@ function createCmsApiHandler(config) {
|
|
|
1397
1522
|
if (method === "GET") return settingsHandlers.GET(req, path[1]);
|
|
1398
1523
|
if (method === "PUT") return settingsHandlers.PUT(req, path[1]);
|
|
1399
1524
|
}
|
|
1525
|
+
if (path[0] === "chat" && chatHandlers) {
|
|
1526
|
+
if (path.length === 2 && path[1] === "identify" && method === "POST") return chatHandlers.identify(req);
|
|
1527
|
+
if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" && method === "GET") return chatHandlers.getMessages(req, path[2]);
|
|
1528
|
+
if (path.length === 2 && path[1] === "messages" && method === "POST") return chatHandlers.postMessage(req);
|
|
1529
|
+
}
|
|
1400
1530
|
if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
|
|
1401
1531
|
const resource = resolveResource(path[0]);
|
|
1402
1532
|
if (!crudResources.includes(resource)) return config.json({ error: "Invalid resource" }, { status: 400 });
|