@data-club/ai-hub 0.0.3 → 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.
- package/dist/auth/locales/cs.d.ts.map +1 -1
- package/dist/auth/locales/cs.js +17 -0
- package/dist/auth/locales/cs.js.map +1 -1
- package/dist/auth/locales/sk.d.ts.map +1 -1
- package/dist/auth/locales/sk.js +17 -0
- package/dist/auth/locales/sk.js.map +1 -1
- package/dist/auth/texts.d.ts +26 -0
- package/dist/auth/texts.d.ts.map +1 -1
- package/dist/auth/texts.js +17 -0
- package/dist/auth/texts.js.map +1 -1
- package/dist/client/AIHubClient.d.ts +13 -0
- package/dist/client/AIHubClient.d.ts.map +1 -1
- package/dist/client/AIHubClient.js +10 -0
- package/dist/client/AIHubClient.js.map +1 -1
- package/dist/components/ChatShell/index.d.ts.map +1 -1
- package/dist/components/ChatShell/index.js +35 -2
- package/dist/components/ChatShell/index.js.map +1 -1
- package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -1
- package/dist/components/DataClubAIHubAdmin/SettingsPage.js +56 -1
- package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -1
- package/dist/components/TurnStatusIndicator/index.d.ts.map +1 -1
- package/dist/components/TurnStatusIndicator/index.js +2 -1
- package/dist/components/TurnStatusIndicator/index.js.map +1 -1
- package/dist/state/affordances.d.ts +13 -1
- package/dist/state/affordances.d.ts.map +1 -1
- package/dist/state/affordances.js +11 -9
- package/dist/state/affordances.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/auth/locales/cs.ts +19 -0
- package/src/auth/locales/sk.ts +19 -0
- package/src/auth/texts.ts +49 -0
- package/src/client/AIHubClient.ts +23 -0
- package/src/components/ChatShell/index.tsx +34 -2
- package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +145 -1
- package/src/components/TurnStatusIndicator/index.tsx +2 -1
- package/src/state/affordances.ts +34 -10
- package/src/types.ts +11 -0
package/src/auth/texts.ts
CHANGED
|
@@ -304,6 +304,26 @@ export interface AIHubChatTexts {
|
|
|
304
304
|
/* ── Language switcher (header, beside the user avatar) ─────────── */
|
|
305
305
|
/** `aria-label` / tooltip on the language-switcher button. */
|
|
306
306
|
languageSwitcherAriaLabel: string;
|
|
307
|
+
|
|
308
|
+
/* ── Turn-status indicator (§10.4 — `●` dot + label) ───────────── */
|
|
309
|
+
/** Idle / ready (no turn in flight). */
|
|
310
|
+
turnStatusReady: string;
|
|
311
|
+
/** `running` + `model_text` (or unknown phase) — assistant text streaming. */
|
|
312
|
+
turnStatusGenerating: string;
|
|
313
|
+
/** `running` + `model_reasoning`. */
|
|
314
|
+
turnStatusReasoning: string;
|
|
315
|
+
/** `running` + `model_tool_input` — assembling a tool call. */
|
|
316
|
+
turnStatusPreparingTool: string;
|
|
317
|
+
/** `running` + `tool_executing`. */
|
|
318
|
+
turnStatusRunningTool: string;
|
|
319
|
+
/** `running` + `waiting_provider` — rate-limited / recovering. */
|
|
320
|
+
turnStatusWaitingRateLimit: string;
|
|
321
|
+
/** Terminal `failed`. */
|
|
322
|
+
turnStatusFailed: string;
|
|
323
|
+
/** Terminal `cancelled` (before the auto-clear to ready). */
|
|
324
|
+
turnStatusCancelled: string;
|
|
325
|
+
/** Terminal `done` (the brief flash before settling to ready). */
|
|
326
|
+
turnStatusDone: string;
|
|
307
327
|
}
|
|
308
328
|
|
|
309
329
|
/**
|
|
@@ -794,6 +814,16 @@ export interface AIHubAdminTexts {
|
|
|
794
814
|
settingsLanguageLockedHelper: string;
|
|
795
815
|
settingsSaveLanguageButton: string;
|
|
796
816
|
|
|
817
|
+
/* Agents section. */
|
|
818
|
+
settingsAgentsSectionHeading: string;
|
|
819
|
+
settingsAgentsDefaultLabel: string;
|
|
820
|
+
/** First `<option>` — no default agent (show the picker). */
|
|
821
|
+
settingsAgentsDefaultNone: string;
|
|
822
|
+
settingsAgentsDefaultHelper: string;
|
|
823
|
+
settingsAgentsSwitcherEnabledLabel: string;
|
|
824
|
+
settingsAgentsSwitcherEnabledHelper: string;
|
|
825
|
+
settingsSaveAgentsButton: string;
|
|
826
|
+
|
|
797
827
|
/* Feature toggles section. */
|
|
798
828
|
settingsTogglesSectionHeading: string;
|
|
799
829
|
settingsTogglesGroupSessions: string;
|
|
@@ -973,6 +1003,16 @@ export const defaultAIHubTexts: AIHubTexts = Object.freeze({
|
|
|
973
1003
|
openLettaAdeMenuItem: "Open Letta ADE",
|
|
974
1004
|
/* Language switcher. */
|
|
975
1005
|
languageSwitcherAriaLabel: "Change language",
|
|
1006
|
+
/* Turn-status indicator. */
|
|
1007
|
+
turnStatusReady: "Ready",
|
|
1008
|
+
turnStatusGenerating: "Generating…",
|
|
1009
|
+
turnStatusReasoning: "Reasoning…",
|
|
1010
|
+
turnStatusPreparingTool: "Preparing tool call…",
|
|
1011
|
+
turnStatusRunningTool: "Running tool…",
|
|
1012
|
+
turnStatusWaitingRateLimit: "Waiting (rate limit)…",
|
|
1013
|
+
turnStatusFailed: "Failed",
|
|
1014
|
+
turnStatusCancelled: "Cancelled",
|
|
1015
|
+
turnStatusDone: "Done",
|
|
976
1016
|
}),
|
|
977
1017
|
admin: Object.freeze({
|
|
978
1018
|
/* Shell. */
|
|
@@ -1250,6 +1290,15 @@ export const defaultAIHubTexts: AIHubTexts = Object.freeze({
|
|
|
1250
1290
|
settingsLanguageLockedHelper:
|
|
1251
1291
|
"When on, the language switcher is hidden and every user sees the default language. When off, users can pick their own language and their choice is remembered on their device.",
|
|
1252
1292
|
settingsSaveLanguageButton: "Save language",
|
|
1293
|
+
settingsAgentsSectionHeading: "Agents",
|
|
1294
|
+
settingsAgentsDefaultLabel: "Default agent",
|
|
1295
|
+
settingsAgentsDefaultNone: "No default — show the agent list",
|
|
1296
|
+
settingsAgentsDefaultHelper:
|
|
1297
|
+
"When set, users open straight into this agent instead of the picker. A deployment with a single agent always opens that one regardless of this setting.",
|
|
1298
|
+
settingsAgentsSwitcherEnabledLabel: "Allow users to switch agents",
|
|
1299
|
+
settingsAgentsSwitcherEnabledHelper:
|
|
1300
|
+
"When off, the “← Agents” back link is hidden so users stay on their agent. It is also hidden whenever only one agent exists.",
|
|
1301
|
+
settingsSaveAgentsButton: "Save agents",
|
|
1253
1302
|
settingsTogglesSectionHeading: "UI feature toggles",
|
|
1254
1303
|
settingsTogglesGroupSessions: "Sessions panel",
|
|
1255
1304
|
settingsTogglesGroupInput: "Input",
|
|
@@ -583,6 +583,13 @@ export interface AdminSettingsView {
|
|
|
583
583
|
default: AIHubLanguage;
|
|
584
584
|
user_locked: boolean;
|
|
585
585
|
};
|
|
586
|
+
/** Agent-picker settings, stored as separate columns server-side. */
|
|
587
|
+
agents: {
|
|
588
|
+
/** Default ai-hub agent id auto-opened on load, or `null` for the picker. */
|
|
589
|
+
default_id: string | null;
|
|
590
|
+
/** Whether users may switch agents (the "← Agents" back link). */
|
|
591
|
+
switcher_enabled: boolean;
|
|
592
|
+
};
|
|
586
593
|
/** The flat §8.4 feature-toggles blob. */
|
|
587
594
|
feature_toggles: AIHubFeatureToggles;
|
|
588
595
|
/** ISO-8601 of the last signing-key rotation. `null` until first rotation. */
|
|
@@ -611,6 +618,12 @@ export interface PatchAdminSettingsBody {
|
|
|
611
618
|
default?: AIHubLanguage;
|
|
612
619
|
userLocked?: boolean;
|
|
613
620
|
};
|
|
621
|
+
/** Agent-picker partial — either or both fields. */
|
|
622
|
+
agents?: {
|
|
623
|
+
/** Agent id to default to; `null` / `""` clears (→ show the picker). */
|
|
624
|
+
defaultId?: string | null;
|
|
625
|
+
switcherEnabled?: boolean;
|
|
626
|
+
};
|
|
614
627
|
}
|
|
615
628
|
|
|
616
629
|
/** Response from `rotateAdminMetricsToken` — the new token returned **once**. */
|
|
@@ -1505,6 +1518,16 @@ export class AIHubClient {
|
|
|
1505
1518
|
}
|
|
1506
1519
|
wire.language = languageWire;
|
|
1507
1520
|
}
|
|
1521
|
+
if (body.agents !== undefined) {
|
|
1522
|
+
const agentsWire: Record<string, unknown> = {};
|
|
1523
|
+
if (body.agents.defaultId !== undefined) {
|
|
1524
|
+
agentsWire.default_id = body.agents.defaultId;
|
|
1525
|
+
}
|
|
1526
|
+
if (body.agents.switcherEnabled !== undefined) {
|
|
1527
|
+
agentsWire.switcher_enabled = body.agents.switcherEnabled;
|
|
1528
|
+
}
|
|
1529
|
+
wire.agents = agentsWire;
|
|
1530
|
+
}
|
|
1508
1531
|
return this.requestJson<AdminSettingsView>("PATCH", "/admin/settings", {
|
|
1509
1532
|
body: wire,
|
|
1510
1533
|
});
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @see ui-sketches/00-shell.md, ui-sketches/03-chat.md
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { useEffect, useMemo, type ReactNode } from "react";
|
|
16
|
+
import { useEffect, useMemo, useRef, type ReactNode } from "react";
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
type AIHubTexts,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "../../auth/texts.js";
|
|
22
22
|
import { useResolvedTexts } from "../../context/useAIHubTexts.js";
|
|
23
23
|
import { useAIHub } from "../../context/useAIHub.js";
|
|
24
|
+
import { useAIHubAuth } from "../../context/useAIHubAuth.js";
|
|
24
25
|
import {
|
|
25
26
|
resolveAffordanceFlags,
|
|
26
27
|
type AIHubAffordanceFlags,
|
|
@@ -171,9 +172,13 @@ export function ChatShell(props: ChatShellProps): ReactNode {
|
|
|
171
172
|
setActiveSession,
|
|
172
173
|
featureToggles,
|
|
173
174
|
} = useAIHub();
|
|
175
|
+
const { deployment } = useAIHubAuth();
|
|
174
176
|
const texts = useResolvedTexts(props.texts);
|
|
175
177
|
const productName = props.productName ?? "ai-hub";
|
|
176
178
|
const showAgentsPicker = props.showAgentsPicker ?? true;
|
|
179
|
+
// §7.x agent-picker deployment config: when switching is off the "← Agents"
|
|
180
|
+
// back link is hidden; a single-agent deployment hides it too (see below).
|
|
181
|
+
const agentSwitcherEnabled = deployment?.agent_switcher_enabled ?? true;
|
|
177
182
|
const sessionsPanelPosition = props.sessionsPanelPosition ?? "left";
|
|
178
183
|
const sessionsPanelWidthPercent = props.sessionsPanelWidthPercent ?? 25;
|
|
179
184
|
|
|
@@ -214,6 +219,31 @@ export function ChatShell(props: ChatShellProps): ReactNode {
|
|
|
214
219
|
}
|
|
215
220
|
}, [autoSelectOnlyAgent, agents, setActiveAgent]);
|
|
216
221
|
|
|
222
|
+
// Deployment default agent (multi-agent deployments): open it once on first
|
|
223
|
+
// agents-load instead of showing the picker. A deep-link `initialAgentId`
|
|
224
|
+
// wins (handled by its own effect above). Guarded by a ref so that, when
|
|
225
|
+
// switching is enabled, clicking "← Agents" genuinely returns to the picker
|
|
226
|
+
// rather than snapping straight back to the default.
|
|
227
|
+
const appliedDefaultAgentRef = useRef(false);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (appliedDefaultAgentRef.current) return;
|
|
230
|
+
if (props.initialAgentId) return;
|
|
231
|
+
if (agentsLoading || agents.length <= 1) return;
|
|
232
|
+
appliedDefaultAgentRef.current = true;
|
|
233
|
+
if (activeAgentId) return;
|
|
234
|
+
const defaultId = deployment?.default_agent_id ?? null;
|
|
235
|
+
if (defaultId && agents.some((a) => a.id === defaultId)) {
|
|
236
|
+
setActiveAgent(defaultId);
|
|
237
|
+
}
|
|
238
|
+
}, [
|
|
239
|
+
agents,
|
|
240
|
+
agentsLoading,
|
|
241
|
+
activeAgentId,
|
|
242
|
+
props.initialAgentId,
|
|
243
|
+
deployment,
|
|
244
|
+
setActiveAgent,
|
|
245
|
+
]);
|
|
246
|
+
|
|
217
247
|
const inPicker = showAgentsPicker && !activeAgentId;
|
|
218
248
|
|
|
219
249
|
const rootClass = props.className
|
|
@@ -259,7 +289,9 @@ export function ChatShell(props: ChatShellProps): ReactNode {
|
|
|
259
289
|
/>
|
|
260
290
|
) : null}
|
|
261
291
|
<MainColumn
|
|
262
|
-
showBackToAgents={
|
|
292
|
+
showBackToAgents={
|
|
293
|
+
showAgentsPicker && agentSwitcherEnabled && agents.length > 1
|
|
294
|
+
}
|
|
263
295
|
showTurnStatus={flags.turnStatusVisible}
|
|
264
296
|
{...(props.texts !== undefined ? { texts: props.texts } : {})}
|
|
265
297
|
{...(props.customHeaderContent !== undefined
|
|
@@ -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 {
|
|
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
|
/* ────────────────────────────────────────────────────────────────────────── */
|
|
@@ -173,6 +173,7 @@ export function TurnStatusIndicator(
|
|
|
173
173
|
}, [state.status, state.phase]);
|
|
174
174
|
|
|
175
175
|
const visual = turnStatusVisual(effective.status, effective.phase);
|
|
176
|
+
const label = ctx.texts.chat[visual.labelKey];
|
|
176
177
|
const rootClass = props.className
|
|
177
178
|
? `${cls.root} ${props.className}`
|
|
178
179
|
: cls.root;
|
|
@@ -196,7 +197,7 @@ export function TurnStatusIndicator(
|
|
|
196
197
|
>
|
|
197
198
|
<span className={dotClass} aria-hidden="true" />
|
|
198
199
|
<span className={cls.label} data-testid="turn-status-label">
|
|
199
|
-
{
|
|
200
|
+
{label}
|
|
200
201
|
</span>
|
|
201
202
|
</div>
|
|
202
203
|
);
|
package/src/state/affordances.ts
CHANGED
|
@@ -254,14 +254,36 @@ export function chatAffordances(state: AIHubState): AIHubChatAffordances {
|
|
|
254
254
|
* `useEffect` + a short timer.
|
|
255
255
|
*/
|
|
256
256
|
export interface AIHubTurnStatusVisual {
|
|
257
|
-
/**
|
|
257
|
+
/**
|
|
258
|
+
* Short human-readable label rendered next to the dot. **English** — this
|
|
259
|
+
* is the fallback for consumers that read `turnStatusVisual` directly. The
|
|
260
|
+
* `<TurnStatusIndicator />` primitive renders the localized
|
|
261
|
+
* `texts.chat[labelKey]` instead.
|
|
262
|
+
*/
|
|
258
263
|
label: string;
|
|
264
|
+
/**
|
|
265
|
+
* The `AIHubChatTexts` key for the localized label. Each value must name an
|
|
266
|
+
* existing `turnStatus*` key in the chat texts.
|
|
267
|
+
*/
|
|
268
|
+
labelKey: TurnStatusLabelKey;
|
|
259
269
|
/** Dot variant — drives the BEM modifier on the dot element. */
|
|
260
270
|
dot: "idle" | "running" | "warning" | "error";
|
|
261
271
|
/** Whether the dot pulses (suppressed under `prefers-reduced-motion`). */
|
|
262
272
|
pulse: boolean;
|
|
263
273
|
}
|
|
264
274
|
|
|
275
|
+
/** The `turnStatus*` keys in `AIHubChatTexts` a status can resolve to. */
|
|
276
|
+
export type TurnStatusLabelKey =
|
|
277
|
+
| "turnStatusReady"
|
|
278
|
+
| "turnStatusGenerating"
|
|
279
|
+
| "turnStatusReasoning"
|
|
280
|
+
| "turnStatusPreparingTool"
|
|
281
|
+
| "turnStatusRunningTool"
|
|
282
|
+
| "turnStatusWaitingRateLimit"
|
|
283
|
+
| "turnStatusFailed"
|
|
284
|
+
| "turnStatusCancelled"
|
|
285
|
+
| "turnStatusDone";
|
|
286
|
+
|
|
265
287
|
/**
|
|
266
288
|
* Computes the §10.4 turn-status indicator's `(label, dot, pulse)` from
|
|
267
289
|
* a `(status, phase)` pair.
|
|
@@ -281,20 +303,22 @@ export function turnStatusVisual(
|
|
|
281
303
|
if (status === "running") {
|
|
282
304
|
switch (phase) {
|
|
283
305
|
case "model_text":
|
|
284
|
-
return { label: "Generating…", dot: "running", pulse: true };
|
|
306
|
+
return { label: "Generating…", labelKey: "turnStatusGenerating", dot: "running", pulse: true };
|
|
285
307
|
case "model_reasoning":
|
|
286
|
-
return { label: "Reasoning…", dot: "running", pulse: true };
|
|
308
|
+
return { label: "Reasoning…", labelKey: "turnStatusReasoning", dot: "running", pulse: true };
|
|
287
309
|
case "model_tool_input":
|
|
288
310
|
return {
|
|
289
311
|
label: "Preparing tool call…",
|
|
312
|
+
labelKey: "turnStatusPreparingTool",
|
|
290
313
|
dot: "running",
|
|
291
314
|
pulse: true,
|
|
292
315
|
};
|
|
293
316
|
case "tool_executing":
|
|
294
|
-
return { label: "Running tool…", dot: "running", pulse: true };
|
|
317
|
+
return { label: "Running tool…", labelKey: "turnStatusRunningTool", dot: "running", pulse: true };
|
|
295
318
|
case "waiting_provider":
|
|
296
319
|
return {
|
|
297
320
|
label: "Waiting (rate limit)…",
|
|
321
|
+
labelKey: "turnStatusWaitingRateLimit",
|
|
298
322
|
dot: "warning",
|
|
299
323
|
pulse: true,
|
|
300
324
|
};
|
|
@@ -302,20 +326,20 @@ export function turnStatusVisual(
|
|
|
302
326
|
// Not exercised by the predicate table (the spec doesn't surface
|
|
303
327
|
// a label for awaiting_user); fall back to "Ready" so the dot
|
|
304
328
|
// doesn't strobe.
|
|
305
|
-
return { label: "Ready", dot: "idle", pulse: false };
|
|
329
|
+
return { label: "Ready", labelKey: "turnStatusReady", dot: "idle", pulse: false };
|
|
306
330
|
case null:
|
|
307
|
-
return { label: "Generating…", dot: "running", pulse: true };
|
|
331
|
+
return { label: "Generating…", labelKey: "turnStatusGenerating", dot: "running", pulse: true };
|
|
308
332
|
}
|
|
309
333
|
}
|
|
310
334
|
if (status === "failed") {
|
|
311
|
-
return { label: "Failed", dot: "error", pulse: false };
|
|
335
|
+
return { label: "Failed", labelKey: "turnStatusFailed", dot: "error", pulse: false };
|
|
312
336
|
}
|
|
313
337
|
if (status === "cancelled") {
|
|
314
|
-
return { label: "Cancelled", dot: "idle", pulse: false };
|
|
338
|
+
return { label: "Cancelled", labelKey: "turnStatusCancelled", dot: "idle", pulse: false };
|
|
315
339
|
}
|
|
316
340
|
if (status === "done") {
|
|
317
|
-
return { label: "Done", dot: "idle", pulse: false };
|
|
341
|
+
return { label: "Done", labelKey: "turnStatusDone", dot: "idle", pulse: false };
|
|
318
342
|
}
|
|
319
343
|
// idle
|
|
320
|
-
return { label: "Ready", dot: "idle", pulse: false };
|
|
344
|
+
return { label: "Ready", labelKey: "turnStatusReady", dot: "idle", pulse: false };
|
|
321
345
|
}
|
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.
|