diff --git a/apps/docs/docs/management/assistant.mdx b/apps/docs/docs/management/assistant.mdx
index d52bdcbbf..363d9e5f4 100644
--- a/apps/docs/docs/management/assistant.mdx
+++ b/apps/docs/docs/management/assistant.mdx
@@ -46,20 +46,18 @@ Homarr includes presets for OpenRouter, OpenAI, Anthropic Claude, Google Gemini,
DeepSeek, Together AI, Ollama, and LM Studio. Provider icons make the presets easy to identify. A custom
preset connects any other service that implements the OpenAI Chat Completions API.
-The provider list also shows **Homarr — Coming soon** as a disabled option. The future service will be a
-Homarr-hosted OpenRouter proxy with the same model discovery, tool-calling, and server-tool features. Keeping
-it visible reserves a clear place for that service without making an unavailable backend selectable today.
+The provider list also shows **Homarr — Coming soon** as a disabled option.
The management page presents setup as three compact sections: provider and endpoint, credentials, then
model and availability. A status summary at the top shows whether the connection is ready, whether a key
-is protected, which model is selected, and whether users can open the assistant.
+is protected, and which model is selected. Redundant active-state badges are omitted when the setup is ready.
1. Choose a provider preset or **Custom endpoint**, then confirm the API base URL. It usually ends in `/v1`.
2. Add an API key if the service requires bearer authentication. Additional authentication or routing
headers are available in the collapsed advanced section.
3. Save the provider connection.
4. Refresh the model list and select a discovered model. Homarr displays the friendly model name but
- stores and sends the provider's exact model ID.
+ stores and sends the provider's exact model ID. Context-window sizes use compact labels such as `1M`.
5. Enable the assistant and save the configuration.
The model discovery path defaults to `/models` and is appended to the API base URL. Leave it blank when
@@ -158,7 +156,8 @@ another message starts.
A compact context control beside the model selector gives a quick glance at cumulative conversation tokens and
cost. Its popover keeps the latest complete context reading while a new response starts and lists every request
turn with its model, provider, input, output, cached-input and reasoning tokens, context use, duration, speed,
-agent steps, routing details, and cost when reported. Closing and reopening the popover reads persisted
+agent steps, routing details, and cost when reported. Before usage is available, the control remains a compact
+circle instead of showing a redundant missing-data label. Closing and reopening the popover reads persisted
conversation metadata instead of resetting the display to zero.
Each assistant response places a small provider logo in the same stable action toolbar as copy, export,
diff --git a/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration-state.spec.ts b/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration-state.spec.ts
index 56b78c1f8..6af825c9e 100644
--- a/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration-state.spec.ts
+++ b/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration-state.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
-import { getAssistantConnectionState } from "./assistant-configuration-state";
+import { formatAssistantContextWindow, getAssistantConnectionState } from "./assistant-configuration-state";
describe("getAssistantConnectionState", () => {
it("marks a saved required-key provider as ready", () => {
@@ -63,3 +63,10 @@ describe("getAssistantConnectionState", () => {
});
});
});
+
+describe("formatAssistantContextWindow", () => {
+ it("uses Homarr's compact number format for large context windows", () => {
+ expect(formatAssistantContextWindow(1_048_576)).toBe("1M");
+ expect(formatAssistantContextWindow(131_072)).toBe("131k");
+ });
+});
diff --git a/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration-state.ts b/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration-state.ts
index 572410bd0..0ccea450f 100644
--- a/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration-state.ts
+++ b/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration-state.ts
@@ -1,3 +1,5 @@
+import { formatNumber } from "@homarr/common";
+
interface AssistantConnectionStateInput {
connectionConfigured: boolean;
destinationChanged: boolean;
@@ -21,3 +23,5 @@ export const getAssistantConnectionState = ({
connectionReady: !connectionPending,
};
};
+
+export const formatAssistantContextWindow = (tokens: number) => formatNumber(tokens, 0);
diff --git a/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration.tsx b/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration.tsx
index dc0c362f4..782a91df8 100644
--- a/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/assistant/_components/assistant-configuration.tsx
@@ -57,7 +57,7 @@ import { showErrorNotification, showSuccessNotification } from "@homarr/notifica
import { useScopedI18n } from "@homarr/translation/client";
import classes from "./assistant-configuration.module.css";
-import { getAssistantConnectionState } from "./assistant-configuration-state";
+import { formatAssistantContextWindow, getAssistantConnectionState } from "./assistant-configuration-state";
type HeaderEntry = { id: number; name: string; value: string };
type CredentialFlow = "idle" | "replace" | "remove";
@@ -438,9 +438,11 @@ export const AssistantConfiguration = () => {
{t("overview.keyProtected")}
)}
-
- {configuration?.enabled ? t("overview.available") : t("overview.disabled")}
-
+ {configuration?.enabled === false && (
+
+ {t("overview.disabled")}
+
+ )}
@@ -473,7 +475,7 @@ export const AssistantConfiguration = () => {
allowDeselect={false}
leftSection={}
renderOption={({ option }) => (
-
+
@@ -486,11 +488,6 @@ export const AssistantConfiguration = () => {
)}
- {option.value === "homarr" && (
-
- {t("provider.options.homarr.description")}
-
- )}
)}
@@ -518,13 +515,11 @@ export const AssistantConfiguration = () => {
title={t("credentials.title")}
description={t("credentials.description")}
status={
-
- {hasStoredApiKey
- ? t("apiKey.configuredBadge")
- : preset.requiresApiKey
- ? t("credentials.required")
- : t("credentials.optional")}
-
+ hasStoredApiKey ? undefined : (
+
+ {preset.requiresApiKey ? t("credentials.required") : t("credentials.optional")}
+
+ )
}
actions={
@@ -781,14 +776,14 @@ export const AssistantConfiguration = () => {
title={t("model.title")}
description={t("model.description")}
status={
- configuration?.modelId ? (
-
- {configuration.enabled ? t("overview.available") : t("overview.disabled")}
-
- ) : (
+ !configuration?.modelId ? (
{t("overview.noModel")}
+ ) : configuration.enabled ? undefined : (
+
+ {t("overview.disabled")}
+
)
}
actions={
@@ -867,7 +862,9 @@ export const AssistantConfiguration = () => {
{selectedModel.contextLength && (
- {t("model.context", { count: selectedModel.contextLength })}
+
+ {t("model.context", { count: formatAssistantContextWindow(selectedModel.contextLength) })}
+
)}
{
? Math.min(100, Math.round(((usage.contextUsed ?? 0) / Math.max(usage.contextLength ?? 1, 1)) * 100))
: 0;
const quickLabel = [
- hasTokenUsage ? `${usage.totalTokens.toLocaleString()} ${t("usage.tokens")}` : t("usage.notReported"),
+ hasTokenUsage ? `${usage.totalTokens.toLocaleString()} ${t("usage.tokens")}` : undefined,
hasCost ? formatCost(usage.cost) : undefined,
]
.filter(Boolean)
@@ -1548,9 +1548,10 @@ const ConversationContext = () => {
setOpened((value) => !value)}
>
{
roundCaps
sections={hasContext ? [{ value: contextPercentage, color: getContextColor(contextPercentage) }] : []}
/>
-
- {quickLabel}
-
+ {quickLabel && (
+
+ {quickLabel}
+
+ )}