From a24bbb4887fa31b9efbaf13a91e0379e5ebc7021 Mon Sep 17 00:00:00 2001 From: ComputelessComputer Date: Fri, 6 Mar 2026 17:47:07 +0900 Subject: [PATCH] refactor(stt): consolidate local model UI and use query hooksReplace ad-hoc model listing and per-model rows with reusable componentsand query helpers to simplify the local STT settings UI: - Use localSttQueries.supportedModels() with useQuery instead of an inline queryFn calling localSttCommands.listSupportedModels(). - Introduce LocalModelSection to render a grouped label and a set of LocalModelTile components for a model category (Argmax, WhisperCPP, Cactus). This replaces repeated HyprProviderLocalRow/CactusRow map blocks and centralizes layout. - Add modelsDir prop to support special casing (cactus) and pass it to tiles. - Convert the old CactusRow into the generic section/tile flow and remove duplicated download/open/delete handling from each mapped row. - Add a compact flag to LocalModelAction to allow denser rendering. These changes reduce duplication, improve composability, and make thecomponent tree clearer for future STT model UI work. --- .../desktop/src/settings/ai/stt/configure.tsx | 195 ++++++++---------- crates/cactus-model/src/lib.rs | 43 +++- 2 files changed, 126 insertions(+), 112 deletions(-) diff --git a/apps/desktop/src/settings/ai/stt/configure.tsx b/apps/desktop/src/settings/ai/stt/configure.tsx index 3dbc9c3a12..46f21590e9 100644 --- a/apps/desktop/src/settings/ai/stt/configure.tsx +++ b/apps/desktop/src/settings/ai/stt/configure.tsx @@ -13,6 +13,7 @@ import { useCallback } from "react"; import { commands as localSttCommands, type LocalModel, + type SttModelInfo, } from "@hypr/plugin-local-stt"; import { commands as openerCommands } from "@hypr/plugin-opener2"; import { @@ -99,14 +100,7 @@ function HyprProviderCard({ icon: React.ReactNode; badge?: string | null; }) { - const supportedModels = useQuery({ - queryKey: ["list-supported-models"], - queryFn: async () => { - const result = await localSttCommands.listSupportedModels(); - return result.status === "ok" ? result.data : []; - }, - staleTime: Infinity, - }); + const supportedModels = useQuery(localSttQueries.supportedModels()); const argmaxModels = supportedModels.data?.filter((m) => m.model_type === "argmax") ?? []; @@ -167,45 +161,24 @@ function HyprProviderCard({ {argmaxModels.length > 0 && ( - <> - - {argmaxModels.map((model) => ( - - ))} - + )} {whispercppModels.length > 0 && ( - <> - - {whispercppModels.map((model) => ( - - ))} - + )} {cactusModels.length > 0 && ( <> - + {/* m.key)} /> */} - - {cactusModels.map((model) => ( - - ))} )} @@ -216,52 +189,30 @@ function HyprProviderCard({ ); } -function CactusRow({ - model, - displayName, +function LocalModelSection({ + label, + models, + modelsDir = "default", }: { - model: LocalModel; - displayName: string; + label: string; + models: SttModelInfo[]; + modelsDir?: "default" | "cactus"; }) { - const handleSelectModel = useSafeSelectModel(); - const { shouldHighlightDownload } = useSttSettings(); - - const { - progress, - hasError, - isDownloaded, - showProgress, - handleDownload, - handleCancel, - handleDelete, - } = useLocalModelDownload(model, handleSelectModel); - - const handleOpen = () => { - void localSttCommands.cactusModelsDir().then((result) => { - if (result.status === "ok") { - void openerCommands.openPath(result.data, null); - } - }); - }; - return ( - -
- {displayName} +
+ +
+ {models.map((model) => ( + + ))}
- - - +
); } @@ -386,6 +337,7 @@ function LocalModelAction({ onDownload, onCancel, onDelete, + compact = false, }: { isDownloaded: boolean; showProgress: boolean; @@ -396,6 +348,7 @@ function LocalModelAction({ onDownload: () => void; onCancel: () => void; onDelete: () => void; + compact?: boolean; }) { const showShimmer = highlight && !isDownloaded && !showProgress && !hasError; @@ -404,8 +357,12 @@ function LocalModelAction({