mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: add download badge to uninstalled AI tools in tool grid
Show a download icon on AI tools that are not yet installed, and fetch feature status on mount in both the sidebar tool panel and fullscreen grid page.
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import type { Tool } from "@ashim/shared";
|
import type { Tool } from "@ashim/shared";
|
||||||
import { FileImage, Star } from "lucide-react";
|
import { PYTHON_SIDECAR_TOOLS } from "@ashim/shared";
|
||||||
|
import { Download, FileImage, Star } from "lucide-react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { ICON_MAP } from "@/lib/icon-map";
|
import { ICON_MAP } from "@/lib/icon-map";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useFeaturesStore } from "@/stores/features-store";
|
||||||
|
|
||||||
interface ToolCardProps {
|
interface ToolCardProps {
|
||||||
tool: Tool;
|
tool: Tool;
|
||||||
@@ -12,6 +14,10 @@ export function ToolCard({ tool }: ToolCardProps) {
|
|||||||
const IconComponent =
|
const IconComponent =
|
||||||
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage;
|
(ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage;
|
||||||
|
|
||||||
|
const isAiTool = (PYTHON_SIDECAR_TOOLS as readonly string[]).includes(tool.id);
|
||||||
|
const isInstalled = useFeaturesStore((s) => s.isToolInstalled)(tool.id);
|
||||||
|
const showDownloadBadge = isAiTool && !isInstalled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group flex items-center gap-3 relative">
|
<div className="group flex items-center gap-3 relative">
|
||||||
<button
|
<button
|
||||||
@@ -36,6 +42,7 @@ export function ToolCard({ tool }: ToolCardProps) {
|
|||||||
Experimental
|
Experimental
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{showDownloadBadge && <Download className="h-3.5 w-3.5 text-muted-foreground" />}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { CATEGORIES, TOOLS } from "@ashim/shared";
|
import { CATEGORIES, TOOLS } from "@ashim/shared";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { useFeaturesStore } from "@/stores/features-store";
|
||||||
import { useSettingsStore } from "@/stores/settings-store";
|
import { useSettingsStore } from "@/stores/settings-store";
|
||||||
import { SearchBar } from "../common/search-bar";
|
import { SearchBar } from "../common/search-bar";
|
||||||
import { ToolCard } from "../common/tool-card";
|
import { ToolCard } from "../common/tool-card";
|
||||||
@@ -7,11 +8,16 @@ import { ToolCard } from "../common/tool-card";
|
|||||||
export function ToolPanel() {
|
export function ToolPanel() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const { disabledTools, experimentalEnabled, loaded, fetch } = useSettingsStore();
|
const { disabledTools, experimentalEnabled, loaded, fetch } = useSettingsStore();
|
||||||
|
const fetchFeatures = useFeaturesStore((s) => s.fetch);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch();
|
fetch();
|
||||||
}, [fetch]);
|
}, [fetch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFeatures();
|
||||||
|
}, [fetchFeatures]);
|
||||||
|
|
||||||
const visibleTools = useMemo(() => {
|
const visibleTools = useMemo(() => {
|
||||||
if (!loaded) return [];
|
if (!loaded) return [];
|
||||||
return TOOLS.filter((t) => {
|
return TOOLS.filter((t) => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { GemLogo } from "@/components/common/gem-logo";
|
|||||||
import { apiGet } from "@/lib/api";
|
import { apiGet } from "@/lib/api";
|
||||||
import { ICON_MAP } from "@/lib/icon-map";
|
import { ICON_MAP } from "@/lib/icon-map";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useFeaturesStore } from "@/stores/features-store";
|
||||||
|
|
||||||
export function FullscreenGridPage() {
|
export function FullscreenGridPage() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -14,6 +15,11 @@ export function FullscreenGridPage() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [disabledTools, setDisabledTools] = useState<string[]>([]);
|
const [disabledTools, setDisabledTools] = useState<string[]>([]);
|
||||||
const [experimentalEnabled, setExperimentalEnabled] = useState(false);
|
const [experimentalEnabled, setExperimentalEnabled] = useState(false);
|
||||||
|
const fetchFeatures = useFeaturesStore((s) => s.fetch);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFeatures();
|
||||||
|
}, [fetchFeatures]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
apiGet<{ settings: Record<string, string> }>("/v1/settings")
|
apiGet<{ settings: Record<string, string> }>("/v1/settings")
|
||||||
|
|||||||
Reference in New Issue
Block a user