fix: improve apple intelligence ui and add reusable alert component (#517)

- hide model dropdown when apple intelligence is selected
- move error state into provider section (only shows when unavailable)
- remove redundant apple intelligence infobox
- add reusable Alert component with variants (error, warning, info, success)
- Alert supports contained prop for use inside settings groups
This commit is contained in:
Viren Mohindra 2026-01-10 09:41:10 +05:30 committed by GitHub
parent ae9c33e0c7
commit 5da495157f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 112 additions and 63 deletions

View file

@ -3,13 +3,16 @@ import { useTranslation } from "react-i18next";
import { RefreshCcw } from "lucide-react"; import { RefreshCcw } from "lucide-react";
import { commands } from "@/bindings"; import { commands } from "@/bindings";
import { SettingsGroup } from "../../ui/SettingsGroup"; import { Alert } from "../../ui/Alert";
import { SettingContainer } from "../../ui/SettingContainer"; import {
Dropdown,
SettingContainer,
SettingsGroup,
Textarea,
} from "@/components/ui";
import { Button } from "../../ui/Button"; import { Button } from "../../ui/Button";
import { ResetButton } from "../../ui/ResetButton"; import { ResetButton } from "../../ui/ResetButton";
import { Input } from "../../ui/Input"; import { Input } from "../../ui/Input";
import { Dropdown } from "../../ui/Dropdown";
import { Textarea } from "../../ui/Textarea";
import { ProviderSelect } from "../PostProcessingSettingsApi/ProviderSelect"; import { ProviderSelect } from "../PostProcessingSettingsApi/ProviderSelect";
import { BaseUrlField } from "../PostProcessingSettingsApi/BaseUrlField"; import { BaseUrlField } from "../PostProcessingSettingsApi/BaseUrlField";
@ -56,28 +59,12 @@ const PostProcessingSettingsApiComponent: React.FC = () => {
</div> </div>
</SettingContainer> </SettingContainer>
{state.appleIntelligenceUnavailable ? (
<div className="p-3 bg-red-500/10 border border-red-500/50">
<p className="text-sm text-red-500">
{t("settings.postProcessing.api.appleIntelligence.unavailable")}
</p>
</div>
) : null}
{state.isAppleProvider ? ( {state.isAppleProvider ? (
<SettingContainer state.appleIntelligenceUnavailable ? (
title={t("settings.postProcessing.api.appleIntelligence.title")} <Alert variant="error" contained>
description={t( {t("settings.postProcessing.api.appleIntelligence.unavailable")}
"settings.postProcessing.api.appleIntelligence.description", </Alert>
)} ) : null
descriptionMode="tooltip"
layout="stacked"
grouped={true}
>
<DisabledNotice>
{t("settings.postProcessing.api.appleIntelligence.requirements")}
</DisabledNotice>
</SettingContainer>
) : ( ) : (
<> <>
{state.selectedProvider?.id === "custom" && ( {state.selectedProvider?.id === "custom" && (
@ -124,51 +111,49 @@ const PostProcessingSettingsApiComponent: React.FC = () => {
</> </>
)} )}
<SettingContainer {!state.isAppleProvider && (
title={t("settings.postProcessing.api.model.title")} <SettingContainer
description={ title={t("settings.postProcessing.api.model.title")}
state.isAppleProvider description={
? t("settings.postProcessing.api.model.descriptionApple") state.isCustomProvider
: state.isCustomProvider
? t("settings.postProcessing.api.model.descriptionCustom") ? t("settings.postProcessing.api.model.descriptionCustom")
: t("settings.postProcessing.api.model.descriptionDefault") : t("settings.postProcessing.api.model.descriptionDefault")
} }
descriptionMode="tooltip" descriptionMode="tooltip"
layout="stacked" layout="stacked"
grouped={true} grouped={true}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ModelSelect <ModelSelect
value={state.model} value={state.model}
options={state.modelOptions} options={state.modelOptions}
disabled={state.isModelUpdating} disabled={state.isModelUpdating}
isLoading={state.isFetchingModels} isLoading={state.isFetchingModels}
placeholder={ placeholder={
state.isAppleProvider state.modelOptions.length > 0
? t("settings.postProcessing.api.model.placeholderApple")
: state.modelOptions.length > 0
? t( ? t(
"settings.postProcessing.api.model.placeholderWithOptions", "settings.postProcessing.api.model.placeholderWithOptions",
) )
: t("settings.postProcessing.api.model.placeholderNoOptions") : t("settings.postProcessing.api.model.placeholderNoOptions")
} }
onSelect={state.handleModelSelect} onSelect={state.handleModelSelect}
onCreate={state.handleModelCreate} onCreate={state.handleModelCreate}
onBlur={() => {}} onBlur={() => {}}
className="flex-1 min-w-[380px]" className="flex-1 min-w-[380px]"
/>
<ResetButton
onClick={state.handleRefreshModels}
disabled={state.isFetchingModels || state.isAppleProvider}
ariaLabel={t("settings.postProcessing.api.model.refreshModels")}
className="flex h-10 w-10 items-center justify-center"
>
<RefreshCcw
className={`h-4 w-4 ${state.isFetchingModels ? "animate-spin" : ""}`}
/> />
</ResetButton> <ResetButton
</div> onClick={state.handleRefreshModels}
</SettingContainer> disabled={state.isFetchingModels}
ariaLabel={t("settings.postProcessing.api.model.refreshModels")}
className="flex h-10 w-10 items-center justify-center"
>
<RefreshCcw
className={`h-4 w-4 ${state.isFetchingModels ? "animate-spin" : ""}`}
/>
</ResetButton>
</div>
</SettingContainer>
)}
</> </>
); );
}; };

View file

@ -0,0 +1,64 @@
import React from "react";
import { AlertCircle, AlertTriangle, Info, CheckCircle } from "lucide-react";
type AlertVariant = "error" | "warning" | "info" | "success";
interface AlertProps {
variant?: AlertVariant;
/** When true, removes rounded corners for use inside containers */
contained?: boolean;
children: React.ReactNode;
className?: string;
}
const variantStyles: Record<
AlertVariant,
{ container: string; icon: string; text: string }
> = {
error: {
container: "bg-red-500/10",
icon: "text-red-500",
text: "text-red-400",
},
warning: {
container: "bg-yellow-500/10",
icon: "text-yellow-500",
text: "text-yellow-400",
},
info: {
container: "bg-blue-500/10",
icon: "text-blue-500",
text: "text-blue-400",
},
success: {
container: "bg-green-500/10",
icon: "text-green-500",
text: "text-green-400",
},
};
const variantIcons: Record<AlertVariant, React.ElementType> = {
error: AlertCircle,
warning: AlertTriangle,
info: Info,
success: CheckCircle,
};
export const Alert: React.FC<AlertProps> = ({
variant = "error",
contained = false,
children,
className = "",
}) => {
const styles = variantStyles[variant];
const Icon = variantIcons[variant];
return (
<div
className={`flex items-start gap-3 p-4 ${styles.container} ${contained ? "" : "rounded-lg"} ${className}`}
>
<Icon className={`w-5 h-5 shrink-0 mt-0.5 ${styles.icon}`} />
<p className={`text-sm ${styles.text}`}>{children}</p>
</div>
);
};