@data-club/ai-hub 0.0.7 → 0.0.9

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.
Files changed (43) hide show
  1. package/dist/auth/locales/cs.d.ts.map +1 -1
  2. package/dist/auth/locales/cs.js +1 -4
  3. package/dist/auth/locales/cs.js.map +1 -1
  4. package/dist/auth/locales/sk.d.ts.map +1 -1
  5. package/dist/auth/locales/sk.js +1 -4
  6. package/dist/auth/locales/sk.js.map +1 -1
  7. package/dist/auth/texts.d.ts +2 -9
  8. package/dist/auth/texts.d.ts.map +1 -1
  9. package/dist/auth/texts.js +1 -4
  10. package/dist/auth/texts.js.map +1 -1
  11. package/dist/components/ChatShell/ShellHeader.d.ts +4 -4
  12. package/dist/components/ChatShell/UserMenu.d.ts +4 -10
  13. package/dist/components/ChatShell/UserMenu.d.ts.map +1 -1
  14. package/dist/components/ChatShell/UserMenu.js +8 -15
  15. package/dist/components/ChatShell/UserMenu.js.map +1 -1
  16. package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts +1 -1
  17. package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts.map +1 -1
  18. package/dist/components/DataClubAIHubAdmin/AgentsPage.js +2 -11
  19. package/dist/components/DataClubAIHubAdmin/AgentsPage.js.map +1 -1
  20. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts.map +1 -1
  21. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js +6 -3
  22. package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js.map +1 -1
  23. package/dist/components/DataClubAIHubAdmin/SettingsPage.js +2 -6
  24. package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -1
  25. package/dist/components/DataClubAIHubAdmin/index.d.ts +1 -1
  26. package/dist/components/DataClubAIHubAdmin/index.js +1 -1
  27. package/dist/util/relativeTime.d.ts +11 -0
  28. package/dist/util/relativeTime.d.ts.map +1 -1
  29. package/dist/util/relativeTime.js +19 -0
  30. package/dist/util/relativeTime.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/auth/locales/cs.ts +1 -6
  33. package/src/auth/locales/sk.ts +1 -6
  34. package/src/auth/texts.ts +3 -15
  35. package/src/components/ChatShell/ShellHeader.tsx +4 -4
  36. package/src/components/ChatShell/UserMenu.tsx +7 -27
  37. package/src/components/DataClubAIHubAdmin/AgentsPage.tsx +1 -21
  38. package/src/components/DataClubAIHubAdmin/RotatingMessagesPage.tsx +8 -1
  39. package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +1 -21
  40. package/src/components/DataClubAIHubAdmin/index.tsx +1 -1
  41. package/src/styles/CLASSES.md +1 -1
  42. package/src/styles/default.css +0 -4
  43. package/src/util/relativeTime.ts +19 -0
@@ -4,22 +4,16 @@
4
4
  *
5
5
  * Step 9 ships the minimal version: avatar circle + username, click
6
6
  * opens a dropdown with the role label and a `Sign out` item. Step 10
7
- * adds the `Theme: Light ↔ Dark` item. **Step 11 adds the `Admin` and
8
- * `Open Letta ADE ↗` items**, both gated to `role >= power_user` per
9
- * §8.3 / [00-shell.md]. Step 12 wires the voluntary `Change Password`
10
- * item via the shell's shell-aware framing.
11
- *
12
- * `Open Letta ADE` opens the deployment's ADE in a new tab
13
- * (`target="_blank" rel="noopener noreferrer"`); the URL comes from the
14
- * `deployment.letta_ade_url` field on `/auth/me` (only present for
15
- * `power_user` and above by design — see `AIHubDeployment` JSDoc).
7
+ * adds the `Theme: Light ↔ Dark` item. **Step 11 adds the `Admin`
8
+ * item**, gated to `role >= power_user` per §8.3 / [00-shell.md].
9
+ * Step 12 wires the voluntary `Change Password` item via the shell's
10
+ * shell-aware framing.
16
11
  *
17
12
  * The right side of the shell header is **library-owned** and not
18
13
  * customizable — per-customer wrappers can theme via CSS but cannot
19
14
  * replace this component.
20
15
  *
21
16
  * @see ui-sketches/00-shell.md — user_menu_button + user_menu_dropdown
22
- * @see SPECIFICATIONS.md §8.3 — Open Letta ADE link
23
17
  */
24
18
 
25
19
  import {
@@ -37,11 +31,11 @@ import type { AIHubRole } from "../../types.js";
37
31
  import { ThemeSwitcher } from "../ThemeSwitcher/index.js";
38
32
 
39
33
  /**
40
- * Returns true when the given role can see the admin-only menu items
41
- * (`Admin` + `Open Letta ADE`). Mirrors the §8.2 matrix.
34
+ * Returns true when the given role can see the admin-only `Admin` menu
35
+ * item. Mirrors the §8.2 matrix.
42
36
  *
43
37
  * @param role - the caller's role, or `null` when not authenticated.
44
- * @returns whether the items should render.
38
+ * @returns whether the item should render.
45
39
  */
46
40
  function canSeeAdminItems(role: AIHubRole | null): boolean {
47
41
  return role === "power_user" || role === "admin" || role === "super_admin";
@@ -76,7 +70,6 @@ export function UserMenu(props: UserMenuProps): ReactNode {
76
70
  const [open, setOpen] = useState(false);
77
71
  const wrapRef = useRef<HTMLDivElement | null>(null);
78
72
  const showAdminItems = canSeeAdminItems(auth.role);
79
- const lettaAdeUrl = auth.deployment?.letta_ade_url ?? null;
80
73
 
81
74
  // Close on outside-click + Escape so the menu can't be left open
82
75
  // accidentally over the chat below.
@@ -168,19 +161,6 @@ export function UserMenu(props: UserMenuProps): ReactNode {
168
161
  ⚙ {texts.adminMenuItem}
169
162
  </button>
170
163
  ) : null}
171
- {showAdminItems && lettaAdeUrl !== null && lettaAdeUrl.length > 0 ? (
172
- <a
173
- role="menuitem"
174
- className="data-club-ai-hub__user-menu__item data-club-ai-hub__user-menu__item--link"
175
- data-testid="user-menu-open-letta-ade"
176
- href={lettaAdeUrl}
177
- target="_blank"
178
- rel="noopener noreferrer"
179
- onClick={() => setOpen(false)}
180
- >
181
- ↗ {texts.openLettaAdeMenuItem}
182
- </a>
183
- ) : null}
184
164
  {themeSwitcherEnabled ? (
185
165
  <div
186
166
  className="data-club-ai-hub__user-menu__item"
@@ -3,7 +3,7 @@
3
3
  * `<DataClubAIHubAdmin />` ([06-admin-agents.md]).
4
4
  *
5
5
  * Lists the ai-hub agents catalog with per-row `[Edit]` + `[Archive]` /
6
- * `[Unarchive]` actions, top-right `[+ Create agent]` + `[↗ Open Letta ADE]`,
6
+ * `[Unarchive]` actions, a top-right `[+ Create agent]` button,
7
7
  * and a `[☐ Show archived]` toggle. **No TPM⚙ button** — TPM-recovery was
8
8
  * removed in Step 11.5 (§6.5); the column is retained on the DB schema but
9
9
  * the UI doesn't surface it. **No 🗑 button** — agents cannot be hard-deleted
@@ -21,7 +21,6 @@ import { useEffect, useMemo, useState, type ReactNode } from "react";
21
21
  import type { AIHubAdminTexts } from "../../auth/texts.js";
22
22
  import type { AdminAgentView } from "../../client/AIHubClient.js";
23
23
  import { useAIHub } from "../../context/useAIHub.js";
24
- import { useAIHubAuth } from "../../context/useAIHubAuth.js";
25
24
 
26
25
  import {
27
26
  AgentEditorModal,
@@ -35,12 +34,6 @@ type EditorState =
35
34
  | { kind: "create" }
36
35
  | { kind: "edit"; agent: AdminAgentView };
37
36
 
38
- /** Resolves the deployment's ADE URL or derives one from the base URL. */
39
- function deriveAdeUrl(adeUrl: string | undefined): string | null {
40
- if (!adeUrl) return null;
41
- return adeUrl.replace(/\/$/, "");
42
- }
43
-
44
37
  /** Sort comparator — alphabetical by display_name, stable. */
45
38
  function byDisplayName(a: AdminAgentView, b: AdminAgentView): number {
46
39
  return a.display_name.localeCompare(b.display_name);
@@ -60,8 +53,6 @@ export interface AdminAgentsPageProps {
60
53
  */
61
54
  export function AdminAgentsPage(props: AdminAgentsPageProps): ReactNode {
62
55
  const { client } = useAIHub();
63
- const auth = useAIHubAuth();
64
- const adeUrl = deriveAdeUrl(auth.deployment?.letta_ade_url);
65
56
 
66
57
  const [agents, setAgents] = useState<AdminAgentView[] | null>(null);
67
58
  const [loadError, setLoadError] = useState<string | null>(null);
@@ -171,17 +162,6 @@ export function AdminAgentsPage(props: AdminAgentsPageProps): ReactNode {
171
162
  {props.texts.agentsHeading}
172
163
  </h1>
173
164
  <div className="data-club-ai-hub-admin__page-header-actions">
174
- {adeUrl !== null ? (
175
- <a
176
- className="data-club-ai-hub-admin__button"
177
- data-testid="admin-agents-open-ade"
178
- href={adeUrl}
179
- target="_blank"
180
- rel="noopener noreferrer"
181
- >
182
- {props.texts.openLettaAdeButton}
183
- </a>
184
- ) : null}
185
165
  <button
186
166
  type="button"
187
167
  className="data-club-ai-hub-admin__button data-club-ai-hub-admin__button--primary"
@@ -17,6 +17,7 @@ import { useEffect, useMemo, useState, type ReactNode } from "react";
17
17
  import type { AIHubAdminTexts } from "../../auth/texts.js";
18
18
  import type { AdminRotatingMessageView } from "../../client/AIHubClient.js";
19
19
  import { useAIHub } from "../../context/useAIHub.js";
20
+ import { formatDateTime } from "../../util/relativeTime.js";
20
21
 
21
22
  import {
22
23
  RotatingMessageEditorModal,
@@ -80,7 +81,7 @@ export interface AdminRotatingMessagesPageProps {
80
81
  export function AdminRotatingMessagesPage(
81
82
  props: AdminRotatingMessagesPageProps,
82
83
  ): ReactNode {
83
- const { client } = useAIHub();
84
+ const { client, language } = useAIHub();
84
85
 
85
86
  const [rows, setRows] = useState<AdminRotatingMessageView[] | null>(null);
86
87
  const [loadError, setLoadError] = useState<string | null>(null);
@@ -246,6 +247,7 @@ export function AdminRotatingMessagesPage(
246
247
  <th>{props.texts.rotatingColumnAgent}</th>
247
248
  <th>{props.texts.rotatingColumnSchedule}</th>
248
249
  <th>{props.texts.rotatingColumnStatus}</th>
250
+ <th>{props.texts.rotatingColumnLastRotated}</th>
249
251
  <th>{props.texts.rotatingColumnId}</th>
250
252
  <th>{props.texts.rotatingColumnActions}</th>
251
253
  </tr>
@@ -281,6 +283,11 @@ export function AdminRotatingMessagesPage(
281
283
  {statusLabel(r, props.texts)}
282
284
  </span>
283
285
  </td>
286
+ <td>
287
+ {r.last_rotated_at
288
+ ? formatDateTime(r.last_rotated_at, language)
289
+ : "—"}
290
+ </td>
284
291
  <td>
285
292
  <button
286
293
  type="button"
@@ -245,7 +245,6 @@ interface LettaConnectionSectionProps extends SectionProps {
245
245
  function LettaConnectionSection(props: LettaConnectionSectionProps): ReactNode {
246
246
  const { client } = useAIHub();
247
247
  const [baseUrl, setBaseUrl] = useState(props.settings.letta_base_url ?? "");
248
- const [adeUrl, setAdeUrl] = useState(props.settings.letta_ade_url ?? "");
249
248
  // Empty string = leave-as-is; non-empty = replace.
250
249
  const [apiKey, setApiKey] = useState("");
251
250
  const [saving, setSaving] = useState(false);
@@ -265,9 +264,6 @@ function LettaConnectionSection(props: LettaConnectionSectionProps): ReactNode {
265
264
  if (baseUrl !== (props.settings.letta_base_url ?? "")) {
266
265
  body.lettaBaseUrl = baseUrl === "" ? null : baseUrl;
267
266
  }
268
- if (adeUrl !== (props.settings.letta_ade_url ?? "")) {
269
- body.lettaAdeUrl = adeUrl === "" ? null : adeUrl;
270
- }
271
267
  if (apiKey !== "") body.lettaApiKey = apiKey;
272
268
  const next = await client.patchAdminSettings(body);
273
269
  props.onSaved(next);
@@ -289,7 +285,7 @@ function LettaConnectionSection(props: LettaConnectionSectionProps): ReactNode {
289
285
  // Clears the DB column (sends `letta_api_key: ""`), which makes the
290
286
  // server fall back to env.LETTA_API_KEY at runtime per the
291
287
  // resolveApiKey precedence in letta/client.ts. Same code path as the
292
- // base/ADE URL nulling — only it targets the API-key column.
288
+ // base URL nulling — only it targets the API-key column.
293
289
  async function handleClearApiKeyOverride(): Promise<void> {
294
290
  setSaving(true);
295
291
  setFlash(null);
@@ -332,22 +328,6 @@ function LettaConnectionSection(props: LettaConnectionSectionProps): ReactNode {
332
328
  disabled={saving}
333
329
  />
334
330
  </label>
335
- <label className="data-club-ai-hub-admin__form-field">
336
- <span className="data-club-ai-hub-admin__form-label">
337
- {props.texts.settingsLettaAdeUrlLabel}
338
- </span>
339
- <input
340
- type="url"
341
- className="data-club-ai-hub-admin__form-input"
342
- data-testid="admin-settings-letta-ade-url"
343
- value={adeUrl}
344
- onChange={(event) => setAdeUrl(event.target.value)}
345
- disabled={saving}
346
- />
347
- <span className="data-club-ai-hub-admin__form-helper">
348
- {props.texts.settingsLettaAdeUrlHelper}
349
- </span>
350
- </label>
351
331
  <label className="data-club-ai-hub-admin__form-field">
352
332
  <span className="data-club-ai-hub-admin__form-label">
353
333
  {props.texts.settingsLettaApiKeyLabel}
@@ -8,7 +8,7 @@
8
8
  * `user`-role caller the component returns `null` outright.
9
9
  *
10
10
  * The shell **renders its own `<ShellHeader />`** so the user-menu (log
11
- * out, theme toggle, Open Letta ADE, ← Back to chat) stays available
11
+ * out, theme toggle, ← Back to chat) stays available
12
12
  * from inside the admin view too — [00-shell.md] makes the shell header
13
13
  * part of every authenticated page. The component is identical to the
14
14
  * one ChatShell renders.
@@ -177,7 +177,7 @@ block and modifier.
177
177
  | `data-club-ai-hub__user-menu__header` | Header row inside the dropdown (username + role). |
178
178
  | `data-club-ai-hub__user-menu__username-large` | Larger username text in the dropdown header. |
179
179
  | `data-club-ai-hub__user-menu__role` | Role label below the username. |
180
- | `data-club-ai-hub__user-menu__item` | A menu item (Sign out today; Theme / Admin / Letta ADE in Steps 10–11). |
180
+ | `data-club-ai-hub__user-menu__item` | A menu item (Sign out today; Theme / Admin in Steps 10–11). |
181
181
 
182
182
  ### Agents picker — Step 9 (`ui-sketches/03-chat.md`)
183
183
 
@@ -660,10 +660,6 @@
660
660
  background-color: color-mix(in srgb, var(--dc-aih-accent) 12%, transparent);
661
661
  }
662
662
 
663
- .data-club-ai-hub__user-menu__item--link {
664
- text-decoration: none;
665
- }
666
-
667
663
  /* ── Language switcher (left of the user menu in the shell header) ────── */
668
664
 
669
665
  .data-club-ai-hub__language-switcher {
@@ -69,6 +69,25 @@ export function formatAbsoluteDate(
69
69
  );
70
70
  }
71
71
 
72
+ /**
73
+ * Formats an ISO timestamp as a short, locale-formatted **date + time**
74
+ * (e.g. `08/06/2026, 14:32`). Used where the exact moment matters — e.g. the
75
+ * admin rotating-messages "last rotation" column, where rotations can be
76
+ * hourly so a date alone wouldn't distinguish runs.
77
+ *
78
+ * @param iso - ISO timestamp. Returns the raw input for an unparseable value.
79
+ * @param language - the active UI language.
80
+ * @returns the localized date-time string.
81
+ */
82
+ export function formatDateTime(iso: string, language: AIHubLanguage): string {
83
+ const ts = Date.parse(iso);
84
+ if (Number.isNaN(ts)) return iso;
85
+ return new Intl.DateTimeFormat(language, {
86
+ dateStyle: "short",
87
+ timeStyle: "short",
88
+ }).format(new Date(ts));
89
+ }
90
+
72
91
  /**
73
92
  * Day-granular relative label — `"today"` / `"yesterday"` / `"N days ago"`
74
93
  * (localized). Used for the admin settings rotation stamp, which pairs an