Skip to content
25 changes: 25 additions & 0 deletions pipes/search/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ body {
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 0 0% 3.9%;
Expand All @@ -59,6 +67,14 @@ body {
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}

Expand All @@ -81,3 +97,12 @@ body {
transform: translate(-50%, -50%) !important;
} */

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

21 changes: 17 additions & 4 deletions pipes/search/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { Toaster } from "@/components/ui/toaster";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
import { HistorySidebar } from "@/components/history-sidebar"

const geistSans = Geist({
variable: "--font-geist-sans",
Expand All @@ -27,11 +29,22 @@ export default function RootLayout({
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
className={`${geistSans.variable} ${geistMono.variable} antialiased `}
>
{children}

<Toaster />
<SidebarProvider defaultOpen={true}>
<div className="flex w-full h-full">
<div className="absolute left-0 top-0 h-full z-[50]">
<HistorySidebar />
</div>
<div className="fixed left-0 top-2 z-[10000] ">
<SidebarTrigger />
</div>
<div className="flex-1 overflow-auto">
{children}
</div>
</div>
</SidebarProvider>
<Toaster />
</body>
</html>
);
Expand Down
33 changes: 16 additions & 17 deletions pipes/search/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,21 @@ export default function SearchPage() {
settings?.aiProviderType === "screenpipe-cloud" && !settings?.user?.token;

return (
<div
className={`flex flex-col gap-4 items-center justify-center h-full ${aiDisabled ? "mt-2" : "mt-12"}`}
>
{aiDisabled && (
<Alert className="w-[70%] shadow-sm">
<Terminal className="h-4 w-4" />
<AlertTitle>heads up!</AlertTitle>
<AlertDescription className="text-muted-foreground">
your ai provider is set to &apos;screenpipe-cloud&apos; and you
don&apos;t have logged in <br />
please login to use this pipe, go to app &gt; settings &gt; login
</AlertDescription>
</Alert>
)}
<p className="text-2xl font-bold">search your screen history</p>
<SearchChat />
</div>
<>
<div className={`flex flex-col gap-4 items-center justify-center h-full ${aiDisabled ? "mt-2" : "mt-12"}`}>
{aiDisabled && (
<Alert className="w-[70%] shadow-sm">
<Terminal className="h-4 w-4" />
<AlertTitle>heads up!</AlertTitle>
<AlertDescription className="text-muted-foreground">
your ai provider is set to &apos;screenpipe-cloud&apos; and you don&apos;t have logged in <br />
please login to use this pipe, go to app &gt; settings &gt; login
</AlertDescription>
</Alert>
)}
<p className="text-2xl font-bold">search your screen history</p>
<SearchChat />
</div>
</>
);
}
148 changes: 148 additions & 0 deletions pipes/search/src/components/history-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"use client";
import React, { useEffect, useState } from "react";
import { Plus, Trash2 } from "lucide-react";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarGroupAction,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarHeader,
} from "@/components/ui/sidebar";
import { SearchForm } from "@/components/search-form";
import { listHistory, HistoryItem, deleteHistoryItem } from "@/hooks/actions/history";


export function HistorySidebar() {
const [todayItems, setTodayItems] = useState<HistoryItem[]>([]);
const [yesterdayItems, setYesterdayItems] = useState<HistoryItem[]>([]);
const [previous7DaysItems, setPrevious7DaysItems] = useState<HistoryItem[]>([]);
const [searchQuery, setSearchQuery] = useState("");
useEffect(() => {
const handleHistoryUpdate = () => {
fetchHistory();
};
window.addEventListener("historyCreated", handleHistoryUpdate);
return () => {
window.removeEventListener("historyCreated", handleHistoryUpdate);
};
}, []);
const fetchHistory = async () => {
const history: HistoryItem[] = await listHistory();
history.sort((a: HistoryItem, b: HistoryItem) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
const sevenDaysAgo = new Date(today);
sevenDaysAgo.setDate(today.getDate() - 7);

const todayItems: HistoryItem[] = [];
const yesterdayItems: HistoryItem[] = [];
const previous7DaysItems: HistoryItem[] = [];

history.forEach((item: HistoryItem) => {
const itemDate = new Date(item.timestamp);
if (itemDate.toDateString() === today.toDateString()) {
todayItems.push(item);
} else if (itemDate.toDateString() === yesterday.toDateString()) {
yesterdayItems.push(item);
} else if (itemDate >= sevenDaysAgo && itemDate < today) {
previous7DaysItems.push(item);
}
});

setTodayItems(todayItems);
setYesterdayItems(yesterdayItems);
setPrevious7DaysItems(previous7DaysItems);
};

useEffect(() => {
fetchHistory();
}, []);

const handleDeleteHistory = async (id: string) => {
await deleteHistoryItem(id);
fetchHistory();
};
const handleHistoryClick = (id: string) => {
localStorage.setItem("historyId", id);
window.dispatchEvent(new Event("historyUpdated"));
};

const handleNewChat = () => {
localStorage.removeItem('historyId');
location.reload();
};
const handleSearchChange = (event: React.FormEvent<HTMLFormElement>) => {
const target = event.target as HTMLInputElement;
setSearchQuery(target.value);
};
const filterItems = (items: HistoryItem[]) => {
return items.filter(item => item.title.toLowerCase().includes(searchQuery.toLowerCase()));
};

const renderHistoryItems = (items: HistoryItem[]) => (
filterItems(items).map(item => (
<SidebarMenuItem key={item.id}>
<SidebarMenuButton asChild>
<div className="p-1">
<a className="" href="#" onClick={() => handleHistoryClick(item.id)}>
<span>{item.title.substring(0, 30)}...</span>
</a>
<Trash2
className="absolute right-0 ml-2 cursor-pointer"
onClick={() => handleDeleteHistory(item.id)}
/>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
))
);

return (
<Sidebar >
<SidebarHeader>
<div className="pl-4">
<SearchForm onChange={handleSearchChange} />
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Today</SidebarGroupLabel>
<SidebarGroupAction title="New Chat" onClick={handleNewChat} >
<Plus /> <span className="sr-only">New Chat</span>
</SidebarGroupAction>
<SidebarGroupContent>
<SidebarMenu>
{renderHistoryItems(todayItems)}
</SidebarMenu>
</SidebarGroupContent>
{yesterdayItems.length > 0 && (
<>
<SidebarGroupLabel>Yesterday</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{renderHistoryItems(yesterdayItems)}
</SidebarMenu>
</SidebarGroupContent>
</>
)}
{previous7DaysItems.length > 0 && (
<>
< SidebarGroupLabel > Previous 7 days</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{renderHistoryItems(previous7DaysItems)}
</SidebarMenu>
</SidebarGroupContent>
</>
)}
</SidebarGroup>
</SidebarContent>
</Sidebar >
);
}
Loading
Loading