@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/dist/admin.cjs CHANGED
@@ -1594,7 +1594,7 @@ var defaultValue = {
1594
1594
  var AdminConfigContext = (0, import_react4.createContext)(defaultValue);
1595
1595
 
1596
1596
  // src/lib/cms-version.ts
1597
- var CMS_VERSION = true ? "1.0.31" : "0.0.0";
1597
+ var CMS_VERSION = true ? "1.0.33" : "0.0.0";
1598
1598
 
1599
1599
  // src/components/Admin/Sidebar.tsx
1600
1600
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -2078,6 +2078,34 @@ var BUILTIN_PLUGIN_DESCRIPTORS = [
2078
2078
  }
2079
2079
  ];
2080
2080
 
2081
+ // src/auth/auth-debug.ts
2082
+ var LOG_PREFIX = "[cms-auth]";
2083
+ function isAuthDebugClientEnabled() {
2084
+ if (typeof window === "undefined") return false;
2085
+ const v = process.env.NEXT_PUBLIC_CMS_AUTH_DEBUG?.trim().toLowerCase();
2086
+ if (v === "1" || v === "true" || v === "yes") return true;
2087
+ if (v === "0" || v === "false" || v === "no") return false;
2088
+ return process.env.NODE_ENV === "development";
2089
+ }
2090
+ function logAuthClient(message, data) {
2091
+ if (!isAuthDebugClientEnabled()) return;
2092
+ if (data) console.info(LOG_PREFIX, message, data);
2093
+ else console.info(LOG_PREFIX, message);
2094
+ }
2095
+ function summarizeSessionUserForLog(user) {
2096
+ if (!user || typeof user !== "object") return { present: false };
2097
+ const u = user;
2098
+ return {
2099
+ present: true,
2100
+ email: u.email ?? null,
2101
+ id: u.id ?? null,
2102
+ adminAccess: u.adminAccess ?? null,
2103
+ isRBACAdmin: u.isRBACAdmin ?? null,
2104
+ isVendorPortal: u.isVendorPortal ?? null,
2105
+ groupName: u.groupName ?? null
2106
+ };
2107
+ }
2108
+
2081
2109
  // src/admin/pages/AdminLayout.tsx
2082
2110
  var import_jsx_runtime7 = require("react/jsx-runtime");
2083
2111
  var PUBLIC_ADMIN_PATHS = [
@@ -2111,6 +2139,14 @@ function AdminLayoutInner({ children }) {
2111
2139
  (0, import_react9.useEffect)(() => {
2112
2140
  if (isMobile) setMoreSheetOpen(false);
2113
2141
  }, [isMobile, pathname]);
2142
+ (0, import_react9.useEffect)(() => {
2143
+ logAuthClient("AdminLayout session state", {
2144
+ pathname,
2145
+ isPublicPath,
2146
+ status,
2147
+ sessionUser: summarizeSessionUserForLog(session?.user)
2148
+ });
2149
+ }, [pathname, isPublicPath, status, session?.user]);
2114
2150
  (0, import_react9.useEffect)(() => {
2115
2151
  if (isPublicPath) return;
2116
2152
  if (status === "loading") {
@@ -2118,6 +2154,7 @@ function AdminLayoutInner({ children }) {
2118
2154
  return () => clearTimeout(timeout);
2119
2155
  }
2120
2156
  if (status === "unauthenticated") {
2157
+ logAuthClient("AdminLayout redirect \u2192 /admin/signin (unauthenticated)", { pathname });
2121
2158
  router.replace("/admin/signin");
2122
2159
  } else {
2123
2160
  setLoadingTimeout(false);
@@ -9075,7 +9112,9 @@ var SigninPage = () => {
9075
9112
  const router = (0, import_navigation8.useRouter)();
9076
9113
  const { status } = (0, import_react31.useSession)();
9077
9114
  (0, import_react30.useEffect)(() => {
9115
+ logAuthClient("SignInPage session status", { status });
9078
9116
  if (status === "authenticated") {
9117
+ logAuthClient("SignInPage redirect \u2192 /admin/dashboard (already authenticated)");
9079
9118
  router.replace("/admin/dashboard");
9080
9119
  }
9081
9120
  }, [status, router]);
@@ -9084,14 +9123,22 @@ var SigninPage = () => {
9084
9123
  setLoading(true);
9085
9124
  setError("");
9086
9125
  try {
9126
+ logAuthClient("SignInPage signIn start", { email });
9087
9127
  const result = await (0, import_react31.signIn)("credentials", {
9088
9128
  email,
9089
9129
  password,
9090
9130
  redirect: false
9091
9131
  });
9132
+ logAuthClient("SignInPage signIn result", {
9133
+ ok: result?.ok ?? null,
9134
+ error: result?.error ?? null,
9135
+ status: result?.status ?? null,
9136
+ url: result?.url ?? null
9137
+ });
9092
9138
  if (result?.error) {
9093
9139
  setError("Invalid email or password");
9094
9140
  } else {
9141
+ logAuthClient("SignInPage navigate \u2192 /admin/dashboard");
9095
9142
  window.location.assign("/admin/dashboard");
9096
9143
  }
9097
9144
  } catch (error2) {
@@ -10210,6 +10257,7 @@ init_utils();
10210
10257
  init_table();
10211
10258
  init_DetailPageLayout();
10212
10259
  init_DetailPageHeader();
10260
+ var import_react_qr_code = require("react-qr-code");
10213
10261
  var import_jsx_runtime52 = require("react/jsx-runtime");
10214
10262
  function formatMoney(amount, currency = "INR") {
10215
10263
  return new Intl.NumberFormat("en-IN", {
@@ -10567,6 +10615,19 @@ function OrderDetailPage({ orderId }) {
10567
10615
  },
10568
10616
  p.id
10569
10617
  )) })
10618
+ ] }),
10619
+ order.qrToken && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("section", { children: [
10620
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("h2", { className: "text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2", children: "Order QR Code" }),
10621
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("div", { className: "min-w-0 border border-gray-200 rounded-lg p-4 bg-gray-50/50 flex flex-col items-center gap-2", children: [
10622
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
10623
+ import_react_qr_code.QRCode,
10624
+ {
10625
+ value: `${window.location.origin}/track/${order.qrToken}`,
10626
+ size: 180
10627
+ }
10628
+ ),
10629
+ /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("p", { className: "text-xs text-gray-400 break-all text-center", children: order.qrToken })
10630
+ ] })
10570
10631
  ] })
10571
10632
  ] })
10572
10633
  }
@@ -16115,6 +16176,155 @@ function serializeEmailRecipients(emails) {
16115
16176
  init_button();
16116
16177
  init_input();
16117
16178
  var import_sonner12 = require("sonner");
16179
+
16180
+ // src/plugins/llm/chat-email-intent.ts
16181
+ function normalizeIntentKey(raw) {
16182
+ return raw.trim().toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
16183
+ }
16184
+ function parseIntentsJson(raw) {
16185
+ if (!raw?.trim()) return null;
16186
+ try {
16187
+ const parsed = JSON.parse(raw);
16188
+ if (!Array.isArray(parsed)) return null;
16189
+ const out = [];
16190
+ for (const row of parsed) {
16191
+ if (!row || typeof row !== "object") continue;
16192
+ const o = row;
16193
+ const intent = normalizeIntentKey(String(o.intent ?? o.id ?? ""));
16194
+ const description = String(o.description ?? "").trim();
16195
+ const emailTo = String(o.emailTo ?? o.email ?? "").trim();
16196
+ if (!intent || !description || !emailTo) continue;
16197
+ out.push({ intent, description, emailTo });
16198
+ }
16199
+ return out.length > 0 ? out : null;
16200
+ } catch {
16201
+ return null;
16202
+ }
16203
+ }
16204
+ function dedupeIntents(intents) {
16205
+ const seen = /* @__PURE__ */ new Set();
16206
+ const out = [];
16207
+ for (const row of intents) {
16208
+ const key = normalizeIntentKey(row.intent);
16209
+ if (!key || seen.has(key)) continue;
16210
+ seen.add(key);
16211
+ out.push({
16212
+ intent: key,
16213
+ description: row.description.trim(),
16214
+ emailTo: row.emailTo.trim()
16215
+ });
16216
+ }
16217
+ return out;
16218
+ }
16219
+ function parseEmailIntentsFromSettings(raw) {
16220
+ return dedupeIntents(parseIntentsJson(raw) ?? []);
16221
+ }
16222
+ function serializeChatLeadIntents(intents) {
16223
+ return JSON.stringify(
16224
+ dedupeIntents(intents).map((r) => ({
16225
+ intent: r.intent,
16226
+ description: r.description,
16227
+ emailTo: r.emailTo
16228
+ }))
16229
+ );
16230
+ }
16231
+ var EMAIL_TOOL_VALIDATION_KEY = "emailTool";
16232
+ function parseEmailToolObject(raw) {
16233
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
16234
+ const o = raw;
16235
+ const intentsRaw = o.intents;
16236
+ let intents;
16237
+ if (Array.isArray(intentsRaw)) {
16238
+ const parsed = parseIntentsJson(JSON.stringify(intentsRaw));
16239
+ if (parsed?.length) intents = parsed;
16240
+ }
16241
+ return {
16242
+ enabled: o.enabled === true,
16243
+ classifierInstructions: typeof o.classifierInstructions === "string" ? o.classifierInstructions.trim() : void 0,
16244
+ toolPrompt: typeof o.toolPrompt === "string" ? o.toolPrompt.trim() : void 0,
16245
+ intents
16246
+ };
16247
+ }
16248
+ function splitValidationRulesForAdmin(validationRulesText) {
16249
+ const raw = validationRulesText?.trim() ?? "";
16250
+ if (!raw) return { guardrailsJson: "", emailTool: null };
16251
+ try {
16252
+ const parsed = JSON.parse(raw);
16253
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
16254
+ return { guardrailsJson: raw, emailTool: null };
16255
+ }
16256
+ const emailTool = parseEmailToolObject(parsed[EMAIL_TOOL_VALIDATION_KEY]);
16257
+ const rest = { ...parsed };
16258
+ delete rest[EMAIL_TOOL_VALIDATION_KEY];
16259
+ const guardrailsJson = Object.keys(rest).length > 0 ? JSON.stringify(rest, null, 2) : "";
16260
+ return { guardrailsJson, emailTool };
16261
+ } catch {
16262
+ return { guardrailsJson: raw, emailTool: null };
16263
+ }
16264
+ }
16265
+ function mergeEmailToolIntoValidationRules(guardrailsJson, emailTool) {
16266
+ const intents = dedupeIntents(emailTool.intents);
16267
+ const hasEmail = intents.length > 0 || emailTool.classifierInstructions.trim() !== "" || emailTool.toolPrompt.trim() !== "";
16268
+ let base = {};
16269
+ const raw = guardrailsJson?.trim();
16270
+ if (raw) {
16271
+ try {
16272
+ const parsed = JSON.parse(raw);
16273
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
16274
+ base = { ...parsed };
16275
+ delete base[EMAIL_TOOL_VALIDATION_KEY];
16276
+ } else {
16277
+ return raw;
16278
+ }
16279
+ } catch {
16280
+ return raw;
16281
+ }
16282
+ }
16283
+ if (!hasEmail) {
16284
+ return Object.keys(base).length > 0 ? JSON.stringify(base, null, 2) : null;
16285
+ }
16286
+ base[EMAIL_TOOL_VALIDATION_KEY] = {
16287
+ enabled: true,
16288
+ classifierInstructions: emailTool.classifierInstructions.trim(),
16289
+ toolPrompt: emailTool.toolPrompt.trim(),
16290
+ intents: intents.map((r) => ({
16291
+ intent: r.intent,
16292
+ description: r.description,
16293
+ emailTo: r.emailTo
16294
+ }))
16295
+ };
16296
+ return JSON.stringify(base, null, 2);
16297
+ }
16298
+
16299
+ // src/plugins/llm/llm-agent-scope.ts
16300
+ var LLM_AGENT_SCOPE_CHATBOT = "chatbot";
16301
+ var LLM_AGENT_SCOPE_BLOG_CREATION = "blog_creation";
16302
+ var LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST = "social_media_post";
16303
+ var LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT = "email_intent_chatbot";
16304
+ var LLM_AGENT_SCOPE_BLOG_METADATA = "blog_metadata";
16305
+ var LLM_AGENT_DEFAULT_SLUG_BY_SCOPE = {
16306
+ [LLM_AGENT_SCOPE_CHATBOT]: "site-chat-assistant",
16307
+ [LLM_AGENT_SCOPE_BLOG_CREATION]: "blog-generator",
16308
+ [LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]: "blog-generator-social",
16309
+ [LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]: "email-intent-chatbot",
16310
+ [LLM_AGENT_SCOPE_BLOG_METADATA]: "blog-generator-metadata"
16311
+ };
16312
+ var LLM_AGENT_DEFAULT_NAME_BY_SCOPE = {
16313
+ [LLM_AGENT_SCOPE_CHATBOT]: "Site Chat Assistant",
16314
+ [LLM_AGENT_SCOPE_BLOG_CREATION]: "Blog Generator",
16315
+ [LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]: "Blog Generator (Social)",
16316
+ [LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]: "Chat Lead Intent Classifier",
16317
+ [LLM_AGENT_SCOPE_BLOG_METADATA]: "Blog Generator (Metadata)"
16318
+ };
16319
+ var SITE_CHAT_ASSISTANT_SLUG = LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16320
+ var SITE_CHAT_ASSISTANT_NAME = LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16321
+ var BLOG_LLM_AGENT_SLUGS = [
16322
+ LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_BLOG_CREATION],
16323
+ LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_BLOG_METADATA],
16324
+ LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_SOCIAL_MEDIA_POST]
16325
+ ];
16326
+
16327
+ // src/admin/pages/PluginsPage.tsx
16118
16328
  var import_jsx_runtime66 = require("react/jsx-runtime");
16119
16329
  function normalizeLinkedInOrganizations(payload) {
16120
16330
  if (!payload || typeof payload !== "object") return [];
@@ -16154,10 +16364,6 @@ function normalizeFacebookGraphPages(graph) {
16154
16364
  }
16155
16365
  return out;
16156
16366
  }
16157
- function slugifyAgentKey(name) {
16158
- const s = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
16159
- return s || "agent";
16160
- }
16161
16367
  function normalizeChatMode(raw) {
16162
16368
  if (raw === "external" || raw === "llm") return raw;
16163
16369
  return "whatsapp";
@@ -16296,6 +16502,8 @@ function PluginSettingsPanel({
16296
16502
  descriptor,
16297
16503
  onSaved
16298
16504
  }) {
16505
+ const { pluginDescriptors = [] } = (0, import_react51.useContext)(AdminConfigContext);
16506
+ const emailPluginAvailable = pluginDescriptors.some((p) => p.name === "email" && p.enabled !== false);
16299
16507
  const settingsGroup = descriptor.settingsGroup;
16300
16508
  const isLlm = settingsGroup === "llm";
16301
16509
  const isEmail = settingsGroup === "email";
@@ -16331,9 +16539,19 @@ function PluginSettingsPanel({
16331
16539
  const [agentTemp, setAgentTemp] = (0, import_react51.useState)("");
16332
16540
  const [agentMaxTokens, setAgentMaxTokens] = (0, import_react51.useState)("");
16333
16541
  const [agentValidationJson, setAgentValidationJson] = (0, import_react51.useState)("");
16542
+ const [emailToolEnabled, setEmailToolEnabled] = (0, import_react51.useState)(false);
16543
+ const [emailIntents, setEmailIntents] = (0, import_react51.useState)([]);
16544
+ const [emailToolPrompt, setEmailToolPrompt] = (0, import_react51.useState)("");
16334
16545
  const [agentLoading, setAgentLoading] = (0, import_react51.useState)(false);
16335
16546
  const [agentSaving, setAgentSaving] = (0, import_react51.useState)(false);
16336
16547
  const [agentProvisionError, setAgentProvisionError] = (0, import_react51.useState)(null);
16548
+ const [llmConfigTab, setLlmConfigTab] = (0, import_react51.useState)("chat");
16549
+ const [notifyEmailAgentId, setNotifyEmailAgentId] = (0, import_react51.useState)(null);
16550
+ const [notifyEmailName, setNotifyEmailName] = (0, import_react51.useState)("");
16551
+ const [notifyEmailSystem, setNotifyEmailSystem] = (0, import_react51.useState)("");
16552
+ const [notifyEmailLoading, setNotifyEmailLoading] = (0, import_react51.useState)(false);
16553
+ const [notifyEmailSaving, setNotifyEmailSaving] = (0, import_react51.useState)(false);
16554
+ const [notifyEmailProvisionError, setNotifyEmailProvisionError] = (0, import_react51.useState)(null);
16337
16555
  const [kbCatalog, setKbCatalog] = (0, import_react51.useState)([]);
16338
16556
  const [attachedAgentKnowledge, setAttachedAgentKnowledge] = (0, import_react51.useState)([]);
16339
16557
  const [attachedKbLoading, setAttachedKbLoading] = (0, import_react51.useState)(false);
@@ -16555,7 +16773,12 @@ function PluginSettingsPanel({
16555
16773
  setIconImageUrl(data.iconImageUrl ?? "");
16556
16774
  setIconBackgroundColor(data.iconBackgroundColor ?? "#6366f1");
16557
16775
  setHeaderColor(data.headerColor ?? "#6366f1");
16558
- setAttachedAgentSlug(data.attachedAgentSlug ?? "");
16776
+ setAttachedAgentSlug((data.attachedAgentSlug ?? "").trim());
16777
+ setEmailToolEnabled(data.emailToolEnabled === "true");
16778
+ setEmailIntents(parseEmailIntentsFromSettings(data.emailIntents));
16779
+ const legacyClassifier = data.emailClassifierInstructions ?? [data.emailIntentPrompt, data.emailNegativeIntentPrompt].filter(Boolean).join("\n\n") ?? "";
16780
+ if (legacyClassifier) setNotifyEmailSystem(legacyClassifier);
16781
+ setEmailToolPrompt(data.emailToolPrompt ?? "");
16559
16782
  }
16560
16783
  if (isErp) {
16561
16784
  setErpPipelineName(data.pipelineName ?? data.pipelineId ?? "");
@@ -16641,33 +16864,116 @@ function PluginSettingsPanel({
16641
16864
  }).catch(() => setErpFormsCatalog([]));
16642
16865
  }, [isErp, loading]);
16643
16866
  const fetchLlmAgents = (0, import_react51.useCallback)(async () => {
16644
- const res = await fetch("/api/llm_agents?limit=100&sortField=name&sortOrder=asc");
16867
+ const res = await fetch(
16868
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_CHATBOT}&limit=5&sortField=name&sortOrder=asc`
16869
+ );
16645
16870
  if (!res.ok) {
16646
16871
  setLlmAgents([]);
16872
+ setAgentId(null);
16647
16873
  return;
16648
16874
  }
16649
16875
  const j = await res.json();
16650
- const list = (j.data ?? []).map((r) => ({
16876
+ const chatRows = (j.data ?? []).filter(
16877
+ (r) => String(r.scope ?? "").trim() === LLM_AGENT_SCOPE_CHATBOT || String(r.slug ?? "").trim() === LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT]
16878
+ );
16879
+ const list = chatRows.map((r) => ({
16651
16880
  id: r.id,
16652
16881
  name: String(r.name ?? ""),
16653
16882
  slug: String(r.slug ?? ""),
16883
+ scope: LLM_AGENT_SCOPE_CHATBOT,
16654
16884
  enabled: r.enabled !== false
16655
16885
  }));
16656
16886
  setLlmAgents(list);
16657
16887
  const pick = list.find((a) => a.enabled) ?? list[0];
16658
- const fullRow = (j.data ?? []).find((r) => r.id === pick?.id);
16888
+ const fullRow = pick ? chatRows.find((r) => r.id === pick.id) : void 0;
16659
16889
  if (pick && fullRow) {
16660
16890
  setAgentId(pick.id);
16661
16891
  setAgentName(pick.name);
16662
16892
  setAgentSlug(pick.slug);
16893
+ setAttachedAgentSlug(pick.slug);
16663
16894
  setAgentSystem(String(fullRow.systemInstruction ?? ""));
16664
16895
  setAgentModel(String(fullRow.model ?? ""));
16665
16896
  setAgentTemp(fullRow.temperature != null ? String(fullRow.temperature) : "");
16666
16897
  setAgentMaxTokens(fullRow.maxTokens != null ? String(fullRow.maxTokens) : "");
16667
- setAgentValidationJson(String(fullRow.validationRules ?? ""));
16668
- setAttachedAgentSlug(pick.slug);
16898
+ const { guardrailsJson } = splitValidationRulesForAdmin(fullRow.validationRules ?? null);
16899
+ setAgentValidationJson(guardrailsJson);
16900
+ } else {
16901
+ setAgentId(null);
16902
+ setAgentName(botName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT]);
16903
+ setAgentSlug(LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT]);
16904
+ setAgentSystem("");
16905
+ setAgentModel("");
16906
+ setAgentTemp("");
16907
+ setAgentMaxTokens("");
16908
+ setAgentValidationJson("");
16909
+ }
16910
+ }, [botName]);
16911
+ const fetchNotifyEmailAgent = (0, import_react51.useCallback)(async () => {
16912
+ const res = await fetch(
16913
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT}&limit=1&sortField=name&sortOrder=asc`
16914
+ );
16915
+ if (!res.ok) {
16916
+ setNotifyEmailAgentId(null);
16917
+ return;
16918
+ }
16919
+ const j = await res.json();
16920
+ const row = j.data?.find((r) => String(r.scope ?? "").trim() === LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT) ?? j.data?.find(
16921
+ (r) => String(r.slug ?? "").trim() === LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]
16922
+ ) ?? j.data?.[0];
16923
+ if (!row || String(row.scope ?? "").trim() !== LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT) {
16924
+ setNotifyEmailAgentId(null);
16925
+ setNotifyEmailName(LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT]);
16926
+ setNotifyEmailSystem("");
16927
+ return;
16928
+ }
16929
+ setNotifyEmailAgentId(row.id);
16930
+ setNotifyEmailName(String(row.name ?? ""));
16931
+ setNotifyEmailSystem(String(row.systemInstruction ?? ""));
16932
+ const { emailTool } = splitValidationRulesForAdmin(row.validationRules ?? null);
16933
+ if (emailTool?.intents?.length) setEmailIntents(emailTool.intents);
16934
+ if (emailTool?.toolPrompt) setEmailToolPrompt(emailTool.toolPrompt);
16935
+ if (!row.systemInstruction?.trim() && emailTool?.classifierInstructions) {
16936
+ setNotifyEmailSystem(emailTool.classifierInstructions);
16669
16937
  }
16670
16938
  }, []);
16939
+ const bootstrapNotifyEmailAgent = (0, import_react51.useCallback)(async () => {
16940
+ setNotifyEmailProvisionError(null);
16941
+ const listRes = await fetch(
16942
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT}&limit=1`
16943
+ );
16944
+ if (!listRes.ok) {
16945
+ const message = `Could not load notify email agent (HTTP ${listRes.status}).`;
16946
+ setNotifyEmailProvisionError(message);
16947
+ await fetchNotifyEmailAgent();
16948
+ return { ok: false, message };
16949
+ }
16950
+ const listJ = await listRes.json();
16951
+ const notifyRow = listJ.data?.find((a) => a.scope === LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT);
16952
+ if (notifyRow) {
16953
+ await fetchNotifyEmailAgent();
16954
+ return { ok: true };
16955
+ }
16956
+ const createRes = await fetch("/api/llm_agents", {
16957
+ method: "POST",
16958
+ headers: { "Content-Type": "application/json" },
16959
+ body: JSON.stringify({
16960
+ name: LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT],
16961
+ slug: LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT],
16962
+ scope: LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT,
16963
+ systemInstruction: "",
16964
+ enabled: true
16965
+ })
16966
+ });
16967
+ if (!createRes.ok) {
16968
+ const errBody = await createRes.json().catch(() => ({}));
16969
+ const message = errBody.error ?? `Could not create notify email agent (HTTP ${createRes.status}).`;
16970
+ setNotifyEmailProvisionError(message);
16971
+ await fetchNotifyEmailAgent();
16972
+ return { ok: false, message };
16973
+ }
16974
+ await fetchNotifyEmailAgent();
16975
+ return { ok: true };
16976
+ }, [fetchNotifyEmailAgent]);
16671
16977
  const fetchKbCatalog = (0, import_react51.useCallback)(async () => {
16672
16978
  try {
16673
16979
  const res = await fetch("/api/knowledge_base_documents?limit=300&sortField=name&sortOrder=asc");
@@ -16713,7 +17019,9 @@ function PluginSettingsPanel({
16713
17019
  }, []);
16714
17020
  const bootstrapLlmAgentForPlugins = (0, import_react51.useCallback)(async () => {
16715
17021
  setAgentProvisionError(null);
16716
- const listRes = await fetch("/api/llm_agents?limit=100&sortField=name&sortOrder=asc");
17022
+ const listRes = await fetch(
17023
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_CHATBOT}&limit=5&sortField=name&sortOrder=asc`
17024
+ );
16717
17025
  if (!listRes.ok) {
16718
17026
  const errBody = await listRes.json().catch(() => ({}));
16719
17027
  const message = errBody.error ?? `Could not load agents (HTTP ${listRes.status}). Ensure your app uses the latest @infuro/cms-core and your admin user can read entity "llm_agents".`;
@@ -16722,16 +17030,25 @@ function PluginSettingsPanel({
16722
17030
  return { ok: false, message };
16723
17031
  }
16724
17032
  const listJ = await listRes.json();
16725
- if (listJ.data?.length) {
17033
+ const chatRow = listJ.data?.find(
17034
+ (a) => String(a.scope ?? "").trim() === LLM_AGENT_SCOPE_CHATBOT || String(a.slug ?? "").trim() === LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT]
17035
+ );
17036
+ if (chatRow) {
16726
17037
  await fetchLlmAgents();
16727
17038
  return { ok: true };
16728
17039
  }
16729
- const defaultName = botName.trim() || "Assistant";
16730
- const defaultSlug = slugifyAgentKey(defaultName);
17040
+ const defaultName = botName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
17041
+ const defaultSlug = LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16731
17042
  const createRes = await fetch("/api/llm_agents", {
16732
17043
  method: "POST",
16733
17044
  headers: { "Content-Type": "application/json" },
16734
- body: JSON.stringify({ name: defaultName, slug: defaultSlug, systemInstruction: "", enabled: true })
17045
+ body: JSON.stringify({
17046
+ name: defaultName,
17047
+ slug: defaultSlug,
17048
+ scope: LLM_AGENT_SCOPE_CHATBOT,
17049
+ systemInstruction: "",
17050
+ enabled: true
17051
+ })
16735
17052
  });
16736
17053
  if (!createRes.ok) {
16737
17054
  const errBody = await createRes.json().catch(() => ({}));
@@ -16757,11 +17074,7 @@ function PluginSettingsPanel({
16757
17074
  }
16758
17075
  };
16759
17076
  const handleCreateAssistantFromForm = async () => {
16760
- const name = agentName.trim() || botName.trim();
16761
- if (!name) {
16762
- import_sonner12.toast.error("Enter an assistant name");
16763
- return;
16764
- }
17077
+ const name = agentName.trim() || botName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16765
17078
  const tempRaw = agentTemp.trim();
16766
17079
  const maxRaw = agentMaxTokens.trim();
16767
17080
  const temperature = tempRaw === "" ? null : Number(tempRaw);
@@ -16774,7 +17087,7 @@ function PluginSettingsPanel({
16774
17087
  import_sonner12.toast.error("Max tokens must be a positive integer");
16775
17088
  return;
16776
17089
  }
16777
- const slug = slugifyAgentKey(name);
17090
+ const slug = LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
16778
17091
  setAgentSaving(true);
16779
17092
  try {
16780
17093
  const res = await fetch("/api/llm_agents", {
@@ -16783,6 +17096,7 @@ function PluginSettingsPanel({
16783
17096
  body: JSON.stringify({
16784
17097
  name,
16785
17098
  slug,
17099
+ scope: LLM_AGENT_SCOPE_CHATBOT,
16786
17100
  systemInstruction: agentSystem.trim(),
16787
17101
  model: agentModel.trim() || null,
16788
17102
  temperature,
@@ -16809,18 +17123,19 @@ function PluginSettingsPanel({
16809
17123
  (0, import_react51.useEffect)(() => {
16810
17124
  if (!isLlm || loading || chatMode !== "llm") return;
16811
17125
  setAgentLoading(true);
17126
+ setNotifyEmailLoading(true);
16812
17127
  void (async () => {
16813
17128
  try {
16814
17129
  await bootstrapLlmAgentForPlugins();
17130
+ await bootstrapNotifyEmailAgent();
17131
+ await fetchNotifyEmailAgent();
17132
+ await fetchKbCatalog();
16815
17133
  } finally {
16816
17134
  setAgentLoading(false);
17135
+ setNotifyEmailLoading(false);
16817
17136
  }
16818
17137
  })();
16819
- }, [isLlm, loading, chatMode, bootstrapLlmAgentForPlugins]);
16820
- (0, import_react51.useEffect)(() => {
16821
- if (!isLlm || loading || chatMode !== "llm") return;
16822
- void fetchKbCatalog();
16823
- }, [isLlm, loading, chatMode, fetchKbCatalog]);
17138
+ }, [isLlm, loading, chatMode, bootstrapLlmAgentForPlugins, bootstrapNotifyEmailAgent, fetchNotifyEmailAgent, fetchKbCatalog]);
16824
17139
  (0, import_react51.useEffect)(() => {
16825
17140
  if (!isLlm || loading || chatMode !== "llm" || !attachedAgentSlug.trim()) {
16826
17141
  setAttachedAgentKnowledge([]);
@@ -16911,7 +17226,13 @@ function PluginSettingsPanel({
16911
17226
  payload.iconImageUrl = { value: iconImageUrl, type: "public" };
16912
17227
  payload.iconBackgroundColor = { value: iconBackgroundColor, type: "public" };
16913
17228
  payload.headerColor = { value: headerColor, type: "public" };
16914
- payload.attachedAgentSlug = { value: attachedAgentSlug.trim(), type: "public" };
17229
+ payload.attachedAgentSlug = {
17230
+ value: agentSlug.trim() || attachedAgentSlug.trim() || LLM_AGENT_DEFAULT_SLUG_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT],
17231
+ type: "public"
17232
+ };
17233
+ payload.emailToolEnabled = { value: emailToolEnabled ? "true" : "false", type: "public" };
17234
+ payload.emailIntents = { value: serializeChatLeadIntents(emailIntents), type: "public" };
17235
+ payload.emailToolPrompt = { value: emailToolPrompt, type: "public" };
16915
17236
  }
16916
17237
  if (isEmail) {
16917
17238
  payload.salesTeamEmails = { value: serializeEmailRecipients(salesTeamEmails), type: "public" };
@@ -16927,65 +17248,169 @@ function PluginSettingsPanel({
16927
17248
  }
16928
17249
  return payload;
16929
17250
  };
16930
- const handleSaveAgent = async () => {
16931
- if (!agentId) {
16932
- import_sonner12.toast.error("No agent to save");
16933
- return;
16934
- }
16935
- const name = agentName.trim();
16936
- if (!name) {
16937
- import_sonner12.toast.error("Agent name is required");
16938
- return;
16939
- }
17251
+ const savePluginSettings = async () => {
17252
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
17253
+ method: "PUT",
17254
+ headers: { "Content-Type": "application/json" },
17255
+ body: JSON.stringify(buildPayload())
17256
+ });
17257
+ return res.ok;
17258
+ };
17259
+ const validateChatAgentNumbers = () => {
16940
17260
  const tempRaw = agentTemp.trim();
16941
17261
  const maxRaw = agentMaxTokens.trim();
16942
17262
  const temperature = tempRaw === "" ? null : Number(tempRaw);
16943
17263
  const maxTokens = maxRaw === "" ? null : parseInt(maxRaw, 10);
16944
17264
  if (tempRaw !== "" && !Number.isFinite(temperature)) {
16945
17265
  import_sonner12.toast.error("Temperature must be a number");
16946
- return;
17266
+ return null;
16947
17267
  }
16948
17268
  if (maxRaw !== "" && (!Number.isFinite(maxTokens) || maxTokens < 1)) {
16949
17269
  import_sonner12.toast.error("Max tokens must be a positive integer");
17270
+ return null;
17271
+ }
17272
+ return { temperature, maxTokens };
17273
+ };
17274
+ const persistChatAgent = async () => {
17275
+ if (!agentId) return false;
17276
+ const nums = validateChatAgentNumbers();
17277
+ if (!nums) return false;
17278
+ const name = agentName.trim() || botName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_CHATBOT];
17279
+ const res = await fetch(`/api/llm_agents/${agentId}`, {
17280
+ method: "PUT",
17281
+ headers: { "Content-Type": "application/json" },
17282
+ body: JSON.stringify({
17283
+ name,
17284
+ systemInstruction: agentSystem.trim(),
17285
+ model: agentModel.trim() || null,
17286
+ temperature: nums.temperature,
17287
+ maxTokens: nums.maxTokens,
17288
+ validationRules: agentValidationJson.trim() || null,
17289
+ enabled: true
17290
+ })
17291
+ });
17292
+ if (!res.ok) return false;
17293
+ setAttachedAgentSlug(agentSlug);
17294
+ return true;
17295
+ };
17296
+ const persistNotifyEmailAgent = async (agentIdOverride) => {
17297
+ const id = agentIdOverride ?? notifyEmailAgentId;
17298
+ if (!id) return false;
17299
+ const validationRules = mergeEmailToolIntoValidationRules(null, {
17300
+ classifierInstructions: "",
17301
+ toolPrompt: emailToolPrompt,
17302
+ intents: emailIntents
17303
+ });
17304
+ const res = await fetch(`/api/llm_agents/${id}`, {
17305
+ method: "PUT",
17306
+ headers: { "Content-Type": "application/json" },
17307
+ body: JSON.stringify({
17308
+ name: notifyEmailName.trim() || LLM_AGENT_DEFAULT_NAME_BY_SCOPE[LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT],
17309
+ systemInstruction: notifyEmailSystem.trim(),
17310
+ validationRules,
17311
+ enabled: true
17312
+ })
17313
+ });
17314
+ return res.ok;
17315
+ };
17316
+ const saveLlmPluginSettingsOnly = async () => {
17317
+ const res = await fetch(`/api/settings/${settingsGroup}`, {
17318
+ method: "PUT",
17319
+ headers: { "Content-Type": "application/json" },
17320
+ body: JSON.stringify(buildPayload())
17321
+ });
17322
+ return res.ok;
17323
+ };
17324
+ const handleSaveLlmChatTab = async () => {
17325
+ if (!agentId) {
17326
+ import_sonner12.toast.error("Wait for the assistant to load, or click Create assistant");
16950
17327
  return;
16951
17328
  }
16952
- setAgentSaving(true);
17329
+ if (!validateChatAgentNumbers()) return;
17330
+ setSaving(true);
16953
17331
  try {
16954
- const res = await fetch(`/api/llm_agents/${agentId}`, {
16955
- method: "PUT",
16956
- headers: { "Content-Type": "application/json" },
16957
- body: JSON.stringify({
16958
- name,
16959
- systemInstruction: agentSystem.trim(),
16960
- model: agentModel.trim() || null,
16961
- temperature,
16962
- maxTokens,
16963
- validationRules: agentValidationJson.trim() || null,
16964
- enabled: true
16965
- })
16966
- });
16967
- if (!res.ok) {
16968
- const err = await res.json().catch(() => ({}));
16969
- import_sonner12.toast.error(err.error || "Failed to update agent");
17332
+ const chatOk = await persistChatAgent();
17333
+ if (!chatOk) {
17334
+ import_sonner12.toast.error("Failed to save assistant");
17335
+ return;
17336
+ }
17337
+ const settingsOk = await saveLlmPluginSettingsOnly();
17338
+ if (!settingsOk) {
17339
+ import_sonner12.toast.error("Failed to save widget settings");
16970
17340
  return;
16971
17341
  }
16972
- setAttachedAgentSlug(agentSlug);
16973
- import_sonner12.toast.success("Agent saved");
17342
+ import_sonner12.toast.success("Chat settings saved");
16974
17343
  } catch {
16975
- import_sonner12.toast.error("Failed to update agent");
17344
+ import_sonner12.toast.error("Failed to save");
16976
17345
  } finally {
16977
- setAgentSaving(false);
17346
+ setSaving(false);
17347
+ }
17348
+ };
17349
+ const handleSaveLlmNotifyTab = async () => {
17350
+ if (!emailPluginAvailable) {
17351
+ import_sonner12.toast.error("Enable the Email plugin (SMTP) first");
17352
+ return;
17353
+ }
17354
+ const filledIntents = emailIntents.filter(
17355
+ (r) => r.intent.trim() && r.description.trim() && r.emailTo.trim()
17356
+ );
17357
+ if (filledIntents.length === 0) {
17358
+ import_sonner12.toast.error("Add at least one intent with Intent, Description, and Email To");
17359
+ return;
17360
+ }
17361
+ setEmailIntents(filledIntents);
17362
+ setNotifyEmailSaving(true);
17363
+ try {
17364
+ let notifyId = notifyEmailAgentId;
17365
+ if (!notifyId) {
17366
+ await bootstrapNotifyEmailAgent();
17367
+ const nr = await fetch(
17368
+ `/api/llm_agents?scope=${LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT}&limit=1`
17369
+ );
17370
+ if (nr.ok) {
17371
+ const nj = await nr.json();
17372
+ const row = nj.data?.find(
17373
+ (r) => String(r.scope ?? "").trim() === LLM_AGENT_SCOPE_EMAIL_INTENT_CHATBOT
17374
+ );
17375
+ if (row) {
17376
+ notifyId = row.id;
17377
+ setNotifyEmailAgentId(row.id);
17378
+ }
17379
+ }
17380
+ }
17381
+ if (!notifyId) {
17382
+ import_sonner12.toast.error("Could not set up lead email \u2014 try again");
17383
+ return;
17384
+ }
17385
+ setEmailToolEnabled(true);
17386
+ const notifyOk = await persistNotifyEmailAgent(notifyId);
17387
+ if (!notifyOk) {
17388
+ import_sonner12.toast.error("Failed to save lead email settings");
17389
+ return;
17390
+ }
17391
+ const settingsOk = await saveLlmPluginSettingsOnly();
17392
+ if (!settingsOk) {
17393
+ import_sonner12.toast.warning("Routing saved, but enable flag failed to save in settings");
17394
+ return;
17395
+ }
17396
+ import_sonner12.toast.success("Lead email routing saved");
17397
+ await fetchNotifyEmailAgent();
17398
+ } catch {
17399
+ import_sonner12.toast.error("Failed to save");
17400
+ } finally {
17401
+ setNotifyEmailSaving(false);
16978
17402
  }
16979
17403
  };
16980
17404
  const handleSave = async () => {
17405
+ if (isLlm && chatMode === "llm") {
17406
+ if (llmConfigTab === "notify") await handleSaveLlmNotifyTab();
17407
+ else await handleSaveLlmChatTab();
17408
+ return;
17409
+ }
16981
17410
  setSaving(true);
16982
17411
  try {
16983
- const res = await fetch(`/api/settings/${settingsGroup}`, {
16984
- method: "PUT",
16985
- headers: { "Content-Type": "application/json" },
16986
- body: JSON.stringify(buildPayload())
16987
- });
16988
- if (!res.ok) throw new Error();
17412
+ const ok = await savePluginSettings();
17413
+ if (!ok) throw new Error();
16989
17414
  if (isSocialMedia && (linkedinAccessToken.trim() || linkedinHasSavedToken)) {
16990
17415
  if (linkedinAccessToken.trim()) {
16991
17416
  setLinkedinHasSavedToken(true);
@@ -17907,328 +18332,458 @@ function PluginSettingsPanel({
17907
18332
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Only paste code from sources you trust." })
17908
18333
  ] }),
17909
18334
  chatMode === "llm" && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
17910
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50/80 dark:bg-gray-800/40 p-3 space-y-3", children: [
17911
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17912
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white", children: [
17913
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Bot, { className: "h-4 w-4" }),
17914
- "Chat assistant (single agent)"
17915
- ] }),
17916
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Configure one assistant for this site here. After it exists, attach knowledge files in the section below." })
17917
- ] }),
17918
- agentLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
17919
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 animate-spin" }),
17920
- "Loading assistant\u2026"
17921
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
17922
- agentProvisionError ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-100", children: agentProvisionError }) : null,
18335
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "inline-flex rounded-lg border border-gray-200 bg-gray-50 p-0.5 dark:border-gray-600 dark:bg-gray-800/80", children: [
18336
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18337
+ "button",
18338
+ {
18339
+ type: "button",
18340
+ className: `flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${llmConfigTab === "chat" ? "bg-white text-gray-900 shadow-sm dark:bg-gray-900 dark:text-white" : "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"}`,
18341
+ onClick: () => setLlmConfigTab("chat"),
18342
+ children: [
18343
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.MessageCircle, { className: "h-3.5 w-3.5 shrink-0" }),
18344
+ "Chat"
18345
+ ]
18346
+ }
18347
+ ),
18348
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18349
+ "button",
18350
+ {
18351
+ type: "button",
18352
+ className: `flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${llmConfigTab === "notify" ? "bg-white text-gray-900 shadow-sm dark:bg-gray-900 dark:text-white" : "text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"}`,
18353
+ onClick: () => setLlmConfigTab("notify"),
18354
+ children: [
18355
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Mail, { className: "h-3.5 w-3.5 shrink-0" }),
18356
+ "Notify"
18357
+ ]
18358
+ }
18359
+ )
18360
+ ] }),
18361
+ llmConfigTab === "chat" ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18362
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-lg border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900/40 p-3 space-y-3", children: [
18363
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Widget on your site" }),
18364
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Title, icon, and colors visitors see on the chat bubble." }),
17923
18365
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17924
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-name", className: "text-sm", children: "Assistant name" }),
18366
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-botName`, className: "text-sm", children: "Widget title" }),
17925
18367
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17926
18368
  Input,
17927
18369
  {
17928
- id: "agent-name",
17929
- value: agentName,
17930
- onChange: (e) => setAgentName(e.target.value),
18370
+ id: `${settingsGroup}-botName`,
18371
+ value: botName,
18372
+ onChange: (e) => setBotName(e.target.value),
17931
18373
  placeholder: "e.g. JM Buddy",
17932
18374
  className: "h-8 text-sm"
17933
18375
  }
17934
18376
  )
17935
18377
  ] }),
17936
18378
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17937
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-slug", className: "text-sm", children: "Slug" }),
18379
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-iconImageUrl`, className: "text-sm", children: "Icon image URL" }),
17938
18380
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17939
18381
  Input,
17940
18382
  {
17941
- id: "agent-slug",
17942
- value: agentId ? agentSlug : slugifyAgentKey((agentName || botName).trim() || "assistant"),
17943
- disabled: true,
17944
- className: "h-8 text-sm font-mono bg-gray-100 dark:bg-gray-700"
18383
+ id: `${settingsGroup}-iconImageUrl`,
18384
+ value: iconImageUrl,
18385
+ onChange: (e) => setIconImageUrl(e.target.value),
18386
+ placeholder: "https://\u2026 or /images/chat-icon.png",
18387
+ className: "h-8 text-sm"
17945
18388
  }
17946
18389
  ),
17947
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500", children: "Derived from the name. Used in API routes." })
18390
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "PNG or image URL. Leave empty to use emoji below." })
17948
18391
  ] }),
17949
18392
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17950
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-system", className: "text-sm", children: "System instruction" }),
18393
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-icon`, className: "text-sm", children: "Icon fallback (emoji)" }),
17951
18394
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17952
- Textarea,
18395
+ Input,
17953
18396
  {
17954
- id: "agent-system",
17955
- value: agentSystem,
17956
- onChange: (e) => setAgentSystem(e.target.value),
17957
- rows: 5,
17958
- placeholder: "How the model should behave\u2026",
17959
- className: "text-sm"
18397
+ id: `${settingsGroup}-icon`,
18398
+ value: icon,
18399
+ onChange: (e) => setIcon(e.target.value),
18400
+ placeholder: "e.g. \u{1F4AC}",
18401
+ className: "h-8 text-sm w-20"
17960
18402
  }
17961
18403
  )
17962
18404
  ] }),
17963
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
18405
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex gap-4", children: [
18406
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18407
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-iconBg`, className: "text-sm", children: "Icon background" }),
18408
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2", children: [
18409
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18410
+ "input",
18411
+ {
18412
+ type: "color",
18413
+ id: `${settingsGroup}-iconBg`,
18414
+ value: iconBackgroundColor,
18415
+ onChange: (e) => setIconBackgroundColor(e.target.value),
18416
+ className: "h-8 w-10 cursor-pointer rounded border border-gray-300 dark:border-gray-600"
18417
+ }
18418
+ ),
18419
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18420
+ Input,
18421
+ {
18422
+ value: iconBackgroundColor,
18423
+ onChange: (e) => setIconBackgroundColor(e.target.value),
18424
+ className: "h-8 w-24 text-sm font-mono"
18425
+ }
18426
+ )
18427
+ ] })
18428
+ ] }),
18429
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18430
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-headerColor`, className: "text-sm", children: "Header color" }),
18431
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2", children: [
18432
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18433
+ "input",
18434
+ {
18435
+ type: "color",
18436
+ id: `${settingsGroup}-headerColor`,
18437
+ value: headerColor,
18438
+ onChange: (e) => setHeaderColor(e.target.value),
18439
+ className: "h-8 w-10 cursor-pointer rounded border border-gray-300 dark:border-gray-600"
18440
+ }
18441
+ ),
18442
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18443
+ Input,
18444
+ {
18445
+ value: headerColor,
18446
+ onChange: (e) => setHeaderColor(e.target.value),
18447
+ className: "h-8 w-24 text-sm font-mono"
18448
+ }
18449
+ )
18450
+ ] })
18451
+ ] })
18452
+ ] })
18453
+ ] }),
18454
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50/80 dark:bg-gray-800/40 p-3 space-y-4", children: [
18455
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18456
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white", children: [
18457
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Bot, { className: "h-4 w-4" }),
18458
+ "Assistant"
18459
+ ] }),
18460
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Storefront assistant \u2014 model, instructions, and guardrails." })
18461
+ ] }),
18462
+ agentLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
18463
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 animate-spin" }),
18464
+ "Loading\u2026"
18465
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18466
+ agentProvisionError ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-100", children: agentProvisionError }) : null,
17964
18467
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17965
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-model", className: "text-sm", children: "Model (optional)" }),
18468
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-system", className: "text-sm", children: "System instruction" }),
17966
18469
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17967
- Input,
18470
+ Textarea,
17968
18471
  {
17969
- id: "agent-model",
17970
- value: agentModel,
17971
- onChange: (e) => setAgentModel(e.target.value),
17972
- placeholder: "Gateway model id",
17973
- className: "h-8 text-sm font-mono"
18472
+ id: "agent-system",
18473
+ value: agentSystem,
18474
+ onChange: (e) => setAgentSystem(e.target.value),
18475
+ rows: 5,
18476
+ placeholder: "How the model should behave\u2026",
18477
+ className: "text-sm"
17974
18478
  }
17975
18479
  )
17976
18480
  ] }),
18481
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
18482
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18483
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-model", className: "text-sm", children: "Model (optional)" }),
18484
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18485
+ Input,
18486
+ {
18487
+ id: "agent-model",
18488
+ value: agentModel,
18489
+ onChange: (e) => setAgentModel(e.target.value),
18490
+ placeholder: "Gateway model id",
18491
+ className: "h-8 text-sm font-mono"
18492
+ }
18493
+ )
18494
+ ] }),
18495
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18496
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-temp", className: "text-sm", children: "Temperature" }),
18497
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18498
+ Input,
18499
+ {
18500
+ id: "agent-temp",
18501
+ value: agentTemp,
18502
+ onChange: (e) => setAgentTemp(e.target.value),
18503
+ placeholder: "e.g. 0.7",
18504
+ className: "h-8 text-sm"
18505
+ }
18506
+ )
18507
+ ] })
18508
+ ] }),
17977
18509
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17978
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-temp", className: "text-sm", children: "Temperature" }),
18510
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-max", className: "text-sm", children: "Max tokens" }),
17979
18511
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17980
18512
  Input,
17981
18513
  {
17982
- id: "agent-temp",
17983
- value: agentTemp,
17984
- onChange: (e) => setAgentTemp(e.target.value),
17985
- placeholder: "e.g. 0.7",
18514
+ id: "agent-max",
18515
+ value: agentMaxTokens,
18516
+ onChange: (e) => setAgentMaxTokens(e.target.value.replace(/\D/g, "")),
18517
+ placeholder: "e.g. 1024",
17986
18518
  className: "h-8 text-sm"
17987
18519
  }
17988
18520
  )
17989
- ] })
17990
- ] }),
17991
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
17992
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-max", className: "text-sm", children: "Max tokens" }),
17993
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
17994
- Input,
17995
- {
17996
- id: "agent-max",
17997
- value: agentMaxTokens,
17998
- onChange: (e) => setAgentMaxTokens(e.target.value.replace(/\D/g, "")),
17999
- placeholder: "e.g. 1024",
18000
- className: "h-8 text-sm"
18001
- }
18002
- )
18521
+ ] }),
18522
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18523
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-validation", className: "text-sm", children: "Validation & output guardrails" }),
18524
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18525
+ Textarea,
18526
+ {
18527
+ id: "agent-validation",
18528
+ value: agentValidationJson,
18529
+ onChange: (e) => setAgentValidationJson(e.target.value),
18530
+ rows: 4,
18531
+ placeholder: 'Plain text or JSON: {"guardrails":"Never promise refunds.","maxUserChars":2000}',
18532
+ className: "text-xs font-mono"
18533
+ }
18534
+ )
18535
+ ] }),
18536
+ !agentId ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
18537
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18538
+ Button,
18539
+ {
18540
+ type: "button",
18541
+ size: "sm",
18542
+ className: "gap-1",
18543
+ disabled: agentSaving,
18544
+ onClick: () => void handleCreateAssistantFromForm(),
18545
+ children: agentSaving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18546
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18547
+ "Creating\u2026"
18548
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18549
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Bot, { className: "h-3.5 w-3.5" }),
18550
+ "Create assistant"
18551
+ ] })
18552
+ }
18553
+ ),
18554
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18555
+ Button,
18556
+ {
18557
+ type: "button",
18558
+ size: "sm",
18559
+ variant: "secondary",
18560
+ className: "gap-1",
18561
+ disabled: agentSaving || agentLoading,
18562
+ onClick: () => void handleRetryBootstrapAgent(),
18563
+ children: "Retry auto-setup"
18564
+ }
18565
+ )
18566
+ ] }) : null
18003
18567
  ] }),
18568
+ agentId && agentSlug.trim() ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-md border border-dashed border-gray-300 dark:border-gray-600 bg-white/60 dark:bg-gray-900/30 p-3 space-y-3", children: [
18569
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800 dark:text-gray-200", children: [
18570
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.FileUp, { className: "h-3.5 w-3.5 shrink-0" }),
18571
+ "Knowledge for this agent"
18572
+ ] }),
18573
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Upload text (.txt, .md, .json) or PDF (.pdf), or link documents already in the knowledge base." }),
18574
+ attachedKbLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Loading linked documents\u2026" }) : attachedAgentKnowledge.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "No documents linked yet." }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("ul", { className: "max-h-32 space-y-1.5 overflow-y-auto", children: attachedAgentKnowledge.map((d) => /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("li", { className: "flex items-center justify-between gap-2 text-sm", children: [
18575
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "min-w-0 truncate", title: d.name, children: d.name }),
18576
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18577
+ Button,
18578
+ {
18579
+ type: "button",
18580
+ variant: "ghost",
18581
+ size: "sm",
18582
+ className: "h-7 shrink-0 text-xs text-red-600 hover:text-red-700 dark:text-red-400",
18583
+ onClick: () => void handleUnlinkKbDoc(d.id),
18584
+ children: "Remove"
18585
+ }
18586
+ )
18587
+ ] }, d.id)) }),
18588
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "min-w-[180px] space-y-1", children: [
18589
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-xs", children: "Upload file" }),
18590
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "relative", children: [
18591
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18592
+ Input,
18593
+ {
18594
+ type: "file",
18595
+ accept: ".txt,.md,.json,.pdf,text/plain,text/markdown,application/json,application/pdf",
18596
+ disabled: uploadingAttachedKbFile || uploadingAttachedKbLink,
18597
+ className: "h-8 cursor-pointer text-xs disabled:opacity-60",
18598
+ onChange: onAttachedKbFileChange
18599
+ },
18600
+ attachedKbInputKey
18601
+ ),
18602
+ uploadingAttachedKbFile ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18603
+ "div",
18604
+ {
18605
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center gap-2 rounded-md bg-background/85 text-xs font-medium text-gray-700 dark:text-gray-200",
18606
+ "aria-live": "polite",
18607
+ children: [
18608
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 shrink-0 animate-spin" }),
18609
+ "Saving & linking\u2026"
18610
+ ]
18611
+ }
18612
+ ) : null
18613
+ ] }),
18614
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Pick a file to upload immediately (chunking, embeddings if configured, then link to this agent)." })
18615
+ ] }),
18616
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex flex-wrap items-end gap-2", children: [
18617
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "min-w-[200px] flex-1 space-y-1", children: [
18618
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-xs", children: "Attach existing document" }),
18619
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Select, { value: attachExistingDocId, onValueChange: setAttachExistingDocId, children: [
18620
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectValue, { placeholder: "Choose a document" }) }),
18621
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(SelectContent, { children: [
18622
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectItem, { value: "__none__", children: "\u2014 Select \u2014" }),
18623
+ kbCatalog.filter((d) => !attachedAgentKnowledge.some((a) => a.id === d.id)).map((d) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectItem, { value: String(d.id), children: d.name }, d.id))
18624
+ ] })
18625
+ ] })
18626
+ ] }),
18627
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18628
+ Button,
18629
+ {
18630
+ type: "button",
18631
+ size: "sm",
18632
+ className: "h-8",
18633
+ disabled: uploadingAttachedKbFile || uploadingAttachedKbLink || attachExistingDocId === "__none__",
18634
+ onClick: () => void handleAttachExistingToAttached(),
18635
+ children: uploadingAttachedKbLink ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18636
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18637
+ "Linking\u2026"
18638
+ ] }) : "Attach"
18639
+ }
18640
+ )
18641
+ ] })
18642
+ ] }) : null
18643
+ ] }),
18644
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18645
+ Button,
18646
+ {
18647
+ type: "button",
18648
+ size: "sm",
18649
+ onClick: () => void handleSaveLlmChatTab(),
18650
+ disabled: saving,
18651
+ className: "gap-1",
18652
+ children: [
18653
+ saving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Save, { className: "h-3.5 w-3.5" }),
18654
+ "Save"
18655
+ ]
18656
+ }
18657
+ )
18658
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-lg border border-gray-200 dark:border-gray-600 bg-gray-50/80 dark:bg-gray-800/40 p-3 space-y-4", children: [
18659
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { children: [
18660
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-sm font-medium text-gray-900 dark:text-white", children: "Lead email routing" }),
18661
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "mt-0.5 text-[11px] text-gray-500 dark:text-gray-400", children: "Routing instructions and intents. When a message matches, your team gets one email per conversation." })
18662
+ ] }),
18663
+ !emailPluginAvailable ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "rounded border border-amber-200 bg-amber-50/80 p-2 text-[11px] text-amber-700 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-300", children: "Enable the Email plugin (SMTP) to send lead alert emails." }) : notifyEmailLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
18664
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 animate-spin" }),
18665
+ "Loading\u2026"
18666
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18667
+ notifyEmailProvisionError ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900 dark:border-amber-800 dark:bg-amber-950/40 dark:text-amber-100", children: notifyEmailProvisionError }) : null,
18004
18668
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18005
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "agent-validation", className: "text-sm", children: "Validation & output guardrails" }),
18669
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: "notify-email-system", className: "text-sm", children: "Routing instructions" }),
18006
18670
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18007
18671
  Textarea,
18008
18672
  {
18009
- id: "agent-validation",
18010
- value: agentValidationJson,
18011
- onChange: (e) => setAgentValidationJson(e.target.value),
18673
+ id: "notify-email-system",
18674
+ value: notifyEmailSystem,
18675
+ onChange: (e) => setNotifyEmailSystem(e.target.value),
18012
18676
  rows: 4,
18013
- placeholder: 'Plain text or JSON: {"guardrails":"Never promise refunds.","maxUserChars":2000}',
18014
- className: "text-xs font-mono"
18015
- }
18016
- )
18017
- ] }),
18018
- !agentId ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
18019
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18020
- Button,
18021
- {
18022
- type: "button",
18023
- size: "sm",
18024
- className: "gap-1",
18025
- disabled: agentSaving,
18026
- onClick: () => void handleCreateAssistantFromForm(),
18027
- children: agentSaving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18028
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18029
- "Creating\u2026"
18030
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18031
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Bot, { className: "h-3.5 w-3.5" }),
18032
- "Create assistant"
18033
- ] })
18034
- }
18035
- ),
18036
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18037
- Button,
18038
- {
18039
- type: "button",
18040
- size: "sm",
18041
- variant: "secondary",
18042
- className: "gap-1",
18043
- disabled: agentSaving || agentLoading,
18044
- onClick: () => void handleRetryBootstrapAgent(),
18045
- children: "Retry auto-setup"
18677
+ placeholder: "When to treat a message as a lead, priority, when not to email\u2026",
18678
+ className: "text-sm"
18046
18679
  }
18047
18680
  )
18048
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18049
- Button,
18050
- {
18051
- type: "button",
18052
- size: "sm",
18053
- className: "gap-1",
18054
- disabled: agentSaving,
18055
- onClick: () => void handleSaveAgent(),
18056
- children: agentSaving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18057
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18058
- "Saving\u2026"
18059
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
18060
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Save, { className: "h-3.5 w-3.5" }),
18061
- "Save assistant"
18062
- ] })
18063
- }
18064
- )
18065
- ] }),
18066
- agentId && agentSlug.trim() ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "rounded-md border border-dashed border-gray-300 dark:border-gray-600 bg-white/60 dark:bg-gray-900/30 p-3 space-y-3", children: [
18067
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800 dark:text-gray-200", children: [
18068
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.FileUp, { className: "h-3.5 w-3.5 shrink-0" }),
18069
- "Knowledge for this agent"
18070
18681
  ] }),
18071
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Upload text (.txt, .md, .json) or PDF (.pdf), or link documents already in the knowledge base." }),
18072
- attachedKbLoading ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Loading linked documents\u2026" }) : attachedAgentKnowledge.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "No documents linked yet." }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("ul", { className: "max-h-32 space-y-1.5 overflow-y-auto", children: attachedAgentKnowledge.map((d) => /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("li", { className: "flex items-center justify-between gap-2 text-sm", children: [
18073
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("span", { className: "min-w-0 truncate", title: d.name, children: d.name }),
18074
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18075
- Button,
18076
- {
18077
- type: "button",
18078
- variant: "ghost",
18079
- size: "sm",
18080
- className: "h-7 shrink-0 text-xs text-red-600 hover:text-red-700 dark:text-red-400",
18081
- onClick: () => void handleUnlinkKbDoc(d.id),
18082
- children: "Remove"
18083
- }
18084
- )
18085
- ] }, d.id)) }),
18086
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "min-w-[180px] space-y-1", children: [
18087
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-xs", children: "Upload file" }),
18088
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "relative", children: [
18089
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18090
- Input,
18091
- {
18092
- type: "file",
18093
- accept: ".txt,.md,.json,.pdf,text/plain,text/markdown,application/json,application/pdf",
18094
- disabled: uploadingAttachedKbFile || uploadingAttachedKbLink,
18095
- className: "h-8 cursor-pointer text-xs disabled:opacity-60",
18096
- onChange: onAttachedKbFileChange
18097
- },
18098
- attachedKbInputKey
18099
- ),
18100
- uploadingAttachedKbFile ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18101
- "div",
18682
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-2", children: [
18683
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
18684
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-sm", children: "Intents" }),
18685
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18686
+ Button,
18102
18687
  {
18103
- className: "pointer-events-none absolute inset-0 flex items-center justify-center gap-2 rounded-md bg-background/85 text-xs font-medium text-gray-700 dark:text-gray-200",
18104
- "aria-live": "polite",
18688
+ type: "button",
18689
+ size: "sm",
18690
+ variant: "outline",
18691
+ className: "h-7 gap-1 text-xs",
18692
+ onClick: () => setEmailIntents((rows) => [
18693
+ ...rows,
18694
+ { intent: "", description: "", emailTo: "" }
18695
+ ]),
18105
18696
  children: [
18106
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-4 w-4 shrink-0 animate-spin" }),
18107
- "Saving & linking\u2026"
18697
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Plus, { className: "h-3 w-3" }),
18698
+ "Add intent"
18108
18699
  ]
18109
18700
  }
18110
- ) : null
18701
+ )
18111
18702
  ] }),
18112
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-[11px] text-gray-500 dark:text-gray-400", children: "Pick a file to upload immediately (chunking, embeddings if configured, then link to this agent)." })
18113
- ] }),
18114
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex flex-wrap items-end gap-2", children: [
18115
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "min-w-[200px] flex-1 space-y-1", children: [
18116
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { className: "text-xs", children: "Attach existing document" }),
18117
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Select, { value: attachExistingDocId, onValueChange: setAttachExistingDocId, children: [
18118
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectValue, { placeholder: "Choose a document" }) }),
18119
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(SelectContent, { children: [
18120
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectItem, { value: "__none__", children: "\u2014 Select \u2014" }),
18121
- kbCatalog.filter((d) => !attachedAgentKnowledge.some((a) => a.id === d.id)).map((d) => /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(SelectItem, { value: String(d.id), children: d.name }, d.id))
18122
- ] })
18703
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("div", { className: "overflow-x-auto rounded border border-gray-200 dark:border-gray-600", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("table", { className: "w-full text-xs", children: [
18704
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("tr", { className: "bg-gray-100/80 text-left dark:bg-gray-900/60", children: [
18705
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("th", { className: "w-[28%] p-2 font-medium", children: "Intent" }),
18706
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("th", { className: "w-[42%] p-2 font-medium", children: "Description" }),
18707
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("th", { className: "w-[26%] p-2 font-medium", children: "Email To" }),
18708
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("th", { className: "w-8 p-2" })
18709
+ ] }) }),
18710
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("tbody", { children: [
18711
+ emailIntents.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { colSpan: 4, className: "p-4 text-center text-gray-500 dark:text-gray-400", children: "Add at least one intent, then Save." }) }) : null,
18712
+ emailIntents.map((row, idx) => /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("tr", { className: "border-t border-gray-200 dark:border-gray-700", children: [
18713
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { className: "p-1.5 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18714
+ Input,
18715
+ {
18716
+ value: row.intent,
18717
+ onChange: (e) => {
18718
+ const v = e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, "");
18719
+ setEmailIntents(
18720
+ (rows) => rows.map((r, i) => i === idx ? { ...r, intent: v } : r)
18721
+ );
18722
+ },
18723
+ placeholder: "SALES_LEAD",
18724
+ className: "h-8 font-mono text-xs"
18725
+ }
18726
+ ) }),
18727
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { className: "p-1.5 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18728
+ Input,
18729
+ {
18730
+ value: row.description,
18731
+ onChange: (e) => setEmailIntents(
18732
+ (rows) => rows.map(
18733
+ (r, i) => i === idx ? { ...r, description: e.target.value } : r
18734
+ )
18735
+ ),
18736
+ placeholder: "What this intent means",
18737
+ className: "h-8 text-xs"
18738
+ }
18739
+ ) }),
18740
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { className: "p-1.5 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18741
+ Input,
18742
+ {
18743
+ value: row.emailTo,
18744
+ onChange: (e) => setEmailIntents(
18745
+ (rows) => rows.map(
18746
+ (r, i) => i === idx ? { ...r, emailTo: e.target.value } : r
18747
+ )
18748
+ ),
18749
+ placeholder: "team@company.com",
18750
+ className: "h-8 text-xs"
18751
+ }
18752
+ ) }),
18753
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("td", { className: "p-1.5 align-top", children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18754
+ Button,
18755
+ {
18756
+ type: "button",
18757
+ size: "icon",
18758
+ variant: "ghost",
18759
+ className: "h-8 w-8 text-red-600",
18760
+ onClick: () => setEmailIntents((rows) => rows.filter((_, i) => i !== idx)),
18761
+ "aria-label": "Remove intent",
18762
+ children: /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Trash2, { className: "h-3.5 w-3.5" })
18763
+ }
18764
+ ) })
18765
+ ] }, idx))
18123
18766
  ] })
18124
- ] }),
18125
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18126
- Button,
18127
- {
18128
- type: "button",
18129
- size: "sm",
18130
- className: "h-8",
18131
- disabled: uploadingAttachedKbFile || uploadingAttachedKbLink || attachExistingDocId === "__none__",
18132
- onClick: () => void handleAttachExistingToAttached(),
18133
- children: uploadingAttachedKbLink ? /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
18134
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
18135
- "Linking\u2026"
18136
- ] }) : "Attach"
18137
- }
18138
- )
18767
+ ] }) })
18139
18768
  ] })
18140
- ] }) : null
18141
- ] }),
18142
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18143
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-botName`, className: "text-sm", children: "Widget title" }),
18144
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18145
- Input,
18146
- {
18147
- id: `${settingsGroup}-botName`,
18148
- value: botName,
18149
- onChange: (e) => setBotName(e.target.value),
18150
- placeholder: "e.g. Support Bot",
18151
- className: "h-8 text-sm"
18152
- }
18153
- )
18154
- ] }),
18155
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18156
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-iconImageUrl`, className: "text-sm", children: "Icon image URL" }),
18157
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18158
- Input,
18159
- {
18160
- id: `${settingsGroup}-iconImageUrl`,
18161
- value: iconImageUrl,
18162
- onChange: (e) => setIconImageUrl(e.target.value),
18163
- placeholder: "https://\u2026 or /images/chat-icon.png",
18164
- className: "h-8 text-sm"
18165
- }
18166
- ),
18167
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "PNG or image URL. Leave empty to use emoji below." })
18168
- ] }),
18169
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18170
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-icon`, className: "text-sm", children: "Icon fallback (emoji)" }),
18171
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18172
- Input,
18769
+ ] }),
18770
+ /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
18771
+ Button,
18173
18772
  {
18174
- id: `${settingsGroup}-icon`,
18175
- value: icon,
18176
- onChange: (e) => setIcon(e.target.value),
18177
- placeholder: "e.g. \u{1F4AC}",
18178
- className: "h-8 text-sm w-20"
18773
+ type: "button",
18774
+ size: "sm",
18775
+ onClick: () => void handleSaveLlmNotifyTab(),
18776
+ disabled: notifyEmailSaving || !emailPluginAvailable,
18777
+ className: "gap-1",
18778
+ children: [
18779
+ notifyEmailSaving ? /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Save, { className: "h-3.5 w-3.5" }),
18780
+ "Save"
18781
+ ]
18179
18782
  }
18180
18783
  )
18181
- ] }),
18182
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex gap-4", children: [
18183
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18184
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-iconBg`, className: "text-sm", children: "Icon background" }),
18185
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2", children: [
18186
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18187
- "input",
18188
- {
18189
- type: "color",
18190
- id: `${settingsGroup}-iconBg`,
18191
- value: iconBackgroundColor,
18192
- onChange: (e) => setIconBackgroundColor(e.target.value),
18193
- className: "h-8 w-10 cursor-pointer rounded border border-gray-300 dark:border-gray-600"
18194
- }
18195
- ),
18196
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18197
- Input,
18198
- {
18199
- value: iconBackgroundColor,
18200
- onChange: (e) => setIconBackgroundColor(e.target.value),
18201
- className: "h-8 w-24 text-sm font-mono"
18202
- }
18203
- )
18204
- ] })
18205
- ] }),
18206
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "space-y-1", children: [
18207
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(Label3, { htmlFor: `${settingsGroup}-headerColor`, className: "text-sm", children: "Header color" }),
18208
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)("div", { className: "flex items-center gap-2", children: [
18209
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18210
- "input",
18211
- {
18212
- type: "color",
18213
- id: `${settingsGroup}-headerColor`,
18214
- value: headerColor,
18215
- onChange: (e) => setHeaderColor(e.target.value),
18216
- className: "h-8 w-10 cursor-pointer rounded border border-gray-300 dark:border-gray-600"
18217
- }
18218
- ),
18219
- /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
18220
- Input,
18221
- {
18222
- value: headerColor,
18223
- onChange: (e) => setHeaderColor(e.target.value),
18224
- className: "h-8 w-24 text-sm font-mono"
18225
- }
18226
- )
18227
- ] })
18228
- ] })
18229
18784
  ] })
18230
18785
  ] }),
18231
- /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Button, { size: "sm", onClick: handleSave, disabled: saving, className: "gap-1", children: [
18786
+ chatMode !== "llm" && /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(Button, { size: "sm", onClick: handleSave, disabled: saving, className: "gap-1", children: [
18232
18787
  /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(import_lucide_react39.Save, { className: "h-3.5 w-3.5" }),
18233
18788
  "Save"
18234
18789
  ] })
@@ -19195,12 +19750,10 @@ function ProductEditPage({ productId }) {
19195
19750
  setSaving(true);
19196
19751
  try {
19197
19752
  const activeCurrency = String(currency || "INR");
19198
- console.log("DEBUG save:", { activeCurrency, price, compareAtPrice });
19199
19753
  let saveRate = 1;
19200
19754
  if (activeCurrency !== "INR") {
19201
19755
  const displayRate = await fetchDisplayRate(activeCurrency);
19202
19756
  saveRate = displayRate !== 0 && displayRate !== 1 ? 1 / displayRate : 1;
19203
- console.log("DEBUG rate:", { displayRate, saveRate, priceInBase: Math.round(Number(price) * saveRate * 100) / 100 });
19204
19757
  }
19205
19758
  const priceInBase = Math.round(Number(price) * saveRate * 100) / 100;
19206
19759
  const compareAtPriceInBase = compareAtPrice ? Math.round(Number(compareAtPrice) * saveRate * 100) / 100 : null;
@@ -21359,7 +21912,7 @@ function RuleTreeNode({ node, depth = 0 }) {
21359
21912
  node.type && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "rounded bg-white border border-gray-200 px-2 py-0.5 text-xs text-gray-700", children: RULE_TYPE_LABEL[node.type] ?? node.type }),
21360
21913
  node.subType && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "text-gray-400 text-xs", children: node.subType }),
21361
21914
  node.comparisonOperator && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "font-mono text-xs font-semibold text-gray-700", children: node.comparisonOperator }),
21362
- node.value && Object.keys(node.value).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "font-mono text-xs bg-white border border-gray-200 rounded px-2 py-0.5 text-gray-800 break-all", children: Object.keys(node.value).length === 1 && "v" in node.value ? String(node.value.v) : JSON.stringify(node.value) })
21915
+ node.value && Object.keys(node.value).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "font-mono text-xs bg-white border border-gray-200 rounded px-2 py-0.5 text-gray-800 break-all", children: node.type === "quantity" && node.subType === "productId" && "v" in node.value ? `product #${node.value.productId} >= ${node.value.v}` : Object.keys(node.value).length === 1 && "v" in node.value ? String(node.value.v) : JSON.stringify(node.value) })
21363
21916
  ] }) }),
21364
21917
  isGroup && open && (node.children ?? []).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "mt-2 pl-4 space-y-2", children: (node.children ?? []).map((child) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(RuleTreeNode, { node: child, depth: depth + 1 }, child.id)) })
21365
21918
  ] });
@@ -21619,10 +22172,10 @@ function conditionToRule(c) {
21619
22172
  return { ...base, type: "minAmount", comparisonOperator: "gte", value: { v: c.amount ?? "0" } };
21620
22173
  case "minQuantity":
21621
22174
  return { ...base, type: "quantity", comparisonOperator: "gte", value: { v: c.quantity ?? "1" } };
21622
- case "Product":
21623
- return { ...base, type: "product", subType: "productId", comparisonOperator: "eq", value: { v: String(c.productId ?? "") } };
22175
+ case "productMinQuantity":
22176
+ return { ...base, type: "quantity", subType: "productId", comparisonOperator: "gte", value: { v: c.quantity ?? "1", productId: String(c.productId ?? "") } };
21624
22177
  case "nthOrder":
21625
- return { ...base, type: "order", subType: "nthOrder", comparisonOperator: "gte", value: { v: c.nthOrder ?? "1" } };
22178
+ return { ...base, type: "order", subType: "nthOrder", comparisonOperator: "eq", value: { v: c.nthOrder ?? "1" } };
21626
22179
  case "userType":
21627
22180
  return { ...base, type: "user", subType: "userType", comparisonOperator: "eq", value: { v: c.userType ?? "" } };
21628
22181
  case "paymentMethod":
@@ -21688,9 +22241,16 @@ function ruleTreeToFriendly(rules) {
21688
22241
  case "minAmount":
21689
22242
  return { id: rule._clientId ?? uid(), kind: "minAmount", amount: v };
21690
22243
  case "quantity":
22244
+ if (rule.subType === "productId") {
22245
+ return {
22246
+ id: rule._clientId ?? uid(),
22247
+ kind: "productMinQuantity",
22248
+ quantity: v,
22249
+ productId: Number(rule.value?.productId) || null,
22250
+ productName: void 0
22251
+ };
22252
+ }
21691
22253
  return { id: rule._clientId ?? uid(), kind: "minQuantity", quantity: v };
21692
- case "product":
21693
- return { id: rule._clientId ?? uid(), kind: "Product", productId: Number(v) || null, productName: void 0 };
21694
22254
  case "order":
21695
22255
  return { id: rule._clientId ?? uid(), kind: "nthOrder", nthOrder: v };
21696
22256
  case "user":
@@ -22014,7 +22574,7 @@ function ConditionCard({
22014
22574
  const iconMap = {
22015
22575
  minAmount: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.DollarSign, { className: "h-3.5 w-3.5 text-blue-500" }),
22016
22576
  minQuantity: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Hash, { className: "h-3.5 w-3.5 text-purple-500" }),
22017
- Product: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Package, { className: "h-3.5 w-3.5 text-green-500" }),
22577
+ productMinQuantity: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.Hash, { className: "h-3.5 w-3.5 text-green-600" }),
22018
22578
  nthOrder: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.ShoppingCart, { className: "h-3.5 w-3.5 text-orange-500" }),
22019
22579
  userType: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.User, { className: "h-3.5 w-3.5 text-pink-500" }),
22020
22580
  paymentMethod: /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(import_lucide_react47.DollarSign, { className: "h-3.5 w-3.5 text-indigo-500" }),
@@ -22036,7 +22596,7 @@ function ConditionCard({
22036
22596
  /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)(SelectContent, { children: [
22037
22597
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "minAmount", children: "Minimum order amount" }),
22038
22598
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "minQuantity", children: "Minimum quantity" }),
22039
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "Product", children: "Product" }),
22599
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "productMinQuantity", children: "Product" }),
22040
22600
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "nthOrder", children: "Customer's Nth order" }),
22041
22601
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "userType", children: "User type" }),
22042
22602
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(SelectItem, { value: "paymentMethod", children: "Payment method" }),
@@ -22056,9 +22616,33 @@ function ConditionCard({
22056
22616
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-500 shrink-0", children: "Qty \u2265" }),
22057
22617
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("input", { type: "number", min: 1, step: "1", value: condition.quantity ?? "", onChange: (e) => onChange({ ...condition, quantity: e.target.value }), className: inputCls3, placeholder: "2" })
22058
22618
  ] }),
22059
- condition.kind === "Product" && /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(ProductPicker, { value: condition.productId, label: condition.productName, onChange: (id, name) => onChange({ ...condition, productId: id, productName: name }) }),
22619
+ condition.kind === "productMinQuantity" && /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "space-y-2", children: [
22620
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
22621
+ ProductPicker,
22622
+ {
22623
+ value: condition.productId,
22624
+ label: condition.productName,
22625
+ onChange: (id, name) => onChange({ ...condition, productId: id, productName: name })
22626
+ }
22627
+ ),
22628
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-center gap-2", children: [
22629
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-500 shrink-0", children: "Minimum quantity of this product" }),
22630
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)(
22631
+ "input",
22632
+ {
22633
+ type: "number",
22634
+ min: 1,
22635
+ step: "1",
22636
+ value: condition.quantity ?? "",
22637
+ onChange: (e) => onChange({ ...condition, quantity: e.target.value }),
22638
+ className: inputCls3,
22639
+ placeholder: "2"
22640
+ }
22641
+ )
22642
+ ] })
22643
+ ] }),
22060
22644
  condition.kind === "nthOrder" && /* @__PURE__ */ (0, import_jsx_runtime79.jsxs)("div", { className: "flex items-center gap-2", children: [
22061
- /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-500 shrink-0", children: "Order number \u2265" }),
22645
+ /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-500 shrink-0", children: "Order number =" }),
22062
22646
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("input", { type: "number", min: 1, step: "1", value: condition.nthOrder ?? "", onChange: (e) => onChange({ ...condition, nthOrder: e.target.value }), className: inputCls3, placeholder: "2" }),
22063
22647
  /* @__PURE__ */ (0, import_jsx_runtime79.jsx)("span", { className: "text-xs text-gray-400 shrink-0", children: "(e.g. 2 = 2nd+ order)" })
22064
22648
  ] }),
@@ -22128,7 +22712,7 @@ function DiscountConditionsBuilder({
22128
22712
  const missingNameIds = [];
22129
22713
  for (const g of groups) {
22130
22714
  for (const c of g.conditions) {
22131
- if (c.kind === "Product" && c.productId && !c.productName)
22715
+ if (c.kind === "productMinQuantity" && c.productId && !c.productName)
22132
22716
  missingNameIds.push({ type: "product", id: c.id, entityId: c.productId, groupId: g.id });
22133
22717
  if (c.kind === "category" && c.categoryId && !c.categoryName)
22134
22718
  missingNameIds.push({ type: "category", id: c.id, entityId: c.categoryId, groupId: g.id });