@infuro/cms-core 1.0.19 → 1.0.21

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
@@ -401,7 +401,7 @@ var defaultValue = {
401
401
  var AdminConfigContext = (0, import_react4.createContext)(defaultValue);
402
402
 
403
403
  // src/lib/cms-version.ts
404
- var CMS_VERSION = true ? "1.0.19" : "0.0.0";
404
+ var CMS_VERSION = true ? "1.0.21" : "0.0.0";
405
405
 
406
406
  // src/components/Admin/Sidebar.tsx
407
407
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -1404,7 +1404,9 @@ function CreateEditForm({ isOpen, onClose, apiEndpoint, columns, existingData })
1404
1404
  if (existingData) {
1405
1405
  setFormData(existingData);
1406
1406
  } else {
1407
- setFormData(columns.reduce((acc, col) => ({ ...acc, [col.field]: col.defaultValue || "" }), {}));
1407
+ setFormData(
1408
+ columns.filter((col) => !col.hideInForm).reduce((acc, col) => ({ ...acc, [col.field]: col.defaultValue || "" }), {})
1409
+ );
1408
1410
  }
1409
1411
  }, [existingData, columns]);
1410
1412
  const handleChange = (e, field) => {
@@ -1418,7 +1420,7 @@ function CreateEditForm({ isOpen, onClose, apiEndpoint, columns, existingData })
1418
1420
  };
1419
1421
  const validateForm = () => {
1420
1422
  let newErrors = {};
1421
- columns.forEach((col) => {
1423
+ columns.filter((col) => !col.hideInForm).forEach((col) => {
1422
1424
  if (col.validation?.required && !formData[col.field]) {
1423
1425
  newErrors[col.field] = `${col.displayName} is required`;
1424
1426
  }
@@ -1458,7 +1460,7 @@ Note: ${result.note}`);
1458
1460
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Button, { type: "button", variant: "ghost", size: "icon", onClick: onClose, className: "h-8 w-8", "aria-label": "Close", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_lucide_react8.X, { className: "h-5 w-5" }) })
1459
1461
  ] }),
1460
1462
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("form", { onSubmit: handleSubmit, className: "flex flex-col flex-1 min-h-0", children: [
1461
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 min-h-0 overflow-y-auto p-6 space-y-4", children: columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
1463
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex-1 min-h-0 overflow-y-auto p-6 space-y-4", children: columns.filter((col) => !col.hideInForm).map((col) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { children: [
1462
1464
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Label3, { className: "block text-sm font-medium mb-1", children: col.displayName }),
1463
1465
  col.relationApi ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1464
1466
  RelationAutocomplete,
@@ -1470,10 +1472,35 @@ Note: ${result.note}`);
1470
1472
  valueField: col.relationValueField ?? "id",
1471
1473
  placeholder: `Select ${col.displayName}`
1472
1474
  }
1473
- ) : col.type === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Textarea, { value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Select, { onValueChange: (value) => setFormData({ ...formData, [col.field]: value }), children: [
1475
+ ) : col.type === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1476
+ Textarea,
1477
+ {
1478
+ rows: typeof col.textareaRows === "number" ? col.textareaRows : 4,
1479
+ className: "min-h-[80px] font-mono text-sm",
1480
+ value: formData[col.field] ?? "",
1481
+ onChange: (e) => handleChange(e, col.field),
1482
+ placeholder: col.placeholder
1483
+ }
1484
+ ) : col.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Select, { onValueChange: (value) => setFormData({ ...formData, [col.field]: value }), children: [
1474
1485
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectTrigger, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectValue, { placeholder: formData[col.field] || "Select an option" }) }),
1475
1486
  /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectContent, { children: col.options?.map((option) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SelectItem, { value: option.value, children: option.label }, option.value)) })
1476
- ] }) : col.type === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Switch, { checked: formData[col.field] || false, onCheckedChange: () => handleToggleChange(col.field) }) : col.type === "date" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Input, { type: "date", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "password" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Input, { type: "password", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "number" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Input, { type: "number", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "file" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FileUpload, { onUploadSuccess: (url) => handleFileUpload(col.field, url) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Input, { type: col.type || "text", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }),
1487
+ ] }) : col.type === "boolean" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Switch, { checked: formData[col.field] || false, onCheckedChange: () => handleToggleChange(col.field) }) : col.type === "date" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Input, { type: "date", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "password" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Input, { type: "password", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "number" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1488
+ Input,
1489
+ {
1490
+ type: "number",
1491
+ placeholder: col.placeholder,
1492
+ value: formData[col.field] ?? "",
1493
+ onChange: (e) => handleChange(e, col.field)
1494
+ }
1495
+ ) : col.type === "file" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(FileUpload, { onUploadSuccess: (url) => handleFileUpload(col.field, url) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1496
+ Input,
1497
+ {
1498
+ type: col.type || "text",
1499
+ placeholder: col.placeholder,
1500
+ value: formData[col.field] ?? "",
1501
+ onChange: (e) => handleChange(e, col.field)
1502
+ }
1503
+ ),
1477
1504
  errors[col.field] && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-red-500 text-sm mt-1", children: errors[col.field] })
1478
1505
  ] }, col.field)) }),
1479
1506
  /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex gap-2 p-4 border-t border-gray-200 shrink-0 bg-white", children: [
@@ -2076,6 +2103,10 @@ function AdminCRUD({
2076
2103
  const hasLoadedRef = (0, import_react13.useRef)(false);
2077
2104
  const isMobile = useIsMobile();
2078
2105
  const showGroupColumn = !!manageUserGroups && roleOptions.length > 0;
2106
+ const listColumns = (0, import_react13.useMemo)(
2107
+ () => Array.isArray(columns) ? columns.filter((c) => !c.hideInTable) : [],
2108
+ [columns]
2109
+ );
2079
2110
  (0, import_react13.useEffect)(() => {
2080
2111
  const timeoutId = setTimeout(() => {
2081
2112
  if (searchInput !== searchQuery) {
@@ -2526,7 +2557,7 @@ function AdminCRUD({
2526
2557
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "ml-2", children: "Refreshing list..." })
2527
2558
  ] }),
2528
2559
  isMobile ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex flex-col gap-2 min-w-0", children: data && data.length > 0 ? data.map((item, index) => {
2529
- const displayCols = columns?.slice(0, 4) ?? [];
2560
+ const displayCols = listColumns?.slice(0, 4) ?? [];
2530
2561
  const primary = displayCols[0];
2531
2562
  const primaryVal = primary ? getNestedValue(item, primary.field || primary.key) : null;
2532
2563
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
@@ -2600,7 +2631,7 @@ function AdminCRUD({
2600
2631
  );
2601
2632
  }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "rounded-md border border-gray-200 p-6 text-center text-gray-500", children: "No data found" }) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "overflow-x-auto min-w-0 rounded-md border border-gray-200", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Table, { children: [
2602
2633
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(TableRow, { children: [
2603
- columns && columns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2634
+ listColumns && listColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2604
2635
  TableHead,
2605
2636
  {
2606
2637
  className: "cursor-pointer",
@@ -2622,7 +2653,7 @@ function AdminCRUD({
2622
2653
  className: "cursor-pointer",
2623
2654
  onClick: () => handleRowClick(item),
2624
2655
  children: [
2625
- columns && columns.map((col, colIndex) => {
2656
+ listColumns && listColumns.map((col, colIndex) => {
2626
2657
  const fieldKey = col.field || col.key;
2627
2658
  const value = getNestedValue(item, fieldKey);
2628
2659
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TableCell, { children: formatCellValue(value, col) }, `${item.id}-${colIndex}-${fieldKey}`);
@@ -2706,7 +2737,7 @@ function AdminCRUD({
2706
2737
  )) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2707
2738
  TableCell,
2708
2739
  {
2709
- colSpan: columns ? columns.length + 1 + (showGroupColumn ? 1 : 0) : 1,
2740
+ colSpan: listColumns.length ? listColumns.length + 1 + (showGroupColumn ? 1 : 0) : 1,
2710
2741
  className: "text-center py-8",
2711
2742
  children: "No data found"
2712
2743
  }
@@ -6492,7 +6523,7 @@ function DashboardPage() {
6492
6523
 
6493
6524
  // src/admin/pages/AdminPageResolver.tsx
6494
6525
  var import_react48 = require("react");
6495
- var import_navigation21 = require("next/navigation");
6526
+ var import_navigation22 = require("next/navigation");
6496
6527
 
6497
6528
  // src/admin/pages/SubmissionDetailPage.tsx
6498
6529
  var import_react33 = require("react");
@@ -9221,6 +9252,7 @@ function PageBuilderPage({ pageId }) {
9221
9252
 
9222
9253
  // src/admin/pages/PluginsPage.tsx
9223
9254
  var import_react41 = require("react");
9255
+ var import_navigation18 = require("next/navigation");
9224
9256
  var import_lucide_react32 = require("lucide-react");
9225
9257
 
9226
9258
  // src/lib/email-recipients.ts
@@ -9270,6 +9302,10 @@ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
9270
9302
  // src/admin/pages/PluginsPage.tsx
9271
9303
  var import_sonner7 = require("sonner");
9272
9304
  var import_jsx_runtime56 = require("react/jsx-runtime");
9305
+ function slugifyAgentKey(name) {
9306
+ const s = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
9307
+ return s || "agent";
9308
+ }
9273
9309
  function normalizeChatMode(raw) {
9274
9310
  if (raw === "external" || raw === "llm") return raw;
9275
9311
  return "whatsapp";
@@ -9429,6 +9465,26 @@ function PluginSettingsPanel({
9429
9465
  const [chatMode, setChatMode] = (0, import_react41.useState)("whatsapp");
9430
9466
  const [whatsappPhone, setWhatsappPhone] = (0, import_react41.useState)("");
9431
9467
  const [externalChatSnippet, setExternalChatSnippet] = (0, import_react41.useState)("");
9468
+ const [attachedAgentSlug, setAttachedAgentSlug] = (0, import_react41.useState)("");
9469
+ const [llmAgents, setLlmAgents] = (0, import_react41.useState)([]);
9470
+ const [agentId, setAgentId] = (0, import_react41.useState)(null);
9471
+ const [agentName, setAgentName] = (0, import_react41.useState)("");
9472
+ const [agentSlug, setAgentSlug] = (0, import_react41.useState)("");
9473
+ const [agentSystem, setAgentSystem] = (0, import_react41.useState)("");
9474
+ const [agentModel, setAgentModel] = (0, import_react41.useState)("");
9475
+ const [agentTemp, setAgentTemp] = (0, import_react41.useState)("");
9476
+ const [agentMaxTokens, setAgentMaxTokens] = (0, import_react41.useState)("");
9477
+ const [agentValidationJson, setAgentValidationJson] = (0, import_react41.useState)("");
9478
+ const [agentLoading, setAgentLoading] = (0, import_react41.useState)(false);
9479
+ const [agentSaving, setAgentSaving] = (0, import_react41.useState)(false);
9480
+ const [agentProvisionError, setAgentProvisionError] = (0, import_react41.useState)(null);
9481
+ const [kbCatalog, setKbCatalog] = (0, import_react41.useState)([]);
9482
+ const [attachedAgentKnowledge, setAttachedAgentKnowledge] = (0, import_react41.useState)([]);
9483
+ const [attachedKbLoading, setAttachedKbLoading] = (0, import_react41.useState)(false);
9484
+ const [attachExistingDocId, setAttachExistingDocId] = (0, import_react41.useState)("__none__");
9485
+ const [uploadingAttachedKbFile, setUploadingAttachedKbFile] = (0, import_react41.useState)(false);
9486
+ const [uploadingAttachedKbLink, setUploadingAttachedKbLink] = (0, import_react41.useState)(false);
9487
+ const [attachedKbInputKey, setAttachedKbInputKey] = (0, import_react41.useState)(0);
9432
9488
  const [erpPipelineName, setErpPipelineName] = (0, import_react41.useState)("");
9433
9489
  const [erpPipelineStageName, setErpPipelineStageName] = (0, import_react41.useState)("");
9434
9490
  const [erpFormsCatalog, setErpFormsCatalog] = (0, import_react41.useState)([]);
@@ -9452,6 +9508,7 @@ function PluginSettingsPanel({
9452
9508
  setIconImageUrl(data.iconImageUrl ?? "");
9453
9509
  setIconBackgroundColor(data.iconBackgroundColor ?? "#6366f1");
9454
9510
  setHeaderColor(data.headerColor ?? "#6366f1");
9511
+ setAttachedAgentSlug(data.attachedAgentSlug ?? "");
9455
9512
  }
9456
9513
  if (isErp) {
9457
9514
  setErpPipelineName(data.pipelineName ?? data.pipelineId ?? "");
@@ -9506,6 +9563,203 @@ function PluginSettingsPanel({
9506
9563
  setErpFormsCatalog(rows);
9507
9564
  }).catch(() => setErpFormsCatalog([]));
9508
9565
  }, [isErp, loading]);
9566
+ const fetchLlmAgents = (0, import_react41.useCallback)(async () => {
9567
+ const res = await fetch("/api/llm_agents?limit=100&sortField=name&sortOrder=asc");
9568
+ if (!res.ok) {
9569
+ setLlmAgents([]);
9570
+ return;
9571
+ }
9572
+ const j = await res.json();
9573
+ const list = (j.data ?? []).map((r) => ({
9574
+ id: r.id,
9575
+ name: String(r.name ?? ""),
9576
+ slug: String(r.slug ?? ""),
9577
+ enabled: r.enabled !== false
9578
+ }));
9579
+ setLlmAgents(list);
9580
+ const pick = list.find((a) => a.enabled) ?? list[0];
9581
+ const fullRow = (j.data ?? []).find((r) => r.id === pick?.id);
9582
+ if (pick && fullRow) {
9583
+ setAgentId(pick.id);
9584
+ setAgentName(pick.name);
9585
+ setAgentSlug(pick.slug);
9586
+ setAgentSystem(String(fullRow.systemInstruction ?? ""));
9587
+ setAgentModel(String(fullRow.model ?? ""));
9588
+ setAgentTemp(fullRow.temperature != null ? String(fullRow.temperature) : "");
9589
+ setAgentMaxTokens(fullRow.maxTokens != null ? String(fullRow.maxTokens) : "");
9590
+ setAgentValidationJson(String(fullRow.validationRules ?? ""));
9591
+ setAttachedAgentSlug(pick.slug);
9592
+ }
9593
+ }, []);
9594
+ const fetchKbCatalog = (0, import_react41.useCallback)(async () => {
9595
+ try {
9596
+ const res = await fetch("/api/knowledge_base_documents?limit=300&sortField=name&sortOrder=asc");
9597
+ if (!res.ok) {
9598
+ setKbCatalog([]);
9599
+ return;
9600
+ }
9601
+ const j = await res.json();
9602
+ setKbCatalog(
9603
+ (j.data ?? []).map((r) => ({
9604
+ id: typeof r.id === "number" ? r.id : Number(r.id),
9605
+ name: String(r.name ?? "")
9606
+ })).filter((r) => Number.isInteger(r.id) && r.id > 0)
9607
+ );
9608
+ } catch {
9609
+ setKbCatalog([]);
9610
+ }
9611
+ }, []);
9612
+ const fetchAttachedKnowledge = (0, import_react41.useCallback)(async (slug) => {
9613
+ if (!slug.trim()) {
9614
+ setAttachedAgentKnowledge([]);
9615
+ return;
9616
+ }
9617
+ setAttachedKbLoading(true);
9618
+ try {
9619
+ const res = await fetch(`/api/llm_agents/${encodeURIComponent(slug.trim())}/knowledge`);
9620
+ if (!res.ok) {
9621
+ setAttachedAgentKnowledge([]);
9622
+ return;
9623
+ }
9624
+ const j = await res.json();
9625
+ setAttachedAgentKnowledge(
9626
+ (j.documents ?? []).map((d) => ({
9627
+ id: typeof d.id === "number" ? d.id : Number(d.id),
9628
+ name: String(d.name ?? "")
9629
+ }))
9630
+ );
9631
+ } catch {
9632
+ setAttachedAgentKnowledge([]);
9633
+ } finally {
9634
+ setAttachedKbLoading(false);
9635
+ }
9636
+ }, []);
9637
+ const bootstrapLlmAgentForPlugins = (0, import_react41.useCallback)(async () => {
9638
+ setAgentProvisionError(null);
9639
+ const listRes = await fetch("/api/llm_agents?limit=100&sortField=name&sortOrder=asc");
9640
+ if (!listRes.ok) {
9641
+ const errBody = await listRes.json().catch(() => ({}));
9642
+ 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".`;
9643
+ setAgentProvisionError(message);
9644
+ await fetchLlmAgents();
9645
+ return { ok: false, message };
9646
+ }
9647
+ const listJ = await listRes.json();
9648
+ if (listJ.data?.length) {
9649
+ await fetchLlmAgents();
9650
+ return { ok: true };
9651
+ }
9652
+ const defaultName = botName.trim() || "Assistant";
9653
+ const defaultSlug = slugifyAgentKey(defaultName);
9654
+ const createRes = await fetch("/api/llm_agents", {
9655
+ method: "POST",
9656
+ headers: { "Content-Type": "application/json" },
9657
+ body: JSON.stringify({ name: defaultName, slug: defaultSlug, systemInstruction: "", enabled: true })
9658
+ });
9659
+ if (!createRes.ok) {
9660
+ const errBody = await createRes.json().catch(() => ({}));
9661
+ const message = errBody.error ?? `Could not create default agent (HTTP ${createRes.status}). Check create permission for "llm_agents".`;
9662
+ setAgentProvisionError(message);
9663
+ await fetchLlmAgents();
9664
+ return { ok: false, message };
9665
+ }
9666
+ await fetchLlmAgents();
9667
+ return { ok: true };
9668
+ }, [botName, fetchLlmAgents]);
9669
+ const handleRetryBootstrapAgent = async () => {
9670
+ setAgentLoading(true);
9671
+ try {
9672
+ const r = await bootstrapLlmAgentForPlugins();
9673
+ if (r.ok) {
9674
+ import_sonner7.toast.success("Assistant agent ready \u2014 you can upload knowledge files below.");
9675
+ } else {
9676
+ import_sonner7.toast.error(r.message);
9677
+ }
9678
+ } finally {
9679
+ setAgentLoading(false);
9680
+ }
9681
+ };
9682
+ const handleCreateAssistantFromForm = async () => {
9683
+ const name = agentName.trim() || botName.trim();
9684
+ if (!name) {
9685
+ import_sonner7.toast.error("Enter an assistant name");
9686
+ return;
9687
+ }
9688
+ const tempRaw = agentTemp.trim();
9689
+ const maxRaw = agentMaxTokens.trim();
9690
+ const temperature = tempRaw === "" ? null : Number(tempRaw);
9691
+ const maxTokens = maxRaw === "" ? null : parseInt(maxRaw, 10);
9692
+ if (tempRaw !== "" && !Number.isFinite(temperature)) {
9693
+ import_sonner7.toast.error("Temperature must be a number");
9694
+ return;
9695
+ }
9696
+ if (maxRaw !== "" && (!Number.isFinite(maxTokens) || maxTokens < 1)) {
9697
+ import_sonner7.toast.error("Max tokens must be a positive integer");
9698
+ return;
9699
+ }
9700
+ const slug = slugifyAgentKey(name);
9701
+ setAgentSaving(true);
9702
+ try {
9703
+ const res = await fetch("/api/llm_agents", {
9704
+ method: "POST",
9705
+ headers: { "Content-Type": "application/json" },
9706
+ body: JSON.stringify({
9707
+ name,
9708
+ slug,
9709
+ systemInstruction: agentSystem.trim(),
9710
+ model: agentModel.trim() || null,
9711
+ temperature,
9712
+ maxTokens,
9713
+ validationRules: agentValidationJson.trim() || null,
9714
+ enabled: true
9715
+ })
9716
+ });
9717
+ if (!res.ok) {
9718
+ const err = await res.json().catch(() => ({}));
9719
+ import_sonner7.toast.error(err.error || "Failed to create assistant");
9720
+ return;
9721
+ }
9722
+ setAgentProvisionError(null);
9723
+ setAttachedAgentSlug(slug);
9724
+ await fetchLlmAgents();
9725
+ import_sonner7.toast.success("Assistant created \u2014 add knowledge files below.");
9726
+ } catch {
9727
+ import_sonner7.toast.error("Failed to create assistant");
9728
+ } finally {
9729
+ setAgentSaving(false);
9730
+ }
9731
+ };
9732
+ (0, import_react41.useEffect)(() => {
9733
+ if (!isLlm || loading || chatMode !== "llm") return;
9734
+ setAgentLoading(true);
9735
+ void (async () => {
9736
+ try {
9737
+ await bootstrapLlmAgentForPlugins();
9738
+ } finally {
9739
+ setAgentLoading(false);
9740
+ }
9741
+ })();
9742
+ }, [isLlm, loading, chatMode, bootstrapLlmAgentForPlugins]);
9743
+ (0, import_react41.useEffect)(() => {
9744
+ if (!isLlm || loading || chatMode !== "llm") return;
9745
+ void fetchKbCatalog();
9746
+ }, [isLlm, loading, chatMode, fetchKbCatalog]);
9747
+ (0, import_react41.useEffect)(() => {
9748
+ if (!isLlm || loading || chatMode !== "llm" || !attachedAgentSlug.trim()) {
9749
+ setAttachedAgentKnowledge([]);
9750
+ return;
9751
+ }
9752
+ void fetchAttachedKnowledge(attachedAgentSlug);
9753
+ }, [isLlm, loading, chatMode, attachedAgentSlug, fetchAttachedKnowledge]);
9754
+ (0, import_react41.useEffect)(() => {
9755
+ setAttachExistingDocId("__none__");
9756
+ }, [attachedAgentSlug]);
9757
+ (0, import_react41.useEffect)(() => {
9758
+ if (!isLlm || loading || chatMode !== "llm" || agentLoading || agentId) return;
9759
+ if (!agentName.trim() && botName.trim()) {
9760
+ setAgentName(botName.trim());
9761
+ }
9762
+ }, [isLlm, loading, chatMode, agentLoading, agentId, agentName, botName]);
9509
9763
  const buildPayload = () => {
9510
9764
  if (isErp) {
9511
9765
  const sortedIds = [...new Set(erpOpportunityFormIds.filter((n) => Number.isInteger(n) && n > 0))].sort(
@@ -9540,6 +9794,7 @@ function PluginSettingsPanel({
9540
9794
  payload.iconImageUrl = { value: iconImageUrl, type: "public" };
9541
9795
  payload.iconBackgroundColor = { value: iconBackgroundColor, type: "public" };
9542
9796
  payload.headerColor = { value: headerColor, type: "public" };
9797
+ payload.attachedAgentSlug = { value: attachedAgentSlug.trim(), type: "public" };
9543
9798
  }
9544
9799
  if (isEmail) {
9545
9800
  payload.salesTeamEmails = { value: serializeEmailRecipients(salesTeamEmails), type: "public" };
@@ -9555,6 +9810,56 @@ function PluginSettingsPanel({
9555
9810
  }
9556
9811
  return payload;
9557
9812
  };
9813
+ const handleSaveAgent = async () => {
9814
+ if (!agentId) {
9815
+ import_sonner7.toast.error("No agent to save");
9816
+ return;
9817
+ }
9818
+ const name = agentName.trim();
9819
+ if (!name) {
9820
+ import_sonner7.toast.error("Agent name is required");
9821
+ return;
9822
+ }
9823
+ const tempRaw = agentTemp.trim();
9824
+ const maxRaw = agentMaxTokens.trim();
9825
+ const temperature = tempRaw === "" ? null : Number(tempRaw);
9826
+ const maxTokens = maxRaw === "" ? null : parseInt(maxRaw, 10);
9827
+ if (tempRaw !== "" && !Number.isFinite(temperature)) {
9828
+ import_sonner7.toast.error("Temperature must be a number");
9829
+ return;
9830
+ }
9831
+ if (maxRaw !== "" && (!Number.isFinite(maxTokens) || maxTokens < 1)) {
9832
+ import_sonner7.toast.error("Max tokens must be a positive integer");
9833
+ return;
9834
+ }
9835
+ setAgentSaving(true);
9836
+ try {
9837
+ const res = await fetch(`/api/llm_agents/${agentId}`, {
9838
+ method: "PUT",
9839
+ headers: { "Content-Type": "application/json" },
9840
+ body: JSON.stringify({
9841
+ name,
9842
+ systemInstruction: agentSystem.trim(),
9843
+ model: agentModel.trim() || null,
9844
+ temperature,
9845
+ maxTokens,
9846
+ validationRules: agentValidationJson.trim() || null,
9847
+ enabled: true
9848
+ })
9849
+ });
9850
+ if (!res.ok) {
9851
+ const err = await res.json().catch(() => ({}));
9852
+ import_sonner7.toast.error(err.error || "Failed to update agent");
9853
+ return;
9854
+ }
9855
+ setAttachedAgentSlug(agentSlug);
9856
+ import_sonner7.toast.success("Agent saved");
9857
+ } catch {
9858
+ import_sonner7.toast.error("Failed to update agent");
9859
+ } finally {
9860
+ setAgentSaving(false);
9861
+ }
9862
+ };
9558
9863
  const handleSave = async () => {
9559
9864
  setSaving(true);
9560
9865
  try {
@@ -9588,6 +9893,96 @@ function PluginSettingsPanel({
9588
9893
  setSaving(false);
9589
9894
  }
9590
9895
  };
9896
+ const uploadKbFileToAttached = async (file) => {
9897
+ const slug = agentSlug.trim() || attachedAgentSlug.trim();
9898
+ if (!slug) {
9899
+ import_sonner7.toast.error("No agent configured");
9900
+ return;
9901
+ }
9902
+ setUploadingAttachedKbFile(true);
9903
+ try {
9904
+ const fd = new FormData();
9905
+ const stem = file.name.replace(/\.[^/.]+$/, "").trim() || "Upload";
9906
+ fd.append("name", stem);
9907
+ fd.append("file", file);
9908
+ const r = await fetch(`/api/llm_agents/${encodeURIComponent(slug)}/knowledge`, { method: "POST", body: fd });
9909
+ if (!r.ok) {
9910
+ const err = await r.json().catch(() => ({}));
9911
+ import_sonner7.toast.error(err.error || "Upload failed");
9912
+ return;
9913
+ }
9914
+ const ingest = await r.json().catch(() => ({}));
9915
+ if (ingest.warning) {
9916
+ import_sonner7.toast.warning(ingest.detail ? `${ingest.warning} (${ingest.detail})` : ingest.warning);
9917
+ } else if (ingest.embeddingAttempted && (ingest.embeddingsWritten ?? 0) === 0 && (ingest.chunkCount ?? 0) > 0) {
9918
+ import_sonner7.toast.warning(
9919
+ "Document saved but no embeddings were written. Set LLM_GATEWAY_URL + LLM_API_KEY, and ensure EMBEDDING_PROVIDER / EMBEDDING_MODEL match knowledge_base_chunks.embedding dimensions (see server logs)."
9920
+ );
9921
+ } else {
9922
+ import_sonner7.toast.success("Knowledge added and linked");
9923
+ }
9924
+ setAttachedKbInputKey((k) => k + 1);
9925
+ await fetchAttachedKnowledge(slug);
9926
+ await fetchKbCatalog();
9927
+ } finally {
9928
+ setUploadingAttachedKbFile(false);
9929
+ }
9930
+ };
9931
+ const onAttachedKbFileChange = (e) => {
9932
+ const file = e.target.files?.[0];
9933
+ if (!file) return;
9934
+ const slug = agentSlug.trim() || attachedAgentSlug.trim();
9935
+ if (!slug) {
9936
+ import_sonner7.toast.error("No agent configured");
9937
+ e.target.value = "";
9938
+ return;
9939
+ }
9940
+ void uploadKbFileToAttached(file);
9941
+ };
9942
+ const handleAttachExistingToAttached = async () => {
9943
+ const slug = agentSlug.trim() || attachedAgentSlug.trim();
9944
+ if (!slug || attachExistingDocId === "__none__") return;
9945
+ const docId = parseInt(attachExistingDocId, 10);
9946
+ if (!Number.isFinite(docId)) return;
9947
+ setUploadingAttachedKbLink(true);
9948
+ try {
9949
+ const r = await fetch(`/api/llm_agents/${encodeURIComponent(slug)}/knowledge`, {
9950
+ method: "POST",
9951
+ headers: { "Content-Type": "application/json" },
9952
+ body: JSON.stringify({ documentId: docId })
9953
+ });
9954
+ if (!r.ok) {
9955
+ const err = await r.json().catch(() => ({}));
9956
+ import_sonner7.toast.error(err.error || "Attach failed");
9957
+ return;
9958
+ }
9959
+ const ingest = await r.json().catch(() => ({}));
9960
+ if (ingest.warning) {
9961
+ import_sonner7.toast.warning(ingest.detail ? `${ingest.warning} (${ingest.detail})` : ingest.warning);
9962
+ } else if (ingest.embeddingAttempted && (ingest.embeddingsWritten ?? 0) === 0 && ((ingest.chunksQueuedForEmbedding ?? 0) > 0 || (ingest.chunkCount ?? 0) > 0)) {
9963
+ import_sonner7.toast.warning(
9964
+ "Document attached but no embeddings were written. Configure the LLM/embed gateway, then attach again to fill NULL embeddings."
9965
+ );
9966
+ } else {
9967
+ import_sonner7.toast.success("Document attached");
9968
+ }
9969
+ setAttachExistingDocId("__none__");
9970
+ await fetchAttachedKnowledge(slug);
9971
+ } finally {
9972
+ setUploadingAttachedKbLink(false);
9973
+ }
9974
+ };
9975
+ const handleUnlinkKbDoc = async (docId) => {
9976
+ const slug = agentSlug.trim() || attachedAgentSlug.trim();
9977
+ if (!slug) return;
9978
+ const r = await fetch(`/api/llm_agents/${encodeURIComponent(slug)}/knowledge/${docId}`, { method: "DELETE" });
9979
+ if (!r.ok) {
9980
+ import_sonner7.toast.error("Could not remove link");
9981
+ return;
9982
+ }
9983
+ import_sonner7.toast.success("Removed from agent");
9984
+ await fetchAttachedKnowledge(slug);
9985
+ };
9591
9986
  if (loading) return /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Loading..." });
9592
9987
  if (isErp) {
9593
9988
  return /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-4", children: [
@@ -10029,8 +10424,240 @@ function PluginSettingsPanel({
10029
10424
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Only paste code from sources you trust." })
10030
10425
  ] }),
10031
10426
  chatMode === "llm" && /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_jsx_runtime56.Fragment, { children: [
10427
+ /* @__PURE__ */ (0, import_jsx_runtime56.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: [
10428
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10429
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white", children: [
10430
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.Bot, { className: "h-4 w-4" }),
10431
+ "Chat assistant (single agent)"
10432
+ ] }),
10433
+ /* @__PURE__ */ (0, import_jsx_runtime56.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." })
10434
+ ] }),
10435
+ agentLoading ? /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
10436
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.Loader2, { className: "h-4 w-4 animate-spin" }),
10437
+ "Loading assistant\u2026"
10438
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_jsx_runtime56.Fragment, { children: [
10439
+ agentProvisionError ? /* @__PURE__ */ (0, import_jsx_runtime56.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,
10440
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10441
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: "agent-name", className: "text-sm", children: "Assistant name" }),
10442
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10443
+ Input,
10444
+ {
10445
+ id: "agent-name",
10446
+ value: agentName,
10447
+ onChange: (e) => setAgentName(e.target.value),
10448
+ placeholder: "e.g. JM Buddy",
10449
+ className: "h-8 text-sm"
10450
+ }
10451
+ )
10452
+ ] }),
10453
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10454
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: "agent-slug", className: "text-sm", children: "Slug" }),
10455
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10456
+ Input,
10457
+ {
10458
+ id: "agent-slug",
10459
+ value: agentId ? agentSlug : slugifyAgentKey((agentName || botName).trim() || "assistant"),
10460
+ disabled: true,
10461
+ className: "h-8 text-sm font-mono bg-gray-100 dark:bg-gray-700"
10462
+ }
10463
+ ),
10464
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { className: "text-[11px] text-gray-500", children: "Derived from the name. Used in API routes." })
10465
+ ] }),
10466
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10467
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: "agent-system", className: "text-sm", children: "System instruction" }),
10468
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10469
+ Textarea,
10470
+ {
10471
+ id: "agent-system",
10472
+ value: agentSystem,
10473
+ onChange: (e) => setAgentSystem(e.target.value),
10474
+ rows: 5,
10475
+ placeholder: "How the model should behave\u2026",
10476
+ className: "text-sm"
10477
+ }
10478
+ )
10479
+ ] }),
10480
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
10481
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10482
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: "agent-model", className: "text-sm", children: "Model (optional)" }),
10483
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10484
+ Input,
10485
+ {
10486
+ id: "agent-model",
10487
+ value: agentModel,
10488
+ onChange: (e) => setAgentModel(e.target.value),
10489
+ placeholder: "Gateway model id",
10490
+ className: "h-8 text-sm font-mono"
10491
+ }
10492
+ )
10493
+ ] }),
10494
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10495
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: "agent-temp", className: "text-sm", children: "Temperature" }),
10496
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10497
+ Input,
10498
+ {
10499
+ id: "agent-temp",
10500
+ value: agentTemp,
10501
+ onChange: (e) => setAgentTemp(e.target.value),
10502
+ placeholder: "e.g. 0.7",
10503
+ className: "h-8 text-sm"
10504
+ }
10505
+ )
10506
+ ] })
10507
+ ] }),
10508
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10509
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: "agent-max", className: "text-sm", children: "Max tokens" }),
10510
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10511
+ Input,
10512
+ {
10513
+ id: "agent-max",
10514
+ value: agentMaxTokens,
10515
+ onChange: (e) => setAgentMaxTokens(e.target.value.replace(/\D/g, "")),
10516
+ placeholder: "e.g. 1024",
10517
+ className: "h-8 text-sm"
10518
+ }
10519
+ )
10520
+ ] }),
10521
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10522
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: "agent-validation", className: "text-sm", children: "Validation & output guardrails" }),
10523
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10524
+ Textarea,
10525
+ {
10526
+ id: "agent-validation",
10527
+ value: agentValidationJson,
10528
+ onChange: (e) => setAgentValidationJson(e.target.value),
10529
+ rows: 4,
10530
+ placeholder: 'Plain text or JSON: {"guardrails":"Never promise refunds.","maxUserChars":2000}',
10531
+ className: "text-xs font-mono"
10532
+ }
10533
+ )
10534
+ ] }),
10535
+ !agentId ? /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
10536
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10537
+ Button,
10538
+ {
10539
+ type: "button",
10540
+ size: "sm",
10541
+ className: "gap-1",
10542
+ disabled: agentSaving,
10543
+ onClick: () => void handleCreateAssistantFromForm(),
10544
+ children: agentSaving ? /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
10545
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
10546
+ "Creating\u2026"
10547
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_jsx_runtime56.Fragment, { children: [
10548
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.Bot, { className: "h-3.5 w-3.5" }),
10549
+ "Create assistant"
10550
+ ] })
10551
+ }
10552
+ ),
10553
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10554
+ Button,
10555
+ {
10556
+ type: "button",
10557
+ size: "sm",
10558
+ variant: "secondary",
10559
+ className: "gap-1",
10560
+ disabled: agentSaving || agentLoading,
10561
+ onClick: () => void handleRetryBootstrapAgent(),
10562
+ children: "Retry auto-setup"
10563
+ }
10564
+ )
10565
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10566
+ Button,
10567
+ {
10568
+ type: "button",
10569
+ size: "sm",
10570
+ className: "gap-1",
10571
+ disabled: agentSaving,
10572
+ onClick: () => void handleSaveAgent(),
10573
+ children: agentSaving ? /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
10574
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
10575
+ "Saving\u2026"
10576
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(import_jsx_runtime56.Fragment, { children: [
10577
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.Save, { className: "h-3.5 w-3.5" }),
10578
+ "Save assistant"
10579
+ ] })
10580
+ }
10581
+ )
10582
+ ] }),
10583
+ agentId && agentSlug.trim() ? /* @__PURE__ */ (0, import_jsx_runtime56.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: [
10584
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800 dark:text-gray-200", children: [
10585
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.FileUp, { className: "h-3.5 w-3.5 shrink-0" }),
10586
+ "Knowledge for this agent"
10587
+ ] }),
10588
+ /* @__PURE__ */ (0, import_jsx_runtime56.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." }),
10589
+ attachedKbLoading ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Loading linked documents\u2026" }) : attachedAgentKnowledge.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "No documents linked yet." }) : /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("ul", { className: "max-h-32 space-y-1.5 overflow-y-auto", children: attachedAgentKnowledge.map((d) => /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("li", { className: "flex items-center justify-between gap-2 text-sm", children: [
10590
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)("span", { className: "min-w-0 truncate", title: d.name, children: d.name }),
10591
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10592
+ Button,
10593
+ {
10594
+ type: "button",
10595
+ variant: "ghost",
10596
+ size: "sm",
10597
+ className: "h-7 shrink-0 text-xs text-red-600 hover:text-red-700 dark:text-red-400",
10598
+ onClick: () => void handleUnlinkKbDoc(d.id),
10599
+ children: "Remove"
10600
+ }
10601
+ )
10602
+ ] }, d.id)) }),
10603
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "min-w-[180px] space-y-1", children: [
10604
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { className: "text-xs", children: "Upload file" }),
10605
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "relative", children: [
10606
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10607
+ Input,
10608
+ {
10609
+ type: "file",
10610
+ accept: ".txt,.md,.json,.pdf,text/plain,text/markdown,application/json,application/pdf",
10611
+ disabled: uploadingAttachedKbFile || uploadingAttachedKbLink,
10612
+ className: "h-8 cursor-pointer text-xs disabled:opacity-60",
10613
+ onChange: onAttachedKbFileChange
10614
+ },
10615
+ attachedKbInputKey
10616
+ ),
10617
+ uploadingAttachedKbFile ? /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(
10618
+ "div",
10619
+ {
10620
+ 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",
10621
+ "aria-live": "polite",
10622
+ children: [
10623
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.Loader2, { className: "h-4 w-4 shrink-0 animate-spin" }),
10624
+ "Saving & linking\u2026"
10625
+ ]
10626
+ }
10627
+ ) : null
10628
+ ] }),
10629
+ /* @__PURE__ */ (0, import_jsx_runtime56.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)." })
10630
+ ] }),
10631
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "flex flex-wrap items-end gap-2", children: [
10632
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "min-w-[200px] flex-1 space-y-1", children: [
10633
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { className: "text-xs", children: "Attach existing document" }),
10634
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(Select, { value: attachExistingDocId, onValueChange: setAttachExistingDocId, children: [
10635
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(SelectValue, { placeholder: "Choose a document" }) }),
10636
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)(SelectContent, { children: [
10637
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(SelectItem, { value: "__none__", children: "\u2014 Select \u2014" }),
10638
+ kbCatalog.filter((d) => !attachedAgentKnowledge.some((a) => a.id === d.id)).map((d) => /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(SelectItem, { value: String(d.id), children: d.name }, d.id))
10639
+ ] })
10640
+ ] })
10641
+ ] }),
10642
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10643
+ Button,
10644
+ {
10645
+ type: "button",
10646
+ size: "sm",
10647
+ className: "h-8",
10648
+ disabled: uploadingAttachedKbFile || uploadingAttachedKbLink || attachExistingDocId === "__none__",
10649
+ onClick: () => void handleAttachExistingToAttached(),
10650
+ children: uploadingAttachedKbLink ? /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("span", { className: "inline-flex items-center gap-1.5", children: [
10651
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(import_lucide_react32.Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
10652
+ "Linking\u2026"
10653
+ ] }) : "Attach"
10654
+ }
10655
+ )
10656
+ ] })
10657
+ ] }) : null
10658
+ ] }),
10032
10659
  /* @__PURE__ */ (0, import_jsx_runtime56.jsxs)("div", { className: "space-y-1", children: [
10033
- /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: `${settingsGroup}-botName`, className: "text-sm", children: "Name" }),
10660
+ /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(Label3, { htmlFor: `${settingsGroup}-botName`, className: "text-sm", children: "Widget title" }),
10034
10661
  /* @__PURE__ */ (0, import_jsx_runtime56.jsx)(
10035
10662
  Input,
10036
10663
  {
@@ -10200,8 +10827,14 @@ function PluginListItem({
10200
10827
  }
10201
10828
  function PluginsPage() {
10202
10829
  const { pluginDescriptors = [] } = (0, import_react41.useContext)(AdminConfigContext);
10830
+ const searchParams = (0, import_navigation18.useSearchParams)();
10203
10831
  const [selectedName, setSelectedName] = (0, import_react41.useState)(null);
10204
10832
  const [enabledMap, setEnabledMap] = (0, import_react41.useState)({});
10833
+ (0, import_react41.useEffect)(() => {
10834
+ if (searchParams.get("plugin") !== "llm") return;
10835
+ const llmDesc = pluginDescriptors.find((p) => p.settingsGroup === "llm");
10836
+ if (llmDesc) setSelectedName(llmDesc.name);
10837
+ }, [searchParams, pluginDescriptors]);
10205
10838
  (0, import_react41.useEffect)(() => {
10206
10839
  pluginDescriptors.forEach((p) => {
10207
10840
  if (!p.settingsGroup) return;
@@ -10264,7 +10897,7 @@ function PluginsPage() {
10264
10897
 
10265
10898
  // src/admin/pages/BrandEditPage.tsx
10266
10899
  var import_react42 = require("react");
10267
- var import_navigation18 = require("next/navigation");
10900
+ var import_navigation19 = require("next/navigation");
10268
10901
  var import_lucide_react33 = require("lucide-react");
10269
10902
 
10270
10903
  // src/components/Admin/SeoSection.tsx
@@ -10359,7 +10992,7 @@ async function fetchSeo(seoId) {
10359
10992
  var import_jsx_runtime58 = require("react/jsx-runtime");
10360
10993
  var isCreate = (id) => id === "create";
10361
10994
  function BrandEditPage({ brandId }) {
10362
- const router = (0, import_navigation18.useRouter)();
10995
+ const router = (0, import_navigation19.useRouter)();
10363
10996
  const create = isCreate(brandId);
10364
10997
  const [loading, setLoading] = (0, import_react42.useState)(!create);
10365
10998
  const [saving, setSaving] = (0, import_react42.useState)(false);
@@ -10562,7 +11195,7 @@ function BrandEditPage({ brandId }) {
10562
11195
 
10563
11196
  // src/admin/pages/ProductEditPage.tsx
10564
11197
  var import_react44 = require("react");
10565
- var import_navigation19 = require("next/navigation");
11198
+ var import_navigation20 = require("next/navigation");
10566
11199
  var import_lucide_react34 = require("lucide-react");
10567
11200
 
10568
11201
  // src/components/Admin/AttributeFacetNameInput.tsx
@@ -10741,7 +11374,7 @@ function pickOtherMetadata(m) {
10741
11374
  return rest;
10742
11375
  }
10743
11376
  function ProductEditPage({ productId }) {
10744
- const router = (0, import_navigation19.useRouter)();
11377
+ const router = (0, import_navigation20.useRouter)();
10745
11378
  const create = isCreate2(productId);
10746
11379
  const [loading, setLoading] = (0, import_react44.useState)(!create);
10747
11380
  const [saving, setSaving] = (0, import_react44.useState)(false);
@@ -11429,7 +12062,7 @@ function ProductEditPage({ productId }) {
11429
12062
 
11430
12063
  // src/admin/pages/CollectionEditPage.tsx
11431
12064
  var import_react45 = require("react");
11432
- var import_navigation20 = require("next/navigation");
12065
+ var import_navigation21 = require("next/navigation");
11433
12066
  var import_lucide_react35 = require("lucide-react");
11434
12067
  var import_jsx_runtime61 = require("react/jsx-runtime");
11435
12068
  var isCreate3 = (id) => id === "create";
@@ -11439,7 +12072,7 @@ var sectionCls2 = "min-w-0 overflow-hidden border border-gray-200 rounded-lg p-4
11439
12072
  var labelCls2 = "block text-xs font-medium text-gray-600 mb-1";
11440
12073
  var inputCls2 = "w-full rounded-md border border-gray-300 px-2 py-1.5 text-sm";
11441
12074
  function CollectionEditPage({ collectionId }) {
11442
- const router = (0, import_navigation20.useRouter)();
12075
+ const router = (0, import_navigation21.useRouter)();
11443
12076
  const create = isCreate3(collectionId);
11444
12077
  const [loading, setLoading] = (0, import_react45.useState)(!create);
11445
12078
  const [saving, setSaving] = (0, import_react45.useState)(false);
@@ -12384,13 +13017,16 @@ function BlogEditorWrapper({ blogId }) {
12384
13017
  return /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(BlogEditor, { existingBlog: blog });
12385
13018
  }
12386
13019
  function AdminPageResolver({ slug }) {
12387
- const router = (0, import_navigation21.useRouter)();
13020
+ const router = (0, import_navigation22.useRouter)();
12388
13021
  const { customCrudConfigs, storeEnabled } = (0, import_react48.useContext)(AdminConfigContext);
12389
13022
  const key = slug?.[0] || "dashboard";
12390
13023
  (0, import_react48.useEffect)(() => {
12391
13024
  if (key === "layout-settings") {
12392
13025
  router.replace("/admin/settings?tab=navbar");
12393
13026
  }
13027
+ if (key === "llm_agents") {
13028
+ router.replace("/admin/plugins?plugin=llm");
13029
+ }
12394
13030
  }, [key, router]);
12395
13031
  if (key === "layout-settings") {
12396
13032
  return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "flex justify-center py-8", children: [
@@ -12398,6 +13034,12 @@ function AdminPageResolver({ slug }) {
12398
13034
  /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "ml-2", children: "Redirecting..." })
12399
13035
  ] });
12400
13036
  }
13037
+ if (key === "llm_agents") {
13038
+ return /* @__PURE__ */ (0, import_jsx_runtime63.jsxs)("div", { className: "flex justify-center py-8", children: [
13039
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("div", { className: "animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-600" }),
13040
+ /* @__PURE__ */ (0, import_jsx_runtime63.jsx)("span", { className: "ml-2", children: "Opening Plugins\u2026" })
13041
+ ] });
13042
+ }
12401
13043
  const Page = PAGE_MAP[key];
12402
13044
  if (Page) {
12403
13045
  return /* @__PURE__ */ (0, import_jsx_runtime63.jsx)(Page, {});