fix: refine assistant controls
This commit is contained in:
1 parent
ef9c37d5ec
commit
ebd63bea74
6 files changed
+56
-35
No files matched your search
+8
-1
@@ -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");
|
||||
});
|
||||
});
|
||||
+4
@@ -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);
|
||||
+20
-23
@@ -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")}
|
||||
</Badge>
|
||||
)}
|
||||
<Badge variant="filled" color={configuration?.enabled ? "green" : "gray"}>
|
||||
{configuration?.enabled ? t("overview.available") : t("overview.disabled")}
|
||||
</Badge>
|
||||
{configuration?.enabled === false && (
|
||||
<Badge variant="filled" color="gray">
|
||||
{t("overview.disabled")}
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
</Paper>
|
||||
@@ -473,7 +475,7 @@ export const AssistantConfiguration = () => {
|
||||
allowDeselect={false}
|
||||
leftSection={<ProviderIcon providerId={provider} />}
|
||||
renderOption={({ option }) => (
|
||||
<Group gap="sm" wrap="nowrap" align={option.value === "homarr" ? "flex-start" : "center"} w="100%">
|
||||
<Group gap="sm" wrap="nowrap" align="center" w="100%">
|
||||
<ProviderIcon providerId={option.value as AssistantProviderOption} />
|
||||
<Box flex={1} miw={0}>
|
||||
<Group gap="xs" wrap="nowrap" justify="space-between">
|
||||
@@ -486,11 +488,6 @@ export const AssistantConfiguration = () => {
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
{option.value === "homarr" && (
|
||||
<Text size="xs" c="dimmed" lineClamp={2} maw="46ch">
|
||||
{t("provider.options.homarr.description")}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</Group>
|
||||
)}
|
||||
@@ -518,13 +515,11 @@ export const AssistantConfiguration = () => {
|
||||
title={t("credentials.title")}
|
||||
description={t("credentials.description")}
|
||||
status={
|
||||
<Badge variant="light" color={hasStoredApiKey ? "green" : preset.requiresApiKey ? "yellow" : "gray"}>
|
||||
{hasStoredApiKey
|
||||
? t("apiKey.configuredBadge")
|
||||
: preset.requiresApiKey
|
||||
? t("credentials.required")
|
||||
: t("credentials.optional")}
|
||||
</Badge>
|
||||
hasStoredApiKey ? undefined : (
|
||||
<Badge variant="light" color={preset.requiresApiKey ? "yellow" : "gray"}>
|
||||
{preset.requiresApiKey ? t("credentials.required") : t("credentials.optional")}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
<Group justify="space-between">
|
||||
@@ -781,14 +776,14 @@ export const AssistantConfiguration = () => {
|
||||
title={t("model.title")}
|
||||
description={t("model.description")}
|
||||
status={
|
||||
configuration?.modelId ? (
|
||||
<Badge variant="light" color={configuration.enabled ? "green" : "gray"}>
|
||||
{configuration.enabled ? t("overview.available") : t("overview.disabled")}
|
||||
</Badge>
|
||||
) : (
|
||||
!configuration?.modelId ? (
|
||||
<Badge variant="light" color="gray">
|
||||
{t("overview.noModel")}
|
||||
</Badge>
|
||||
) : configuration.enabled ? undefined : (
|
||||
<Badge variant="light" color="gray">
|
||||
{t("overview.disabled")}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
@@ -867,7 +862,9 @@ export const AssistantConfiguration = () => {
|
||||
</Box>
|
||||
<Group className={classes.selectedModelBadges} gap="xs" justify="flex-end">
|
||||
{selectedModel.contextLength && (
|
||||
<Badge variant="light">{t("model.context", { count: selectedModel.contextLength })}</Badge>
|
||||
<Badge variant="light">
|
||||
{t("model.context", { count: formatAssistantContextWindow(selectedModel.contextLength) })}
|
||||
</Badge>
|
||||
)}
|
||||
<Badge
|
||||
variant="light"
|
||||
|
||||
@@ -1157,6 +1157,13 @@
|
||||
background: var(--mantine-color-default-hover);
|
||||
}
|
||||
|
||||
.composerContext[data-compact] {
|
||||
width: 1.75rem;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.composerContext:focus-visible {
|
||||
outline: 2px solid var(--mantine-primary-color-filled);
|
||||
outline-offset: 2px;
|
||||
@@ -1223,6 +1230,10 @@
|
||||
background: var(--mantine-color-body);
|
||||
}
|
||||
|
||||
.composer:has(.composerInput:focus-within) {
|
||||
box-shadow: inset 0 0 0 2px var(--mantine-color-red-filled);
|
||||
}
|
||||
|
||||
.composerDropzone[data-dragging] .composer {
|
||||
box-shadow: inset 0 0 0 2px var(--mantine-primary-color-filled);
|
||||
}
|
||||
|
||||
@@ -1527,7 +1527,7 @@ const ConversationContext = () => {
|
||||
? 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 = () => {
|
||||
<UnstyledButton
|
||||
className={classes.composerContext}
|
||||
type="button"
|
||||
aria-label={`${t("usage.contextWindow")}: ${quickLabel}`}
|
||||
aria-label={quickLabel ? `${t("usage.contextWindow")}: ${quickLabel}` : t("usage.contextWindow")}
|
||||
aria-expanded={opened}
|
||||
aria-haspopup="dialog"
|
||||
data-compact={!quickLabel || undefined}
|
||||
onClick={() => setOpened((value) => !value)}
|
||||
>
|
||||
<RingProgress
|
||||
@@ -1559,9 +1560,11 @@ const ConversationContext = () => {
|
||||
roundCaps
|
||||
sections={hasContext ? [{ value: contextPercentage, color: getContextColor(contextPercentage) }] : []}
|
||||
/>
|
||||
<Text size="xs" fw={650} truncate>
|
||||
{quickLabel}
|
||||
</Text>
|
||||
{quickLabel && (
|
||||
<Text size="xs" fw={650} truncate>
|
||||
{quickLabel}
|
||||
</Text>
|
||||
)}
|
||||
</UnstyledButton>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown className={classes.conversationContextPopover}>
|
||||
|
||||
Reference in new issue
Block a user