@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.js CHANGED
@@ -351,7 +351,7 @@ var defaultValue = {
351
351
  var AdminConfigContext = createContext(defaultValue);
352
352
 
353
353
  // src/lib/cms-version.ts
354
- var CMS_VERSION = true ? "1.0.19" : "0.0.0";
354
+ var CMS_VERSION = true ? "1.0.21" : "0.0.0";
355
355
 
356
356
  // src/components/Admin/Sidebar.tsx
357
357
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -880,7 +880,7 @@ function AdminShell({ children }) {
880
880
  }
881
881
 
882
882
  // src/components/Admin/CRUD.tsx
883
- import { useEffect as useEffect6, useRef as useRef3, useState as useState8 } from "react";
883
+ import { useEffect as useEffect6, useMemo as useMemo2, useRef as useRef3, useState as useState8 } from "react";
884
884
 
885
885
  // src/components/Admin/CreateEditForm.tsx
886
886
  import { useState as useState6, useEffect as useEffect4 } from "react";
@@ -1363,7 +1363,9 @@ function CreateEditForm({ isOpen, onClose, apiEndpoint, columns, existingData })
1363
1363
  if (existingData) {
1364
1364
  setFormData(existingData);
1365
1365
  } else {
1366
- setFormData(columns.reduce((acc, col) => ({ ...acc, [col.field]: col.defaultValue || "" }), {}));
1366
+ setFormData(
1367
+ columns.filter((col) => !col.hideInForm).reduce((acc, col) => ({ ...acc, [col.field]: col.defaultValue || "" }), {})
1368
+ );
1367
1369
  }
1368
1370
  }, [existingData, columns]);
1369
1371
  const handleChange = (e, field) => {
@@ -1377,7 +1379,7 @@ function CreateEditForm({ isOpen, onClose, apiEndpoint, columns, existingData })
1377
1379
  };
1378
1380
  const validateForm = () => {
1379
1381
  let newErrors = {};
1380
- columns.forEach((col) => {
1382
+ columns.filter((col) => !col.hideInForm).forEach((col) => {
1381
1383
  if (col.validation?.required && !formData[col.field]) {
1382
1384
  newErrors[col.field] = `${col.displayName} is required`;
1383
1385
  }
@@ -1417,7 +1419,7 @@ Note: ${result.note}`);
1417
1419
  /* @__PURE__ */ jsx17(Button, { type: "button", variant: "ghost", size: "icon", onClick: onClose, className: "h-8 w-8", "aria-label": "Close", children: /* @__PURE__ */ jsx17(X2, { className: "h-5 w-5" }) })
1418
1420
  ] }),
1419
1421
  /* @__PURE__ */ jsxs11("form", { onSubmit: handleSubmit, className: "flex flex-col flex-1 min-h-0", children: [
1420
- /* @__PURE__ */ jsx17("div", { className: "flex-1 min-h-0 overflow-y-auto p-6 space-y-4", children: columns.map((col) => /* @__PURE__ */ jsxs11("div", { children: [
1422
+ /* @__PURE__ */ jsx17("div", { className: "flex-1 min-h-0 overflow-y-auto p-6 space-y-4", children: columns.filter((col) => !col.hideInForm).map((col) => /* @__PURE__ */ jsxs11("div", { children: [
1421
1423
  /* @__PURE__ */ jsx17(Label3, { className: "block text-sm font-medium mb-1", children: col.displayName }),
1422
1424
  col.relationApi ? /* @__PURE__ */ jsx17(
1423
1425
  RelationAutocomplete,
@@ -1429,10 +1431,35 @@ Note: ${result.note}`);
1429
1431
  valueField: col.relationValueField ?? "id",
1430
1432
  placeholder: `Select ${col.displayName}`
1431
1433
  }
1432
- ) : col.type === "textarea" ? /* @__PURE__ */ jsx17(Textarea, { value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "select" ? /* @__PURE__ */ jsxs11(Select, { onValueChange: (value) => setFormData({ ...formData, [col.field]: value }), children: [
1434
+ ) : col.type === "textarea" ? /* @__PURE__ */ jsx17(
1435
+ Textarea,
1436
+ {
1437
+ rows: typeof col.textareaRows === "number" ? col.textareaRows : 4,
1438
+ className: "min-h-[80px] font-mono text-sm",
1439
+ value: formData[col.field] ?? "",
1440
+ onChange: (e) => handleChange(e, col.field),
1441
+ placeholder: col.placeholder
1442
+ }
1443
+ ) : col.type === "select" ? /* @__PURE__ */ jsxs11(Select, { onValueChange: (value) => setFormData({ ...formData, [col.field]: value }), children: [
1433
1444
  /* @__PURE__ */ jsx17(SelectTrigger, { children: /* @__PURE__ */ jsx17(SelectValue, { placeholder: formData[col.field] || "Select an option" }) }),
1434
1445
  /* @__PURE__ */ jsx17(SelectContent, { children: col.options?.map((option) => /* @__PURE__ */ jsx17(SelectItem, { value: option.value, children: option.label }, option.value)) })
1435
- ] }) : col.type === "boolean" ? /* @__PURE__ */ jsx17(Switch, { checked: formData[col.field] || false, onCheckedChange: () => handleToggleChange(col.field) }) : col.type === "date" ? /* @__PURE__ */ jsx17(Input, { type: "date", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "password" ? /* @__PURE__ */ jsx17(Input, { type: "password", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "number" ? /* @__PURE__ */ jsx17(Input, { type: "number", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "file" ? /* @__PURE__ */ jsx17(FileUpload, { onUploadSuccess: (url) => handleFileUpload(col.field, url) }) : /* @__PURE__ */ jsx17(Input, { type: col.type || "text", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }),
1446
+ ] }) : col.type === "boolean" ? /* @__PURE__ */ jsx17(Switch, { checked: formData[col.field] || false, onCheckedChange: () => handleToggleChange(col.field) }) : col.type === "date" ? /* @__PURE__ */ jsx17(Input, { type: "date", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "password" ? /* @__PURE__ */ jsx17(Input, { type: "password", value: formData[col.field] || "", onChange: (e) => handleChange(e, col.field) }) : col.type === "number" ? /* @__PURE__ */ jsx17(
1447
+ Input,
1448
+ {
1449
+ type: "number",
1450
+ placeholder: col.placeholder,
1451
+ value: formData[col.field] ?? "",
1452
+ onChange: (e) => handleChange(e, col.field)
1453
+ }
1454
+ ) : col.type === "file" ? /* @__PURE__ */ jsx17(FileUpload, { onUploadSuccess: (url) => handleFileUpload(col.field, url) }) : /* @__PURE__ */ jsx17(
1455
+ Input,
1456
+ {
1457
+ type: col.type || "text",
1458
+ placeholder: col.placeholder,
1459
+ value: formData[col.field] ?? "",
1460
+ onChange: (e) => handleChange(e, col.field)
1461
+ }
1462
+ ),
1436
1463
  errors[col.field] && /* @__PURE__ */ jsx17("p", { className: "text-red-500 text-sm mt-1", children: errors[col.field] })
1437
1464
  ] }, col.field)) }),
1438
1465
  /* @__PURE__ */ jsxs11("div", { className: "flex gap-2 p-4 border-t border-gray-200 shrink-0 bg-white", children: [
@@ -2035,6 +2062,10 @@ function AdminCRUD({
2035
2062
  const hasLoadedRef = useRef3(false);
2036
2063
  const isMobile = useIsMobile();
2037
2064
  const showGroupColumn = !!manageUserGroups && roleOptions.length > 0;
2065
+ const listColumns = useMemo2(
2066
+ () => Array.isArray(columns) ? columns.filter((c) => !c.hideInTable) : [],
2067
+ [columns]
2068
+ );
2038
2069
  useEffect6(() => {
2039
2070
  const timeoutId = setTimeout(() => {
2040
2071
  if (searchInput !== searchQuery) {
@@ -2485,7 +2516,7 @@ function AdminCRUD({
2485
2516
  /* @__PURE__ */ jsx22("span", { className: "ml-2", children: "Refreshing list..." })
2486
2517
  ] }),
2487
2518
  isMobile ? /* @__PURE__ */ jsx22("div", { className: "flex flex-col gap-2 min-w-0", children: data && data.length > 0 ? data.map((item, index) => {
2488
- const displayCols = columns?.slice(0, 4) ?? [];
2519
+ const displayCols = listColumns?.slice(0, 4) ?? [];
2489
2520
  const primary = displayCols[0];
2490
2521
  const primaryVal = primary ? getNestedValue(item, primary.field || primary.key) : null;
2491
2522
  return /* @__PURE__ */ jsxs15(
@@ -2559,7 +2590,7 @@ function AdminCRUD({
2559
2590
  );
2560
2591
  }) : /* @__PURE__ */ jsx22("div", { className: "rounded-md border border-gray-200 p-6 text-center text-gray-500", children: "No data found" }) }) : /* @__PURE__ */ jsx22("div", { className: "overflow-x-auto min-w-0 rounded-md border border-gray-200", children: /* @__PURE__ */ jsxs15(Table, { children: [
2561
2592
  /* @__PURE__ */ jsx22(TableHeader, { children: /* @__PURE__ */ jsxs15(TableRow, { children: [
2562
- columns && columns.map((col) => /* @__PURE__ */ jsxs15(
2593
+ listColumns && listColumns.map((col) => /* @__PURE__ */ jsxs15(
2563
2594
  TableHead,
2564
2595
  {
2565
2596
  className: "cursor-pointer",
@@ -2581,7 +2612,7 @@ function AdminCRUD({
2581
2612
  className: "cursor-pointer",
2582
2613
  onClick: () => handleRowClick(item),
2583
2614
  children: [
2584
- columns && columns.map((col, colIndex) => {
2615
+ listColumns && listColumns.map((col, colIndex) => {
2585
2616
  const fieldKey = col.field || col.key;
2586
2617
  const value = getNestedValue(item, fieldKey);
2587
2618
  return /* @__PURE__ */ jsx22(TableCell, { children: formatCellValue(value, col) }, `${item.id}-${colIndex}-${fieldKey}`);
@@ -2665,7 +2696,7 @@ function AdminCRUD({
2665
2696
  )) : /* @__PURE__ */ jsx22(TableRow, { children: /* @__PURE__ */ jsx22(
2666
2697
  TableCell,
2667
2698
  {
2668
- colSpan: columns ? columns.length + 1 + (showGroupColumn ? 1 : 0) : 1,
2699
+ colSpan: listColumns.length ? listColumns.length + 1 + (showGroupColumn ? 1 : 0) : 1,
2669
2700
  className: "text-center py-8",
2670
2701
  children: "No data found"
2671
2702
  }
@@ -3939,7 +3970,7 @@ function TagAutocomplete({
3939
3970
 
3940
3971
  // src/components/Admin/JoditRichText.tsx
3941
3972
  import dynamic from "next/dynamic";
3942
- import { useMemo as useMemo2 } from "react";
3973
+ import { useMemo as useMemo3 } from "react";
3943
3974
  import "jodit/es2021/jodit.min.css";
3944
3975
  import { jsx as jsx28 } from "react/jsx-runtime";
3945
3976
  var JoditEditor = dynamic(() => import("jodit-react").then((m) => m.default), {
@@ -3947,7 +3978,7 @@ var JoditEditor = dynamic(() => import("jodit-react").then((m) => m.default), {
3947
3978
  loading: () => /* @__PURE__ */ jsx28("div", { className: "min-h-[300px] rounded-md border border-gray-200 bg-gray-50 animate-pulse" })
3948
3979
  });
3949
3980
  function JoditRichText({ value, onChange, placeholder, minHeight = 400 }) {
3950
- const config = useMemo2(
3981
+ const config = useMemo3(
3951
3982
  () => ({
3952
3983
  readonly: false,
3953
3984
  placeholder: placeholder ?? "",
@@ -5763,7 +5794,7 @@ function InvitePage() {
5763
5794
 
5764
5795
  // src/admin/pages/DashboardPage.tsx
5765
5796
  import { useSession as useSession4 } from "next-auth/react";
5766
- import { useContext as useContext3, useEffect as useEffect19, useMemo as useMemo3, useState as useState22 } from "react";
5797
+ import { useContext as useContext3, useEffect as useEffect19, useMemo as useMemo4, useState as useState22 } from "react";
5767
5798
  import { useRouter as useRouter6 } from "next/navigation";
5768
5799
  import { Chart as ChartJS2, ArcElement, Tooltip as Tooltip2, Legend as Legend2 } from "chart.js";
5769
5800
  import { Doughnut } from "react-chartjs-2";
@@ -5781,7 +5812,7 @@ function DashboardPage() {
5781
5812
  const [analyticsEnabled, setAnalyticsEnabled] = useState22(false);
5782
5813
  const [days, setDays] = useState22(30);
5783
5814
  const [activeTab, setActiveTab] = useState22("overview");
5784
- const formatMoney4 = useMemo3(
5815
+ const formatMoney4 = useMemo4(
5785
5816
  () => (value) => new Intl.NumberFormat(void 0, {
5786
5817
  style: "currency",
5787
5818
  currency: "INR",
@@ -6460,7 +6491,7 @@ function DashboardPage() {
6460
6491
  }
6461
6492
 
6462
6493
  // src/admin/pages/AdminPageResolver.tsx
6463
- import { useState as useState36, useEffect as useEffect33, useContext as useContext7, useMemo as useMemo4 } from "react";
6494
+ import { useState as useState36, useEffect as useEffect33, useContext as useContext7, useMemo as useMemo5 } from "react";
6464
6495
  import { useRouter as useRouter15 } from "next/navigation";
6465
6496
 
6466
6497
  // src/admin/pages/SubmissionDetailPage.tsx
@@ -9204,8 +9235,26 @@ function PageBuilderPage({ pageId }) {
9204
9235
  }
9205
9236
 
9206
9237
  // src/admin/pages/PluginsPage.tsx
9207
- import { useContext as useContext6, useState as useState30, useEffect as useEffect27 } from "react";
9208
- import { HardDrive, Mail, CreditCard as CreditCard2, MessageCircle, BarChart3 as BarChart32, Building2 as Building22, Puzzle as Puzzle2, CheckCircle2 as CheckCircle22, XCircle, Save as Save5, X as X18, Plus as Plus8, Smartphone } from "lucide-react";
9238
+ import { useContext as useContext6, useState as useState30, useEffect as useEffect27, useCallback as useCallback7 } from "react";
9239
+ import { useSearchParams as useSearchParams4 } from "next/navigation";
9240
+ import {
9241
+ HardDrive,
9242
+ Mail,
9243
+ CreditCard as CreditCard2,
9244
+ MessageCircle,
9245
+ BarChart3 as BarChart32,
9246
+ Building2 as Building22,
9247
+ Puzzle as Puzzle2,
9248
+ CheckCircle2 as CheckCircle22,
9249
+ XCircle,
9250
+ Save as Save5,
9251
+ X as X18,
9252
+ Plus as Plus8,
9253
+ Smartphone,
9254
+ Bot,
9255
+ FileUp,
9256
+ Loader2
9257
+ } from "lucide-react";
9209
9258
 
9210
9259
  // src/lib/email-recipients.ts
9211
9260
  function parseEmailRecipientsFromConfig(raw) {
@@ -9254,6 +9303,10 @@ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
9254
9303
  // src/admin/pages/PluginsPage.tsx
9255
9304
  import { toast as toast5 } from "sonner";
9256
9305
  import { Fragment as Fragment14, jsx as jsx56, jsxs as jsxs46 } from "react/jsx-runtime";
9306
+ function slugifyAgentKey(name) {
9307
+ const s = name.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
9308
+ return s || "agent";
9309
+ }
9257
9310
  function normalizeChatMode(raw) {
9258
9311
  if (raw === "external" || raw === "llm") return raw;
9259
9312
  return "whatsapp";
@@ -9413,6 +9466,26 @@ function PluginSettingsPanel({
9413
9466
  const [chatMode, setChatMode] = useState30("whatsapp");
9414
9467
  const [whatsappPhone, setWhatsappPhone] = useState30("");
9415
9468
  const [externalChatSnippet, setExternalChatSnippet] = useState30("");
9469
+ const [attachedAgentSlug, setAttachedAgentSlug] = useState30("");
9470
+ const [llmAgents, setLlmAgents] = useState30([]);
9471
+ const [agentId, setAgentId] = useState30(null);
9472
+ const [agentName, setAgentName] = useState30("");
9473
+ const [agentSlug, setAgentSlug] = useState30("");
9474
+ const [agentSystem, setAgentSystem] = useState30("");
9475
+ const [agentModel, setAgentModel] = useState30("");
9476
+ const [agentTemp, setAgentTemp] = useState30("");
9477
+ const [agentMaxTokens, setAgentMaxTokens] = useState30("");
9478
+ const [agentValidationJson, setAgentValidationJson] = useState30("");
9479
+ const [agentLoading, setAgentLoading] = useState30(false);
9480
+ const [agentSaving, setAgentSaving] = useState30(false);
9481
+ const [agentProvisionError, setAgentProvisionError] = useState30(null);
9482
+ const [kbCatalog, setKbCatalog] = useState30([]);
9483
+ const [attachedAgentKnowledge, setAttachedAgentKnowledge] = useState30([]);
9484
+ const [attachedKbLoading, setAttachedKbLoading] = useState30(false);
9485
+ const [attachExistingDocId, setAttachExistingDocId] = useState30("__none__");
9486
+ const [uploadingAttachedKbFile, setUploadingAttachedKbFile] = useState30(false);
9487
+ const [uploadingAttachedKbLink, setUploadingAttachedKbLink] = useState30(false);
9488
+ const [attachedKbInputKey, setAttachedKbInputKey] = useState30(0);
9416
9489
  const [erpPipelineName, setErpPipelineName] = useState30("");
9417
9490
  const [erpPipelineStageName, setErpPipelineStageName] = useState30("");
9418
9491
  const [erpFormsCatalog, setErpFormsCatalog] = useState30([]);
@@ -9436,6 +9509,7 @@ function PluginSettingsPanel({
9436
9509
  setIconImageUrl(data.iconImageUrl ?? "");
9437
9510
  setIconBackgroundColor(data.iconBackgroundColor ?? "#6366f1");
9438
9511
  setHeaderColor(data.headerColor ?? "#6366f1");
9512
+ setAttachedAgentSlug(data.attachedAgentSlug ?? "");
9439
9513
  }
9440
9514
  if (isErp) {
9441
9515
  setErpPipelineName(data.pipelineName ?? data.pipelineId ?? "");
@@ -9490,6 +9564,203 @@ function PluginSettingsPanel({
9490
9564
  setErpFormsCatalog(rows);
9491
9565
  }).catch(() => setErpFormsCatalog([]));
9492
9566
  }, [isErp, loading]);
9567
+ const fetchLlmAgents = useCallback7(async () => {
9568
+ const res = await fetch("/api/llm_agents?limit=100&sortField=name&sortOrder=asc");
9569
+ if (!res.ok) {
9570
+ setLlmAgents([]);
9571
+ return;
9572
+ }
9573
+ const j = await res.json();
9574
+ const list = (j.data ?? []).map((r) => ({
9575
+ id: r.id,
9576
+ name: String(r.name ?? ""),
9577
+ slug: String(r.slug ?? ""),
9578
+ enabled: r.enabled !== false
9579
+ }));
9580
+ setLlmAgents(list);
9581
+ const pick = list.find((a) => a.enabled) ?? list[0];
9582
+ const fullRow = (j.data ?? []).find((r) => r.id === pick?.id);
9583
+ if (pick && fullRow) {
9584
+ setAgentId(pick.id);
9585
+ setAgentName(pick.name);
9586
+ setAgentSlug(pick.slug);
9587
+ setAgentSystem(String(fullRow.systemInstruction ?? ""));
9588
+ setAgentModel(String(fullRow.model ?? ""));
9589
+ setAgentTemp(fullRow.temperature != null ? String(fullRow.temperature) : "");
9590
+ setAgentMaxTokens(fullRow.maxTokens != null ? String(fullRow.maxTokens) : "");
9591
+ setAgentValidationJson(String(fullRow.validationRules ?? ""));
9592
+ setAttachedAgentSlug(pick.slug);
9593
+ }
9594
+ }, []);
9595
+ const fetchKbCatalog = useCallback7(async () => {
9596
+ try {
9597
+ const res = await fetch("/api/knowledge_base_documents?limit=300&sortField=name&sortOrder=asc");
9598
+ if (!res.ok) {
9599
+ setKbCatalog([]);
9600
+ return;
9601
+ }
9602
+ const j = await res.json();
9603
+ setKbCatalog(
9604
+ (j.data ?? []).map((r) => ({
9605
+ id: typeof r.id === "number" ? r.id : Number(r.id),
9606
+ name: String(r.name ?? "")
9607
+ })).filter((r) => Number.isInteger(r.id) && r.id > 0)
9608
+ );
9609
+ } catch {
9610
+ setKbCatalog([]);
9611
+ }
9612
+ }, []);
9613
+ const fetchAttachedKnowledge = useCallback7(async (slug) => {
9614
+ if (!slug.trim()) {
9615
+ setAttachedAgentKnowledge([]);
9616
+ return;
9617
+ }
9618
+ setAttachedKbLoading(true);
9619
+ try {
9620
+ const res = await fetch(`/api/llm_agents/${encodeURIComponent(slug.trim())}/knowledge`);
9621
+ if (!res.ok) {
9622
+ setAttachedAgentKnowledge([]);
9623
+ return;
9624
+ }
9625
+ const j = await res.json();
9626
+ setAttachedAgentKnowledge(
9627
+ (j.documents ?? []).map((d) => ({
9628
+ id: typeof d.id === "number" ? d.id : Number(d.id),
9629
+ name: String(d.name ?? "")
9630
+ }))
9631
+ );
9632
+ } catch {
9633
+ setAttachedAgentKnowledge([]);
9634
+ } finally {
9635
+ setAttachedKbLoading(false);
9636
+ }
9637
+ }, []);
9638
+ const bootstrapLlmAgentForPlugins = useCallback7(async () => {
9639
+ setAgentProvisionError(null);
9640
+ const listRes = await fetch("/api/llm_agents?limit=100&sortField=name&sortOrder=asc");
9641
+ if (!listRes.ok) {
9642
+ const errBody = await listRes.json().catch(() => ({}));
9643
+ 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".`;
9644
+ setAgentProvisionError(message);
9645
+ await fetchLlmAgents();
9646
+ return { ok: false, message };
9647
+ }
9648
+ const listJ = await listRes.json();
9649
+ if (listJ.data?.length) {
9650
+ await fetchLlmAgents();
9651
+ return { ok: true };
9652
+ }
9653
+ const defaultName = botName.trim() || "Assistant";
9654
+ const defaultSlug = slugifyAgentKey(defaultName);
9655
+ const createRes = await fetch("/api/llm_agents", {
9656
+ method: "POST",
9657
+ headers: { "Content-Type": "application/json" },
9658
+ body: JSON.stringify({ name: defaultName, slug: defaultSlug, systemInstruction: "", enabled: true })
9659
+ });
9660
+ if (!createRes.ok) {
9661
+ const errBody = await createRes.json().catch(() => ({}));
9662
+ const message = errBody.error ?? `Could not create default agent (HTTP ${createRes.status}). Check create permission for "llm_agents".`;
9663
+ setAgentProvisionError(message);
9664
+ await fetchLlmAgents();
9665
+ return { ok: false, message };
9666
+ }
9667
+ await fetchLlmAgents();
9668
+ return { ok: true };
9669
+ }, [botName, fetchLlmAgents]);
9670
+ const handleRetryBootstrapAgent = async () => {
9671
+ setAgentLoading(true);
9672
+ try {
9673
+ const r = await bootstrapLlmAgentForPlugins();
9674
+ if (r.ok) {
9675
+ toast5.success("Assistant agent ready \u2014 you can upload knowledge files below.");
9676
+ } else {
9677
+ toast5.error(r.message);
9678
+ }
9679
+ } finally {
9680
+ setAgentLoading(false);
9681
+ }
9682
+ };
9683
+ const handleCreateAssistantFromForm = async () => {
9684
+ const name = agentName.trim() || botName.trim();
9685
+ if (!name) {
9686
+ toast5.error("Enter an assistant name");
9687
+ return;
9688
+ }
9689
+ const tempRaw = agentTemp.trim();
9690
+ const maxRaw = agentMaxTokens.trim();
9691
+ const temperature = tempRaw === "" ? null : Number(tempRaw);
9692
+ const maxTokens = maxRaw === "" ? null : parseInt(maxRaw, 10);
9693
+ if (tempRaw !== "" && !Number.isFinite(temperature)) {
9694
+ toast5.error("Temperature must be a number");
9695
+ return;
9696
+ }
9697
+ if (maxRaw !== "" && (!Number.isFinite(maxTokens) || maxTokens < 1)) {
9698
+ toast5.error("Max tokens must be a positive integer");
9699
+ return;
9700
+ }
9701
+ const slug = slugifyAgentKey(name);
9702
+ setAgentSaving(true);
9703
+ try {
9704
+ const res = await fetch("/api/llm_agents", {
9705
+ method: "POST",
9706
+ headers: { "Content-Type": "application/json" },
9707
+ body: JSON.stringify({
9708
+ name,
9709
+ slug,
9710
+ systemInstruction: agentSystem.trim(),
9711
+ model: agentModel.trim() || null,
9712
+ temperature,
9713
+ maxTokens,
9714
+ validationRules: agentValidationJson.trim() || null,
9715
+ enabled: true
9716
+ })
9717
+ });
9718
+ if (!res.ok) {
9719
+ const err = await res.json().catch(() => ({}));
9720
+ toast5.error(err.error || "Failed to create assistant");
9721
+ return;
9722
+ }
9723
+ setAgentProvisionError(null);
9724
+ setAttachedAgentSlug(slug);
9725
+ await fetchLlmAgents();
9726
+ toast5.success("Assistant created \u2014 add knowledge files below.");
9727
+ } catch {
9728
+ toast5.error("Failed to create assistant");
9729
+ } finally {
9730
+ setAgentSaving(false);
9731
+ }
9732
+ };
9733
+ useEffect27(() => {
9734
+ if (!isLlm || loading || chatMode !== "llm") return;
9735
+ setAgentLoading(true);
9736
+ void (async () => {
9737
+ try {
9738
+ await bootstrapLlmAgentForPlugins();
9739
+ } finally {
9740
+ setAgentLoading(false);
9741
+ }
9742
+ })();
9743
+ }, [isLlm, loading, chatMode, bootstrapLlmAgentForPlugins]);
9744
+ useEffect27(() => {
9745
+ if (!isLlm || loading || chatMode !== "llm") return;
9746
+ void fetchKbCatalog();
9747
+ }, [isLlm, loading, chatMode, fetchKbCatalog]);
9748
+ useEffect27(() => {
9749
+ if (!isLlm || loading || chatMode !== "llm" || !attachedAgentSlug.trim()) {
9750
+ setAttachedAgentKnowledge([]);
9751
+ return;
9752
+ }
9753
+ void fetchAttachedKnowledge(attachedAgentSlug);
9754
+ }, [isLlm, loading, chatMode, attachedAgentSlug, fetchAttachedKnowledge]);
9755
+ useEffect27(() => {
9756
+ setAttachExistingDocId("__none__");
9757
+ }, [attachedAgentSlug]);
9758
+ useEffect27(() => {
9759
+ if (!isLlm || loading || chatMode !== "llm" || agentLoading || agentId) return;
9760
+ if (!agentName.trim() && botName.trim()) {
9761
+ setAgentName(botName.trim());
9762
+ }
9763
+ }, [isLlm, loading, chatMode, agentLoading, agentId, agentName, botName]);
9493
9764
  const buildPayload = () => {
9494
9765
  if (isErp) {
9495
9766
  const sortedIds = [...new Set(erpOpportunityFormIds.filter((n) => Number.isInteger(n) && n > 0))].sort(
@@ -9524,6 +9795,7 @@ function PluginSettingsPanel({
9524
9795
  payload.iconImageUrl = { value: iconImageUrl, type: "public" };
9525
9796
  payload.iconBackgroundColor = { value: iconBackgroundColor, type: "public" };
9526
9797
  payload.headerColor = { value: headerColor, type: "public" };
9798
+ payload.attachedAgentSlug = { value: attachedAgentSlug.trim(), type: "public" };
9527
9799
  }
9528
9800
  if (isEmail) {
9529
9801
  payload.salesTeamEmails = { value: serializeEmailRecipients(salesTeamEmails), type: "public" };
@@ -9539,6 +9811,56 @@ function PluginSettingsPanel({
9539
9811
  }
9540
9812
  return payload;
9541
9813
  };
9814
+ const handleSaveAgent = async () => {
9815
+ if (!agentId) {
9816
+ toast5.error("No agent to save");
9817
+ return;
9818
+ }
9819
+ const name = agentName.trim();
9820
+ if (!name) {
9821
+ toast5.error("Agent name is required");
9822
+ return;
9823
+ }
9824
+ const tempRaw = agentTemp.trim();
9825
+ const maxRaw = agentMaxTokens.trim();
9826
+ const temperature = tempRaw === "" ? null : Number(tempRaw);
9827
+ const maxTokens = maxRaw === "" ? null : parseInt(maxRaw, 10);
9828
+ if (tempRaw !== "" && !Number.isFinite(temperature)) {
9829
+ toast5.error("Temperature must be a number");
9830
+ return;
9831
+ }
9832
+ if (maxRaw !== "" && (!Number.isFinite(maxTokens) || maxTokens < 1)) {
9833
+ toast5.error("Max tokens must be a positive integer");
9834
+ return;
9835
+ }
9836
+ setAgentSaving(true);
9837
+ try {
9838
+ const res = await fetch(`/api/llm_agents/${agentId}`, {
9839
+ method: "PUT",
9840
+ headers: { "Content-Type": "application/json" },
9841
+ body: JSON.stringify({
9842
+ name,
9843
+ systemInstruction: agentSystem.trim(),
9844
+ model: agentModel.trim() || null,
9845
+ temperature,
9846
+ maxTokens,
9847
+ validationRules: agentValidationJson.trim() || null,
9848
+ enabled: true
9849
+ })
9850
+ });
9851
+ if (!res.ok) {
9852
+ const err = await res.json().catch(() => ({}));
9853
+ toast5.error(err.error || "Failed to update agent");
9854
+ return;
9855
+ }
9856
+ setAttachedAgentSlug(agentSlug);
9857
+ toast5.success("Agent saved");
9858
+ } catch {
9859
+ toast5.error("Failed to update agent");
9860
+ } finally {
9861
+ setAgentSaving(false);
9862
+ }
9863
+ };
9542
9864
  const handleSave = async () => {
9543
9865
  setSaving(true);
9544
9866
  try {
@@ -9572,6 +9894,96 @@ function PluginSettingsPanel({
9572
9894
  setSaving(false);
9573
9895
  }
9574
9896
  };
9897
+ const uploadKbFileToAttached = async (file) => {
9898
+ const slug = agentSlug.trim() || attachedAgentSlug.trim();
9899
+ if (!slug) {
9900
+ toast5.error("No agent configured");
9901
+ return;
9902
+ }
9903
+ setUploadingAttachedKbFile(true);
9904
+ try {
9905
+ const fd = new FormData();
9906
+ const stem = file.name.replace(/\.[^/.]+$/, "").trim() || "Upload";
9907
+ fd.append("name", stem);
9908
+ fd.append("file", file);
9909
+ const r = await fetch(`/api/llm_agents/${encodeURIComponent(slug)}/knowledge`, { method: "POST", body: fd });
9910
+ if (!r.ok) {
9911
+ const err = await r.json().catch(() => ({}));
9912
+ toast5.error(err.error || "Upload failed");
9913
+ return;
9914
+ }
9915
+ const ingest = await r.json().catch(() => ({}));
9916
+ if (ingest.warning) {
9917
+ toast5.warning(ingest.detail ? `${ingest.warning} (${ingest.detail})` : ingest.warning);
9918
+ } else if (ingest.embeddingAttempted && (ingest.embeddingsWritten ?? 0) === 0 && (ingest.chunkCount ?? 0) > 0) {
9919
+ toast5.warning(
9920
+ "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)."
9921
+ );
9922
+ } else {
9923
+ toast5.success("Knowledge added and linked");
9924
+ }
9925
+ setAttachedKbInputKey((k) => k + 1);
9926
+ await fetchAttachedKnowledge(slug);
9927
+ await fetchKbCatalog();
9928
+ } finally {
9929
+ setUploadingAttachedKbFile(false);
9930
+ }
9931
+ };
9932
+ const onAttachedKbFileChange = (e) => {
9933
+ const file = e.target.files?.[0];
9934
+ if (!file) return;
9935
+ const slug = agentSlug.trim() || attachedAgentSlug.trim();
9936
+ if (!slug) {
9937
+ toast5.error("No agent configured");
9938
+ e.target.value = "";
9939
+ return;
9940
+ }
9941
+ void uploadKbFileToAttached(file);
9942
+ };
9943
+ const handleAttachExistingToAttached = async () => {
9944
+ const slug = agentSlug.trim() || attachedAgentSlug.trim();
9945
+ if (!slug || attachExistingDocId === "__none__") return;
9946
+ const docId = parseInt(attachExistingDocId, 10);
9947
+ if (!Number.isFinite(docId)) return;
9948
+ setUploadingAttachedKbLink(true);
9949
+ try {
9950
+ const r = await fetch(`/api/llm_agents/${encodeURIComponent(slug)}/knowledge`, {
9951
+ method: "POST",
9952
+ headers: { "Content-Type": "application/json" },
9953
+ body: JSON.stringify({ documentId: docId })
9954
+ });
9955
+ if (!r.ok) {
9956
+ const err = await r.json().catch(() => ({}));
9957
+ toast5.error(err.error || "Attach failed");
9958
+ return;
9959
+ }
9960
+ const ingest = await r.json().catch(() => ({}));
9961
+ if (ingest.warning) {
9962
+ toast5.warning(ingest.detail ? `${ingest.warning} (${ingest.detail})` : ingest.warning);
9963
+ } else if (ingest.embeddingAttempted && (ingest.embeddingsWritten ?? 0) === 0 && ((ingest.chunksQueuedForEmbedding ?? 0) > 0 || (ingest.chunkCount ?? 0) > 0)) {
9964
+ toast5.warning(
9965
+ "Document attached but no embeddings were written. Configure the LLM/embed gateway, then attach again to fill NULL embeddings."
9966
+ );
9967
+ } else {
9968
+ toast5.success("Document attached");
9969
+ }
9970
+ setAttachExistingDocId("__none__");
9971
+ await fetchAttachedKnowledge(slug);
9972
+ } finally {
9973
+ setUploadingAttachedKbLink(false);
9974
+ }
9975
+ };
9976
+ const handleUnlinkKbDoc = async (docId) => {
9977
+ const slug = agentSlug.trim() || attachedAgentSlug.trim();
9978
+ if (!slug) return;
9979
+ const r = await fetch(`/api/llm_agents/${encodeURIComponent(slug)}/knowledge/${docId}`, { method: "DELETE" });
9980
+ if (!r.ok) {
9981
+ toast5.error("Could not remove link");
9982
+ return;
9983
+ }
9984
+ toast5.success("Removed from agent");
9985
+ await fetchAttachedKnowledge(slug);
9986
+ };
9575
9987
  if (loading) return /* @__PURE__ */ jsx56("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Loading..." });
9576
9988
  if (isErp) {
9577
9989
  return /* @__PURE__ */ jsxs46("div", { className: "space-y-4", children: [
@@ -10013,8 +10425,240 @@ function PluginSettingsPanel({
10013
10425
  /* @__PURE__ */ jsx56("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Only paste code from sources you trust." })
10014
10426
  ] }),
10015
10427
  chatMode === "llm" && /* @__PURE__ */ jsxs46(Fragment14, { children: [
10428
+ /* @__PURE__ */ jsxs46("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: [
10429
+ /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10430
+ /* @__PURE__ */ jsxs46("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white", children: [
10431
+ /* @__PURE__ */ jsx56(Bot, { className: "h-4 w-4" }),
10432
+ "Chat assistant (single agent)"
10433
+ ] }),
10434
+ /* @__PURE__ */ jsx56("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." })
10435
+ ] }),
10436
+ agentLoading ? /* @__PURE__ */ jsxs46("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [
10437
+ /* @__PURE__ */ jsx56(Loader2, { className: "h-4 w-4 animate-spin" }),
10438
+ "Loading assistant\u2026"
10439
+ ] }) : /* @__PURE__ */ jsxs46(Fragment14, { children: [
10440
+ agentProvisionError ? /* @__PURE__ */ jsx56("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,
10441
+ /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10442
+ /* @__PURE__ */ jsx56(Label3, { htmlFor: "agent-name", className: "text-sm", children: "Assistant name" }),
10443
+ /* @__PURE__ */ jsx56(
10444
+ Input,
10445
+ {
10446
+ id: "agent-name",
10447
+ value: agentName,
10448
+ onChange: (e) => setAgentName(e.target.value),
10449
+ placeholder: "e.g. JM Buddy",
10450
+ className: "h-8 text-sm"
10451
+ }
10452
+ )
10453
+ ] }),
10454
+ /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10455
+ /* @__PURE__ */ jsx56(Label3, { htmlFor: "agent-slug", className: "text-sm", children: "Slug" }),
10456
+ /* @__PURE__ */ jsx56(
10457
+ Input,
10458
+ {
10459
+ id: "agent-slug",
10460
+ value: agentId ? agentSlug : slugifyAgentKey((agentName || botName).trim() || "assistant"),
10461
+ disabled: true,
10462
+ className: "h-8 text-sm font-mono bg-gray-100 dark:bg-gray-700"
10463
+ }
10464
+ ),
10465
+ /* @__PURE__ */ jsx56("p", { className: "text-[11px] text-gray-500", children: "Derived from the name. Used in API routes." })
10466
+ ] }),
10467
+ /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10468
+ /* @__PURE__ */ jsx56(Label3, { htmlFor: "agent-system", className: "text-sm", children: "System instruction" }),
10469
+ /* @__PURE__ */ jsx56(
10470
+ Textarea,
10471
+ {
10472
+ id: "agent-system",
10473
+ value: agentSystem,
10474
+ onChange: (e) => setAgentSystem(e.target.value),
10475
+ rows: 5,
10476
+ placeholder: "How the model should behave\u2026",
10477
+ className: "text-sm"
10478
+ }
10479
+ )
10480
+ ] }),
10481
+ /* @__PURE__ */ jsxs46("div", { className: "grid grid-cols-2 gap-2", children: [
10482
+ /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10483
+ /* @__PURE__ */ jsx56(Label3, { htmlFor: "agent-model", className: "text-sm", children: "Model (optional)" }),
10484
+ /* @__PURE__ */ jsx56(
10485
+ Input,
10486
+ {
10487
+ id: "agent-model",
10488
+ value: agentModel,
10489
+ onChange: (e) => setAgentModel(e.target.value),
10490
+ placeholder: "Gateway model id",
10491
+ className: "h-8 text-sm font-mono"
10492
+ }
10493
+ )
10494
+ ] }),
10495
+ /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10496
+ /* @__PURE__ */ jsx56(Label3, { htmlFor: "agent-temp", className: "text-sm", children: "Temperature" }),
10497
+ /* @__PURE__ */ jsx56(
10498
+ Input,
10499
+ {
10500
+ id: "agent-temp",
10501
+ value: agentTemp,
10502
+ onChange: (e) => setAgentTemp(e.target.value),
10503
+ placeholder: "e.g. 0.7",
10504
+ className: "h-8 text-sm"
10505
+ }
10506
+ )
10507
+ ] })
10508
+ ] }),
10509
+ /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10510
+ /* @__PURE__ */ jsx56(Label3, { htmlFor: "agent-max", className: "text-sm", children: "Max tokens" }),
10511
+ /* @__PURE__ */ jsx56(
10512
+ Input,
10513
+ {
10514
+ id: "agent-max",
10515
+ value: agentMaxTokens,
10516
+ onChange: (e) => setAgentMaxTokens(e.target.value.replace(/\D/g, "")),
10517
+ placeholder: "e.g. 1024",
10518
+ className: "h-8 text-sm"
10519
+ }
10520
+ )
10521
+ ] }),
10522
+ /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10523
+ /* @__PURE__ */ jsx56(Label3, { htmlFor: "agent-validation", className: "text-sm", children: "Validation & output guardrails" }),
10524
+ /* @__PURE__ */ jsx56(
10525
+ Textarea,
10526
+ {
10527
+ id: "agent-validation",
10528
+ value: agentValidationJson,
10529
+ onChange: (e) => setAgentValidationJson(e.target.value),
10530
+ rows: 4,
10531
+ placeholder: 'Plain text or JSON: {"guardrails":"Never promise refunds.","maxUserChars":2000}',
10532
+ className: "text-xs font-mono"
10533
+ }
10534
+ )
10535
+ ] }),
10536
+ !agentId ? /* @__PURE__ */ jsxs46("div", { className: "flex flex-wrap items-center gap-2", children: [
10537
+ /* @__PURE__ */ jsx56(
10538
+ Button,
10539
+ {
10540
+ type: "button",
10541
+ size: "sm",
10542
+ className: "gap-1",
10543
+ disabled: agentSaving,
10544
+ onClick: () => void handleCreateAssistantFromForm(),
10545
+ children: agentSaving ? /* @__PURE__ */ jsxs46("span", { className: "inline-flex items-center gap-1.5", children: [
10546
+ /* @__PURE__ */ jsx56(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
10547
+ "Creating\u2026"
10548
+ ] }) : /* @__PURE__ */ jsxs46(Fragment14, { children: [
10549
+ /* @__PURE__ */ jsx56(Bot, { className: "h-3.5 w-3.5" }),
10550
+ "Create assistant"
10551
+ ] })
10552
+ }
10553
+ ),
10554
+ /* @__PURE__ */ jsx56(
10555
+ Button,
10556
+ {
10557
+ type: "button",
10558
+ size: "sm",
10559
+ variant: "secondary",
10560
+ className: "gap-1",
10561
+ disabled: agentSaving || agentLoading,
10562
+ onClick: () => void handleRetryBootstrapAgent(),
10563
+ children: "Retry auto-setup"
10564
+ }
10565
+ )
10566
+ ] }) : /* @__PURE__ */ jsx56(
10567
+ Button,
10568
+ {
10569
+ type: "button",
10570
+ size: "sm",
10571
+ className: "gap-1",
10572
+ disabled: agentSaving,
10573
+ onClick: () => void handleSaveAgent(),
10574
+ children: agentSaving ? /* @__PURE__ */ jsxs46("span", { className: "inline-flex items-center gap-1.5", children: [
10575
+ /* @__PURE__ */ jsx56(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
10576
+ "Saving\u2026"
10577
+ ] }) : /* @__PURE__ */ jsxs46(Fragment14, { children: [
10578
+ /* @__PURE__ */ jsx56(Save5, { className: "h-3.5 w-3.5" }),
10579
+ "Save assistant"
10580
+ ] })
10581
+ }
10582
+ )
10583
+ ] }),
10584
+ agentId && agentSlug.trim() ? /* @__PURE__ */ jsxs46("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: [
10585
+ /* @__PURE__ */ jsxs46("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800 dark:text-gray-200", children: [
10586
+ /* @__PURE__ */ jsx56(FileUp, { className: "h-3.5 w-3.5 shrink-0" }),
10587
+ "Knowledge for this agent"
10588
+ ] }),
10589
+ /* @__PURE__ */ jsx56("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." }),
10590
+ attachedKbLoading ? /* @__PURE__ */ jsx56("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Loading linked documents\u2026" }) : attachedAgentKnowledge.length === 0 ? /* @__PURE__ */ jsx56("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: "No documents linked yet." }) : /* @__PURE__ */ jsx56("ul", { className: "max-h-32 space-y-1.5 overflow-y-auto", children: attachedAgentKnowledge.map((d) => /* @__PURE__ */ jsxs46("li", { className: "flex items-center justify-between gap-2 text-sm", children: [
10591
+ /* @__PURE__ */ jsx56("span", { className: "min-w-0 truncate", title: d.name, children: d.name }),
10592
+ /* @__PURE__ */ jsx56(
10593
+ Button,
10594
+ {
10595
+ type: "button",
10596
+ variant: "ghost",
10597
+ size: "sm",
10598
+ className: "h-7 shrink-0 text-xs text-red-600 hover:text-red-700 dark:text-red-400",
10599
+ onClick: () => void handleUnlinkKbDoc(d.id),
10600
+ children: "Remove"
10601
+ }
10602
+ )
10603
+ ] }, d.id)) }),
10604
+ /* @__PURE__ */ jsxs46("div", { className: "min-w-[180px] space-y-1", children: [
10605
+ /* @__PURE__ */ jsx56(Label3, { className: "text-xs", children: "Upload file" }),
10606
+ /* @__PURE__ */ jsxs46("div", { className: "relative", children: [
10607
+ /* @__PURE__ */ jsx56(
10608
+ Input,
10609
+ {
10610
+ type: "file",
10611
+ accept: ".txt,.md,.json,.pdf,text/plain,text/markdown,application/json,application/pdf",
10612
+ disabled: uploadingAttachedKbFile || uploadingAttachedKbLink,
10613
+ className: "h-8 cursor-pointer text-xs disabled:opacity-60",
10614
+ onChange: onAttachedKbFileChange
10615
+ },
10616
+ attachedKbInputKey
10617
+ ),
10618
+ uploadingAttachedKbFile ? /* @__PURE__ */ jsxs46(
10619
+ "div",
10620
+ {
10621
+ 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",
10622
+ "aria-live": "polite",
10623
+ children: [
10624
+ /* @__PURE__ */ jsx56(Loader2, { className: "h-4 w-4 shrink-0 animate-spin" }),
10625
+ "Saving & linking\u2026"
10626
+ ]
10627
+ }
10628
+ ) : null
10629
+ ] }),
10630
+ /* @__PURE__ */ jsx56("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)." })
10631
+ ] }),
10632
+ /* @__PURE__ */ jsxs46("div", { className: "flex flex-wrap items-end gap-2", children: [
10633
+ /* @__PURE__ */ jsxs46("div", { className: "min-w-[200px] flex-1 space-y-1", children: [
10634
+ /* @__PURE__ */ jsx56(Label3, { className: "text-xs", children: "Attach existing document" }),
10635
+ /* @__PURE__ */ jsxs46(Select, { value: attachExistingDocId, onValueChange: setAttachExistingDocId, children: [
10636
+ /* @__PURE__ */ jsx56(SelectTrigger, { className: "h-8 text-xs", children: /* @__PURE__ */ jsx56(SelectValue, { placeholder: "Choose a document" }) }),
10637
+ /* @__PURE__ */ jsxs46(SelectContent, { children: [
10638
+ /* @__PURE__ */ jsx56(SelectItem, { value: "__none__", children: "\u2014 Select \u2014" }),
10639
+ kbCatalog.filter((d) => !attachedAgentKnowledge.some((a) => a.id === d.id)).map((d) => /* @__PURE__ */ jsx56(SelectItem, { value: String(d.id), children: d.name }, d.id))
10640
+ ] })
10641
+ ] })
10642
+ ] }),
10643
+ /* @__PURE__ */ jsx56(
10644
+ Button,
10645
+ {
10646
+ type: "button",
10647
+ size: "sm",
10648
+ className: "h-8",
10649
+ disabled: uploadingAttachedKbFile || uploadingAttachedKbLink || attachExistingDocId === "__none__",
10650
+ onClick: () => void handleAttachExistingToAttached(),
10651
+ children: uploadingAttachedKbLink ? /* @__PURE__ */ jsxs46("span", { className: "inline-flex items-center gap-1.5", children: [
10652
+ /* @__PURE__ */ jsx56(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
10653
+ "Linking\u2026"
10654
+ ] }) : "Attach"
10655
+ }
10656
+ )
10657
+ ] })
10658
+ ] }) : null
10659
+ ] }),
10016
10660
  /* @__PURE__ */ jsxs46("div", { className: "space-y-1", children: [
10017
- /* @__PURE__ */ jsx56(Label3, { htmlFor: `${settingsGroup}-botName`, className: "text-sm", children: "Name" }),
10661
+ /* @__PURE__ */ jsx56(Label3, { htmlFor: `${settingsGroup}-botName`, className: "text-sm", children: "Widget title" }),
10018
10662
  /* @__PURE__ */ jsx56(
10019
10663
  Input,
10020
10664
  {
@@ -10184,8 +10828,14 @@ function PluginListItem({
10184
10828
  }
10185
10829
  function PluginsPage() {
10186
10830
  const { pluginDescriptors = [] } = useContext6(AdminConfigContext);
10831
+ const searchParams = useSearchParams4();
10187
10832
  const [selectedName, setSelectedName] = useState30(null);
10188
10833
  const [enabledMap, setEnabledMap] = useState30({});
10834
+ useEffect27(() => {
10835
+ if (searchParams.get("plugin") !== "llm") return;
10836
+ const llmDesc = pluginDescriptors.find((p) => p.settingsGroup === "llm");
10837
+ if (llmDesc) setSelectedName(llmDesc.name);
10838
+ }, [searchParams, pluginDescriptors]);
10189
10839
  useEffect27(() => {
10190
10840
  pluginDescriptors.forEach((p) => {
10191
10841
  if (!p.settingsGroup) return;
@@ -11770,7 +12420,7 @@ function CollectionEditPage({ collectionId }) {
11770
12420
  }
11771
12421
 
11772
12422
  // src/admin/pages/RolesPage.tsx
11773
- import { useCallback as useCallback7, useEffect as useEffect32, useState as useState35 } from "react";
12423
+ import { useCallback as useCallback8, useEffect as useEffect32, useState as useState35 } from "react";
11774
12424
  import { useSession as useSession5 } from "next-auth/react";
11775
12425
  import { Shield as Shield2, Save as Save9, Trash2 as Trash27 } from "lucide-react";
11776
12426
 
@@ -11814,7 +12464,7 @@ function RolesPage() {
11814
12464
  const [newName, setNewName] = useState35("");
11815
12465
  const [deleteRoleOpen, setDeleteRoleOpen] = useState35(false);
11816
12466
  const [error, setError] = useState35(null);
11817
- const load = useCallback7(async () => {
12467
+ const load = useCallback8(async () => {
11818
12468
  setLoading(true);
11819
12469
  setError(null);
11820
12470
  try {
@@ -12375,6 +13025,9 @@ function AdminPageResolver({ slug }) {
12375
13025
  if (key === "layout-settings") {
12376
13026
  router.replace("/admin/settings?tab=navbar");
12377
13027
  }
13028
+ if (key === "llm_agents") {
13029
+ router.replace("/admin/plugins?plugin=llm");
13030
+ }
12378
13031
  }, [key, router]);
12379
13032
  if (key === "layout-settings") {
12380
13033
  return /* @__PURE__ */ jsxs53("div", { className: "flex justify-center py-8", children: [
@@ -12382,6 +13035,12 @@ function AdminPageResolver({ slug }) {
12382
13035
  /* @__PURE__ */ jsx63("span", { className: "ml-2", children: "Redirecting..." })
12383
13036
  ] });
12384
13037
  }
13038
+ if (key === "llm_agents") {
13039
+ return /* @__PURE__ */ jsxs53("div", { className: "flex justify-center py-8", children: [
13040
+ /* @__PURE__ */ jsx63("div", { className: "animate-spin rounded-full h-6 w-6 border-2 border-gray-300 border-t-gray-600" }),
13041
+ /* @__PURE__ */ jsx63("span", { className: "ml-2", children: "Opening Plugins\u2026" })
13042
+ ] });
13043
+ }
12385
13044
  const Page = PAGE_MAP[key];
12386
13045
  if (Page) {
12387
13046
  return /* @__PURE__ */ jsx63(Page, {});
@@ -12431,7 +13090,7 @@ function AdminPageResolver({ slug }) {
12431
13090
  { field: "orderCount", displayName: "Orders" },
12432
13091
  { field: "totalPaid", displayName: "Total paid" }
12433
13092
  ] : crud.columns;
12434
- const extraListParams = useMemo4(
13093
+ const extraListParams = useMemo5(
12435
13094
  () => isContactsWithStore ? { includeSummary: "1" } : void 0,
12436
13095
  [isContactsWithStore]
12437
13096
  );