From 12014694c2f7897f7ffc5c7c4328a85e9f94522d Mon Sep 17 00:00:00 2001 From: David Anyatonwu Date: Thu, 5 Dec 2024 09:07:08 +0100 Subject: [PATCH 1/2] feat(ui): redesign pipe store to match Obsidian layout --- .../components/pipe-store.tsx | 855 +++++++++--------- screenpipe-app-tauri/src-tauri/Cargo.lock | 2 +- 2 files changed, 405 insertions(+), 452 deletions(-) diff --git a/screenpipe-app-tauri/components/pipe-store.tsx b/screenpipe-app-tauri/components/pipe-store.tsx index acde89da5..4cd897418 100644 --- a/screenpipe-app-tauri/components/pipe-store.tsx +++ b/screenpipe-app-tauri/components/pipe-store.tsx @@ -17,6 +17,7 @@ import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import { toast } from "./ui/use-toast"; import { Input } from "./ui/input"; +import { Switch } from "./ui/switch"; import { Download, Plus, @@ -24,10 +25,10 @@ import { ExternalLink, FolderOpen, RefreshCw, + Search, Power, - Link, - Heart, Puzzle, + X, } from "lucide-react"; import { PipeConfigForm } from "./pipe-config-form"; import { useHealthCheck } from "@/lib/hooks/use-health-check"; @@ -41,14 +42,8 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { readFile } from "@tauri-apps/plugin-fs"; -import { homeDir, join } from "@tauri-apps/api/path"; +import { join } from "@tauri-apps/api/path"; import { convertHtmlToMarkdown } from "@/lib/utils"; -import LogViewer from "./log-viewer-v2"; -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "@/components/ui/collapsible"; import { LogFileButton } from "./log-file-button"; import { useSettings } from "@/lib/hooks/use-settings"; import { StripeSubscriptionButton } from "./stripe-subscription-button"; @@ -60,6 +55,7 @@ export interface Pipe { source: string; fullDescription: string; config?: Record; + author?: string; } interface CorePipe { @@ -99,10 +95,26 @@ const corePipes: CorePipe[] = [ }, ]; +const getAuthorFromSource = (source: string): string => { + if (!source) return 'Unknown'; + if (!source.startsWith('http')) return 'Local'; + + try { + // Extract author from GitHub URL + // Format: https://github.com/author/repo/... + const match = source.match(/github\.com\/([^\/]+)/); + return match ? match[1] : 'Unknown'; + } catch { + return 'Unknown'; + } +}; + const PipeDialog: React.FC = () => { const [newRepoUrl, setNewRepoUrl] = useState(""); const [selectedPipe, setSelectedPipe] = useState(null); const [pipes, setPipes] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [showInstalledOnly, setShowInstalledOnly] = useState(false); const { health } = useHealthCheck(); const { getDataDir } = useSettings(); const { user, checkLoomSubscription } = useUser(); @@ -132,7 +144,6 @@ const PipeDialog: React.FC = () => { }); // Refresh the pipe list and installed pipes await fetchInstalledPipes(); - setSelectedPipe(null); } catch (error) { console.error("failed to reset pipes:", error); toast({ @@ -141,41 +152,29 @@ const PipeDialog: React.FC = () => { variant: "destructive", }); } finally { - setSelectedPipe(null); setPipes([]); } }; const fetchInstalledPipes = async () => { - if (!health || health?.status === "error") { - return; - } + if (!health || health?.status === "error") return; + const dataDir = await getDataDir(); try { const response = await fetch("http://localhost:3030/pipes/list"); - if (!response.ok) { - throw new Error("failed to fetch installed pipes"); - } + if (!response.ok) throw new Error("failed to fetch installed pipes"); + const data = (await response.json()).data; - for (const pipe of data) { const pathToReadme = await join(dataDir, "pipes", pipe.id, "README.md"); try { const readme = await readFile(pathToReadme); - const readmeString = new TextDecoder().decode(readme); - pipe.fullDescription = convertHtmlToMarkdown(readmeString); + pipe.fullDescription = convertHtmlToMarkdown(new TextDecoder().decode(readme)); } catch (error) { - console.warn(`no readme found for pipe ${pipe.id}`); pipe.fullDescription = "no description available for this pipe."; } } setPipes(data); - if (selectedPipe) { - const updatedSelectedPipe = data.find( - (pipe: Pipe) => pipe.id === selectedPipe.id - ); - setSelectedPipe(updatedSelectedPipe || null); - } } catch (error) { console.error("Error fetching installed pipes:", error); toast({ @@ -206,7 +205,7 @@ const PipeDialog: React.FC = () => { if (!response.ok) { throw new Error("failed to download pipe"); } - const data = await response.json(); + await response.json(); toast({ title: "pipe downloaded", @@ -240,43 +239,26 @@ const PipeDialog: React.FC = () => { enabled: !pipe.enabled, }); - if (!pipe.enabled) { - await fetch(`http://localhost:3030/pipes/enable`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ pipe_id: pipe.id }), - }); + const endpoint = pipe.enabled ? "disable" : "enable"; + const response = await fetch(`http://localhost:3030/pipes/${endpoint}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ pipe_id: pipe.id }), + }); - toast({ - title: "enabling pipe", - description: "this may take a few moments...", - }); - } else { - await fetch(`http://localhost:3030/pipes/disable`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ pipe_id: pipe.id }), - }); + if (!response.ok) throw new Error(`failed to ${endpoint} pipe`); - toast({ - title: "disabling pipe", - description: "this may take a few moments...", - }); - } + toast({ + title: `${endpoint}ing pipe`, + description: "this may take a few moments...", + }); await new Promise((resolve) => setTimeout(resolve, 1000)); - - if (selectedPipe && selectedPipe.id === pipe.id) { - setSelectedPipe((prevPipe) => - prevPipe ? { ...prevPipe, enabled: !prevPipe.enabled } : null - ); - } + await fetchInstalledPipes(); } catch (error) { - console.error("Failed to toggle pipe:", error); + console.error(`Failed to ${pipe.enabled ? "disable" : "enable"} pipe:`, error); toast({ title: "error toggling pipe", description: "please try again or check the logs for more information.", @@ -326,7 +308,6 @@ const PipeDialog: React.FC = () => { }); } finally { setNewRepoUrl(""); - setSelectedPipe(null); } } }; @@ -351,7 +332,6 @@ const PipeDialog: React.FC = () => { }); } }; - const handleConfigSave = async (config: Record) => { if (selectedPipe) { try { @@ -380,7 +360,6 @@ const PipeDialog: React.FC = () => { } } }; - const handleDeletePipe = async (pipe: Pipe) => { try { posthog.capture("delete_pipe", { @@ -391,269 +370,213 @@ const PipeDialog: React.FC = () => { description: "please wait...", }); - const response = await fetch(`http://localhost:3030/pipes/delete`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ pipe_id: pipe.id }), - }); - - if (!response.ok) { - throw new Error("failed to delete pipe"); - } - await fetchInstalledPipes(); - setSelectedPipe(null); - toast({ - title: "pipe deleted", - description: "the pipe has been successfully removed.", - }); } catch (error) { console.error("failed to delete pipe:", error); - toast({ - title: "error deleting pipe", - description: "please try again or check the logs for more information.", - variant: "destructive", - }); } }; - const renderPipeContent = () => { - if (!selectedPipe) { - return ( -
-

no pipe selected

- {/* */} - {!health || - (health?.status === "error" && ( -

- screenpipe is not running. -
- please start screenpipe to use the pipe store. -

- ))} -
- ); - } + const allPipes = [...pipes, ...corePipes.map(cp => ({ + id: cp.id, + fullDescription: cp.description, + source: cp.url, + enabled: false, + }))]; + + const filteredPipes = allPipes.filter(pipe => + pipe.id.toLowerCase().includes(searchQuery.toLowerCase()) && + (!showInstalledOnly || pipe.enabled) + ); + + const renderPipeDetails = () => { + if (!selectedPipe) return null; return ( - <> -

{selectedPipe.id}

- -
- {selectedPipe.id === "pipe-for-loom" && - !selectedPipe.enabled && - !hasLoomSubscription ? ( - handleToggleEnabled(selectedPipe)} - /> - ) : ( - - )} - {!selectedPipe.source?.startsWith("https://") && ( - - - +
+ +
+
+ {selectedPipe.id === "pipe-for-loom" && + !selectedPipe.enabled && + !hasLoomSubscription ? ( + handleToggleEnabled(selectedPipe)} + /> + ) : ( + + )} + +
+ {!selectedPipe.source?.startsWith("https://") && ( + + + + + + +

refresh the code from your local disk

+
+
+
+ )} + {selectedPipe.source?.startsWith("http") && ( - - -

refresh the code from your local disk

-
- - - )} - - {selectedPipe.source?.startsWith("http") && ( - - )} - - -
- - - {selectedPipe && selectedPipe.enabled && selectedPipe?.config?.port && ( -
-
-

pipe ui

- + )} + + +
- - ); - } - - // If it's not recognized as a video, log this info - console.log( - "Link not recognized as video, rendering as normal link" - ); - return ( - - {children} - - ); - }, - video({ src }) { - console.log("vid", src); - return ( -