@data-club/ai-hub 0.0.4 → 0.0.5

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.
@@ -27,7 +27,10 @@
27
27
  import { useEffect, useState, type ReactNode } from "react";
28
28
 
29
29
  import type { AIHubAdminTexts, AIHubLanguage } from "../../auth/texts.js";
30
- import type { AdminSettingsView } from "../../client/AIHubClient.js";
30
+ import type {
31
+ AdminAgentView,
32
+ AdminSettingsView,
33
+ } from "../../client/AIHubClient.js";
31
34
  import { useAIHub } from "../../context/useAIHub.js";
32
35
  import { useAIHubAuth } from "../../context/useAIHubAuth.js";
33
36
  import {
@@ -188,6 +191,11 @@ export function AdminSettingsPage(props: AdminSettingsPageProps): ReactNode {
188
191
  texts={props.texts}
189
192
  onSaved={handleSectionSaved}
190
193
  />
194
+ <AgentsSection
195
+ settings={settings}
196
+ texts={props.texts}
197
+ onSaved={handleSectionSaved}
198
+ />
191
199
  <FeatureTogglesSection
192
200
  settings={settings}
193
201
  texts={props.texts}
@@ -646,6 +654,142 @@ function LanguageSection(props: LanguageSectionProps): ReactNode {
646
654
  );
647
655
  }
648
656
 
657
+ /* ────────────────────────────────────────────────────────────────────────── */
658
+ /* Section: Agents */
659
+ /* ────────────────────────────────────────────────────────────────────────── */
660
+
661
+ interface AgentsSectionProps extends SectionProps {
662
+ onSaved: (next: AdminSettingsView) => void;
663
+ }
664
+
665
+ function AgentsSection(props: AgentsSectionProps): ReactNode {
666
+ const { client } = useAIHub();
667
+ const [defaultAgentId, setDefaultAgentId] = useState<string>(
668
+ props.settings.agents.default_id ?? "",
669
+ );
670
+ const [switcherEnabled, setSwitcherEnabled] = useState(
671
+ props.settings.agents.switcher_enabled,
672
+ );
673
+ // Non-archived agents to populate the default-agent <select>.
674
+ const [agents, setAgents] = useState<AdminAgentView[] | null>(null);
675
+ const [saving, setSaving] = useState(false);
676
+ const [flash, setFlash] = useState<{ tone: "success" | "error"; text: string } | null>(
677
+ null,
678
+ );
679
+
680
+ useEffect(() => {
681
+ let cancelled = false;
682
+ void (async () => {
683
+ try {
684
+ const rows = await client.listAdminAgents();
685
+ if (cancelled) return;
686
+ setAgents(rows.filter((a) => !a.is_archived));
687
+ } catch {
688
+ if (!cancelled) setAgents([]);
689
+ }
690
+ })();
691
+ return () => {
692
+ cancelled = true;
693
+ };
694
+ }, [client]);
695
+
696
+ async function handleSave(event: React.FormEvent): Promise<void> {
697
+ event.preventDefault();
698
+ setSaving(true);
699
+ setFlash(null);
700
+ try {
701
+ const next = await client.patchAdminSettings({
702
+ agents: { defaultId: defaultAgentId, switcherEnabled },
703
+ });
704
+ props.onSaved(next);
705
+ setFlash({ tone: "success", text: props.texts.settingsSaveSuccessFlash });
706
+ } catch (err) {
707
+ setFlash({
708
+ tone: "error",
709
+ text:
710
+ err instanceof Error && err.message.length > 0
711
+ ? err.message
712
+ : props.texts.settingsSaveGenericError,
713
+ });
714
+ } finally {
715
+ setSaving(false);
716
+ }
717
+ }
718
+
719
+ return (
720
+ <section
721
+ className="data-club-ai-hub-admin__settings-section"
722
+ data-testid="admin-settings-agents-section"
723
+ >
724
+ <h2 className="data-club-ai-hub-admin__settings-section-heading">
725
+ {props.texts.settingsAgentsSectionHeading}
726
+ </h2>
727
+ <form onSubmit={(event) => void handleSave(event)} noValidate>
728
+ <label
729
+ className="data-club-ai-hub-admin__form-label"
730
+ htmlFor="admin-settings-default-agent"
731
+ >
732
+ {props.texts.settingsAgentsDefaultLabel}
733
+ </label>
734
+ <select
735
+ id="admin-settings-default-agent"
736
+ className="data-club-ai-hub-admin__form-input"
737
+ data-testid="admin-settings-default-agent"
738
+ value={defaultAgentId}
739
+ onChange={(event) => setDefaultAgentId(event.target.value)}
740
+ disabled={saving || agents === null}
741
+ >
742
+ <option value="">{props.texts.settingsAgentsDefaultNone}</option>
743
+ {(agents ?? []).map((agent) => (
744
+ <option key={agent.id} value={agent.id}>
745
+ {agent.display_name}
746
+ </option>
747
+ ))}
748
+ </select>
749
+ <p className="data-club-ai-hub-admin__form-helper">
750
+ {props.texts.settingsAgentsDefaultHelper}
751
+ </p>
752
+ <label className="data-club-ai-hub-admin__checkbox">
753
+ <input
754
+ type="checkbox"
755
+ data-testid="admin-settings-agent-switcher-enabled"
756
+ checked={switcherEnabled}
757
+ onChange={(event) => setSwitcherEnabled(event.target.checked)}
758
+ disabled={saving}
759
+ />
760
+ <span>{props.texts.settingsAgentsSwitcherEnabledLabel}</span>
761
+ </label>
762
+ <p className="data-club-ai-hub-admin__form-helper">
763
+ {props.texts.settingsAgentsSwitcherEnabledHelper}
764
+ </p>
765
+ {flash !== null ? (
766
+ <div
767
+ className={
768
+ flash.tone === "success"
769
+ ? "data-club-ai-hub-admin__flash data-club-ai-hub-admin__flash--success"
770
+ : "data-club-ai-hub-admin__flash data-club-ai-hub-admin__flash--error"
771
+ }
772
+ data-testid="admin-settings-agents-flash"
773
+ role={flash.tone === "error" ? "alert" : "status"}
774
+ >
775
+ {flash.text}
776
+ </div>
777
+ ) : null}
778
+ <button
779
+ type="submit"
780
+ className="data-club-ai-hub-admin__button data-club-ai-hub-admin__button--primary"
781
+ data-testid="admin-settings-agents-save"
782
+ disabled={saving}
783
+ >
784
+ {saving
785
+ ? props.texts.settingsSaveSavingButton
786
+ : props.texts.settingsSaveAgentsButton}
787
+ </button>
788
+ </form>
789
+ </section>
790
+ );
791
+ }
792
+
649
793
  /* ────────────────────────────────────────────────────────────────────────── */
650
794
  /* Section: Feature toggles */
651
795
  /* ────────────────────────────────────────────────────────────────────────── */
package/src/types.ts CHANGED
@@ -112,6 +112,17 @@ export interface AIHubDeployment {
112
112
  * per-device choice.
113
113
  */
114
114
  user_language_locked: boolean;
115
+ /**
116
+ * ai-hub agent id auto-opened on load (skipping the agents picker). `null` =
117
+ * no default → the picker is shown. Ignored when only one agent exists (that
118
+ * one always opens). A dangling id falls back to the picker.
119
+ */
120
+ default_agent_id: string | null;
121
+ /**
122
+ * When `false`, users can't switch agents — the "← Agents" back link is
123
+ * hidden. (Also hidden whenever only one agent exists, regardless of this.)
124
+ */
125
+ agent_switcher_enabled: boolean;
115
126
  /**
116
127
  * Browser-facing URL of the self-hosted Letta ADE — only present when the
117
128
  * caller's role is `power_user` or higher. Absent for `user`-role callers.