@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/api.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- export { A as AnalyticsHandlerConfig, a as AuthHandlersConfig, B as BlogBySlugConfig, C as ChangePasswordConfig, b as CmsApiHandlerConfig, c as CmsGetter, d as CrudHandlerOptions, D as DashboardStatsConfig, E as EntityMap, F as ForgotPasswordConfig, e as FormBySlugConfig, I as InviteAcceptConfig, f as SetPasswordConfig, g as SettingsApiConfig, U as UploadHandlerConfig, h as UserAuthApiConfig, i as UserAvatarConfig, j as UserProfileConfig, k as UsersApiConfig, l as createAnalyticsHandlers, m as createBlogBySlugHandler, n as createChangePasswordHandler, o as createCmsApiHandler, p as createCrudByIdHandler, q as createCrudHandler, r as createDashboardStatsHandler, s as createForgotPasswordHandler, t as createFormBySlugHandler, u as createInviteAcceptHandler, v as createSetPasswordHandler, w as createSettingsApiHandlers, x as createUploadHandler, y as createUserAuthApiRouter, z as createUserAvatarHandler, G as createUserProfileHandler, H as createUsersApiHandlers } from './index-BPnATEXW.cjs';
1
+ export { A as AnalyticsHandlerConfig, a as AuthHandlersConfig, B as BlogBySlugConfig, C as ChangePasswordConfig, b as CmsApiHandlerConfig, c as CmsGetter, d as CrudHandlerOptions, D as DashboardStatsConfig, E as EntityMap, F as ForgotPasswordConfig, e as FormBySlugConfig, I as InviteAcceptConfig, f as SetPasswordConfig, g as SettingsApiConfig, U as UploadHandlerConfig, h as UserAuthApiConfig, i as UserAvatarConfig, j as UserProfileConfig, k as UsersApiConfig, l as createAnalyticsHandlers, m as createBlogBySlugHandler, n as createChangePasswordHandler, o as createCmsApiHandler, p as createCrudByIdHandler, q as createCrudHandler, r as createDashboardStatsHandler, s as createForgotPasswordHandler, t as createFormBySlugHandler, u as createInviteAcceptHandler, v as createSetPasswordHandler, w as createSettingsApiHandlers, x as createUploadHandler, y as createUserAuthApiRouter, z as createUserAvatarHandler, G as createUserProfileHandler, H as createUsersApiHandlers } from './index-P5ajDo8-.cjs';
2
2
  import 'typeorm';
package/dist/api.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { A as AnalyticsHandlerConfig, a as AuthHandlersConfig, B as BlogBySlugConfig, C as ChangePasswordConfig, b as CmsApiHandlerConfig, c as CmsGetter, d as CrudHandlerOptions, D as DashboardStatsConfig, E as EntityMap, F as ForgotPasswordConfig, e as FormBySlugConfig, I as InviteAcceptConfig, f as SetPasswordConfig, g as SettingsApiConfig, U as UploadHandlerConfig, h as UserAuthApiConfig, i as UserAvatarConfig, j as UserProfileConfig, k as UsersApiConfig, l as createAnalyticsHandlers, m as createBlogBySlugHandler, n as createChangePasswordHandler, o as createCmsApiHandler, p as createCrudByIdHandler, q as createCrudHandler, r as createDashboardStatsHandler, s as createForgotPasswordHandler, t as createFormBySlugHandler, u as createInviteAcceptHandler, v as createSetPasswordHandler, w as createSettingsApiHandlers, x as createUploadHandler, y as createUserAuthApiRouter, z as createUserAvatarHandler, G as createUserProfileHandler, H as createUsersApiHandlers } from './index-BPnATEXW.js';
1
+ export { A as AnalyticsHandlerConfig, a as AuthHandlersConfig, B as BlogBySlugConfig, C as ChangePasswordConfig, b as CmsApiHandlerConfig, c as CmsGetter, d as CrudHandlerOptions, D as DashboardStatsConfig, E as EntityMap, F as ForgotPasswordConfig, e as FormBySlugConfig, I as InviteAcceptConfig, f as SetPasswordConfig, g as SettingsApiConfig, U as UploadHandlerConfig, h as UserAuthApiConfig, i as UserAvatarConfig, j as UserProfileConfig, k as UsersApiConfig, l as createAnalyticsHandlers, m as createBlogBySlugHandler, n as createChangePasswordHandler, o as createCmsApiHandler, p as createCrudByIdHandler, q as createCrudHandler, r as createDashboardStatsHandler, s as createForgotPasswordHandler, t as createFormBySlugHandler, u as createInviteAcceptHandler, v as createSetPasswordHandler, w as createSettingsApiHandlers, x as createUploadHandler, y as createUserAuthApiRouter, z as createUserAvatarHandler, G as createUserProfileHandler, H as createUsersApiHandlers } from './index-P5ajDo8-.js';
2
2
  import 'typeorm';
package/dist/api.js CHANGED
@@ -1226,6 +1226,129 @@ function createSettingsApiHandlers(config) {
1226
1226
  }
1227
1227
  };
1228
1228
  }
1229
+ var KB_CHUNK_LIMIT = 10;
1230
+ var KB_CONTEXT_MAX_CHARS = 4e3;
1231
+ function getQueryTerms(message) {
1232
+ return message.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2).slice(0, 6);
1233
+ }
1234
+ function createChatHandlers(config) {
1235
+ const { dataSource, entityMap, json, getCms } = config;
1236
+ const contactRepo = () => dataSource.getRepository(entityMap.contacts);
1237
+ const convRepo = () => dataSource.getRepository(entityMap.chat_conversations);
1238
+ const msgRepo = () => dataSource.getRepository(entityMap.chat_messages);
1239
+ const chunkRepo = () => dataSource.getRepository(entityMap.knowledge_base_chunks);
1240
+ return {
1241
+ async identify(req) {
1242
+ try {
1243
+ const body = await req.json();
1244
+ const name = body?.name?.trim();
1245
+ const email = body?.email?.trim();
1246
+ if (!name || !email) return json({ error: "name and email required" }, { status: 400 });
1247
+ const repo = contactRepo();
1248
+ let contact = await repo.findOne({ where: { email, deleted: false } });
1249
+ if (!contact) {
1250
+ const created = repo.create({ name, email, phone: body.phone?.trim() || null });
1251
+ contact = await repo.save(created);
1252
+ }
1253
+ const convRepoInst = convRepo();
1254
+ const conv = await convRepoInst.save(convRepoInst.create({ contactId: contact.id }));
1255
+ return json({
1256
+ contactId: contact.id,
1257
+ conversationId: conv.id
1258
+ });
1259
+ } catch (err) {
1260
+ const message = err instanceof Error ? err.message : "Failed to identify";
1261
+ return json({ error: "Failed to identify", detail: message }, { status: 500 });
1262
+ }
1263
+ },
1264
+ async getMessages(req, conversationId) {
1265
+ try {
1266
+ const conv = await convRepo().findOne({
1267
+ where: { id: parseInt(conversationId, 10) },
1268
+ relations: ["messages"]
1269
+ });
1270
+ if (!conv) return json({ error: "Conversation not found" }, { status: 404 });
1271
+ 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 }));
1272
+ return json({ messages });
1273
+ } catch {
1274
+ return json({ error: "Failed to fetch messages" }, { status: 500 });
1275
+ }
1276
+ },
1277
+ async postMessage(req) {
1278
+ try {
1279
+ const body = await req.json();
1280
+ const conversationId = body?.conversationId;
1281
+ const message = body?.message?.trim();
1282
+ if (!conversationId || !message) return json({ error: "conversationId and message required" }, { status: 400 });
1283
+ const conv = await convRepo().findOne({
1284
+ where: { id: conversationId },
1285
+ relations: ["messages"]
1286
+ });
1287
+ if (!conv) return json({ error: "Conversation not found" }, { status: 404 });
1288
+ const msgRepoInst = msgRepo();
1289
+ await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "user", content: message }));
1290
+ const cms = await getCms();
1291
+ const llm = cms.getPlugin("llm");
1292
+ if (!llm?.chat) return json({ error: "LLM not configured" }, { status: 503 });
1293
+ let contextParts = [];
1294
+ const queryEmbedding = llm.embed ? await llm.embed(message) : null;
1295
+ if (queryEmbedding && queryEmbedding.length > 0) {
1296
+ const vectorStr = "[" + queryEmbedding.join(",") + "]";
1297
+ try {
1298
+ const rows = await dataSource.query(
1299
+ `SELECT id, content FROM knowledge_base_chunks WHERE embedding IS NOT NULL ORDER BY embedding <=> $1::vector LIMIT $2`,
1300
+ [vectorStr, KB_CHUNK_LIMIT]
1301
+ );
1302
+ let totalLen = 0;
1303
+ for (const r of rows) {
1304
+ const text = (r.content ?? "").trim();
1305
+ if (!text || totalLen + text.length > KB_CONTEXT_MAX_CHARS) continue;
1306
+ contextParts.push(text);
1307
+ totalLen += text.length;
1308
+ }
1309
+ } catch {
1310
+ }
1311
+ }
1312
+ if (contextParts.length === 0) {
1313
+ const terms = getQueryTerms(message);
1314
+ if (terms.length > 0) {
1315
+ const conditions = terms.map((t) => ({ content: ILike2(`%${t}%`) }));
1316
+ const chunks = await chunkRepo().find({
1317
+ where: conditions,
1318
+ take: KB_CHUNK_LIMIT,
1319
+ order: { id: "ASC" }
1320
+ });
1321
+ const seen = /* @__PURE__ */ new Set();
1322
+ let totalLen = 0;
1323
+ for (const c of chunks) {
1324
+ const text = c.content.trim();
1325
+ if (seen.has(text) || totalLen + text.length > KB_CONTEXT_MAX_CHARS) continue;
1326
+ seen.add(text);
1327
+ contextParts.push(text);
1328
+ totalLen += text.length;
1329
+ }
1330
+ }
1331
+ }
1332
+ 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 }));
1333
+ 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.
1334
+
1335
+ Context:
1336
+ ${contextParts.join("\n\n")}` : "You are a helpful assistant for the company. If you do not have specific information, say so.";
1337
+ const messages = [
1338
+ { role: "system", content: systemContent },
1339
+ ...history,
1340
+ { role: "user", content: message }
1341
+ ];
1342
+ const { content } = await llm.chat(messages);
1343
+ await msgRepoInst.save(msgRepoInst.create({ conversationId, role: "assistant", content }));
1344
+ return json({ content });
1345
+ } catch (err) {
1346
+ const msg = err instanceof Error ? err.message : "";
1347
+ return json({ error: msg || "Failed to send message" }, { status: 500 });
1348
+ }
1349
+ }
1350
+ };
1351
+ }
1229
1352
 
1230
1353
  // src/api/cms-api-handler.ts
1231
1354
  var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set(["users", "password_reset_tokens", "user_groups", "permissions", "comments", "form_fields", "configs"]);
@@ -1248,7 +1371,8 @@ function createCmsApiHandler(config) {
1248
1371
  usersApi,
1249
1372
  userAvatar,
1250
1373
  userProfile,
1251
- settings: settingsConfig
1374
+ settings: settingsConfig,
1375
+ chat: chatConfig
1252
1376
  } = config;
1253
1377
  const analytics = analyticsConfig ?? (getCms ? {
1254
1378
  json: config.json,
@@ -1293,6 +1417,7 @@ function createCmsApiHandler(config) {
1293
1417
  const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
1294
1418
  const profilePut = userProfile ? createUserProfileHandler(userProfile) : null;
1295
1419
  const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
1420
+ const chatHandlers = chatConfig ? createChatHandlers(chatConfig) : null;
1296
1421
  function resolveResource(segment) {
1297
1422
  const model = pathToModel(segment);
1298
1423
  return crudResources.includes(model) ? model : segment;
@@ -1352,6 +1477,11 @@ function createCmsApiHandler(config) {
1352
1477
  if (method === "GET") return settingsHandlers.GET(req, path[1]);
1353
1478
  if (method === "PUT") return settingsHandlers.PUT(req, path[1]);
1354
1479
  }
1480
+ if (path[0] === "chat" && chatHandlers) {
1481
+ if (path.length === 2 && path[1] === "identify" && method === "POST") return chatHandlers.identify(req);
1482
+ if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" && method === "GET") return chatHandlers.getMessages(req, path[2]);
1483
+ if (path.length === 2 && path[1] === "messages" && method === "POST") return chatHandlers.postMessage(req);
1484
+ }
1355
1485
  if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
1356
1486
  const resource = resolveResource(path[0]);
1357
1487
  if (!crudResources.includes(resource)) return config.json({ error: "Invalid resource" }, { status: 400 });