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:
parent
ae9c33e0c7
commit
5da495157f
2 changed files with 112 additions and 63 deletions
|
|
@ -3,13 +3,16 @@ import { useTranslation } from "react-i18next";
|
|||
import { RefreshCcw } from "lucide-react";
|
||||
import { commands } from "@/bindings";
|
||||
|
||||
import { SettingsGroup } from "../../ui/SettingsGroup";
|
||||
import { SettingContainer } from "../../ui/SettingContainer";
|
||||
import { Alert } from "../../ui/Alert";
|
||||
import {
|
||||
Dropdown,
|
||||
SettingContainer,
|
||||
SettingsGroup,
|
||||
Textarea,
|
||||
} from "@/components/ui";
|
||||
import { Button } from "../../ui/Button";
|
||||
import { ResetButton } from "../../ui/ResetButton";
|
||||
import { Input } from "../../ui/Input";
|
||||
import { Dropdown } from "../../ui/Dropdown";
|
||||
import { Textarea } from "../../ui/Textarea";
|
||||
|
||||
import { ProviderSelect } from "../PostProcessingSettingsApi/ProviderSelect";
|
||||
import { BaseUrlField } from "../PostProcessingSettingsApi/BaseUrlField";
|
||||
|
|
@ -56,28 +59,12 @@ const PostProcessingSettingsApiComponent: React.FC = () => {
|
|||
</div>
|
||||
</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 ? (
|
||||
<SettingContainer
|
||||
title={t("settings.postProcessing.api.appleIntelligence.title")}
|
||||
description={t(
|
||||
"settings.postProcessing.api.appleIntelligence.description",
|
||||
)}
|
||||
descriptionMode="tooltip"
|
||||
layout="stacked"
|
||||
grouped={true}
|
||||
>
|
||||
<DisabledNotice>
|
||||
{t("settings.postProcessing.api.appleIntelligence.requirements")}
|
||||
</DisabledNotice>
|
||||
</SettingContainer>
|
||||
state.appleIntelligenceUnavailable ? (
|
||||
<Alert variant="error" contained>
|
||||
{t("settings.postProcessing.api.appleIntelligence.unavailable")}
|
||||
</Alert>
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
{state.selectedProvider?.id === "custom" && (
|
||||
|
|
@ -124,51 +111,49 @@ const PostProcessingSettingsApiComponent: React.FC = () => {
|
|||
</>
|
||||
)}
|
||||
|
||||
<SettingContainer
|
||||
title={t("settings.postProcessing.api.model.title")}
|
||||
description={
|
||||
state.isAppleProvider
|
||||
? t("settings.postProcessing.api.model.descriptionApple")
|
||||
: state.isCustomProvider
|
||||
{!state.isAppleProvider && (
|
||||
<SettingContainer
|
||||
title={t("settings.postProcessing.api.model.title")}
|
||||
description={
|
||||
state.isCustomProvider
|
||||
? t("settings.postProcessing.api.model.descriptionCustom")
|
||||
: t("settings.postProcessing.api.model.descriptionDefault")
|
||||
}
|
||||
descriptionMode="tooltip"
|
||||
layout="stacked"
|
||||
grouped={true}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<ModelSelect
|
||||
value={state.model}
|
||||
options={state.modelOptions}
|
||||
disabled={state.isModelUpdating}
|
||||
isLoading={state.isFetchingModels}
|
||||
placeholder={
|
||||
state.isAppleProvider
|
||||
? t("settings.postProcessing.api.model.placeholderApple")
|
||||
: state.modelOptions.length > 0
|
||||
}
|
||||
descriptionMode="tooltip"
|
||||
layout="stacked"
|
||||
grouped={true}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<ModelSelect
|
||||
value={state.model}
|
||||
options={state.modelOptions}
|
||||
disabled={state.isModelUpdating}
|
||||
isLoading={state.isFetchingModels}
|
||||
placeholder={
|
||||
state.modelOptions.length > 0
|
||||
? t(
|
||||
"settings.postProcessing.api.model.placeholderWithOptions",
|
||||
)
|
||||
: t("settings.postProcessing.api.model.placeholderNoOptions")
|
||||
}
|
||||
onSelect={state.handleModelSelect}
|
||||
onCreate={state.handleModelCreate}
|
||||
onBlur={() => {}}
|
||||
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" : ""}`}
|
||||
}
|
||||
onSelect={state.handleModelSelect}
|
||||
onCreate={state.handleModelCreate}
|
||||
onBlur={() => {}}
|
||||
className="flex-1 min-w-[380px]"
|
||||
/>
|
||||
</ResetButton>
|
||||
</div>
|
||||
</SettingContainer>
|
||||
<ResetButton
|
||||
onClick={state.handleRefreshModels}
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
64
src/components/ui/Alert.tsx
Normal file
64
src/components/ui/Alert.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in a new issue