Merge branch 'coleam00:main' into main
Browse files
app/components/sidebar/Menu.client.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { cubicEasingFn } from '~/utils/easings';
|
|
| 8 |
import { logger } from '~/utils/logger';
|
| 9 |
import { HistoryItem } from './HistoryItem';
|
| 10 |
import { binDates } from './date-binning';
|
|
|
|
| 11 |
|
| 12 |
const menuVariants = {
|
| 13 |
closed: {
|
|
@@ -39,6 +40,11 @@ export function Menu() {
|
|
| 39 |
const [open, setOpen] = useState(false);
|
| 40 |
const [dialogContent, setDialogContent] = useState<DialogContent>(null);
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
const loadEntries = useCallback(() => {
|
| 43 |
if (db) {
|
| 44 |
getAll(db)
|
|
@@ -115,11 +121,11 @@ export function Menu() {
|
|
| 115 |
initial="closed"
|
| 116 |
animate={open ? 'open' : 'closed'}
|
| 117 |
variants={menuVariants}
|
| 118 |
-
className="flex flex-col side-menu fixed top-0 w-[350px] h-full bg-bolt-elements-background-depth-2 border-r rounded-r-3xl border-bolt-elements-borderColor z-sidebar shadow-xl shadow-bolt-elements-sidebar-dropdownShadow text-sm"
|
| 119 |
>
|
| 120 |
<div className="flex items-center h-[var(--header-height)]">{/* Placeholder */}</div>
|
| 121 |
<div className="flex-1 flex flex-col h-full w-full overflow-hidden">
|
| 122 |
-
<div className="p-4">
|
| 123 |
<a
|
| 124 |
href="/"
|
| 125 |
className="flex gap-2 items-center bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"
|
|
@@ -128,11 +134,26 @@ export function Menu() {
|
|
| 128 |
Start new chat
|
| 129 |
</a>
|
| 130 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
<div className="text-bolt-elements-textPrimary font-medium pl-6 pr-5 my-2">Your Chats</div>
|
| 132 |
<div className="flex-1 overflow-auto pl-4 pr-5 pb-5">
|
| 133 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
<DialogRoot open={dialogContent !== null}>
|
| 135 |
-
{binDates(
|
| 136 |
<div key={category} className="mt-4 first:mt-0 space-y-1">
|
| 137 |
<div className="text-bolt-elements-textTertiary sticky top-0 z-1 bg-bolt-elements-background-depth-2 pl-2 pt-2 pb-1">
|
| 138 |
{category}
|
|
|
|
| 8 |
import { logger } from '~/utils/logger';
|
| 9 |
import { HistoryItem } from './HistoryItem';
|
| 10 |
import { binDates } from './date-binning';
|
| 11 |
+
import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
|
| 12 |
|
| 13 |
const menuVariants = {
|
| 14 |
closed: {
|
|
|
|
| 40 |
const [open, setOpen] = useState(false);
|
| 41 |
const [dialogContent, setDialogContent] = useState<DialogContent>(null);
|
| 42 |
|
| 43 |
+
const { filteredItems: filteredList, handleSearchChange } = useSearchFilter({
|
| 44 |
+
items: list,
|
| 45 |
+
searchFields: ['description'],
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
const loadEntries = useCallback(() => {
|
| 49 |
if (db) {
|
| 50 |
getAll(db)
|
|
|
|
| 121 |
initial="closed"
|
| 122 |
animate={open ? 'open' : 'closed'}
|
| 123 |
variants={menuVariants}
|
| 124 |
+
className="flex selection-accent flex-col side-menu fixed top-0 w-[350px] h-full bg-bolt-elements-background-depth-2 border-r rounded-r-3xl border-bolt-elements-borderColor z-sidebar shadow-xl shadow-bolt-elements-sidebar-dropdownShadow text-sm"
|
| 125 |
>
|
| 126 |
<div className="flex items-center h-[var(--header-height)]">{/* Placeholder */}</div>
|
| 127 |
<div className="flex-1 flex flex-col h-full w-full overflow-hidden">
|
| 128 |
+
<div className="p-4 select-none">
|
| 129 |
<a
|
| 130 |
href="/"
|
| 131 |
className="flex gap-2 items-center bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"
|
|
|
|
| 134 |
Start new chat
|
| 135 |
</a>
|
| 136 |
</div>
|
| 137 |
+
<div className="pl-4 pr-4 my-2">
|
| 138 |
+
<div className="relative w-full">
|
| 139 |
+
<input
|
| 140 |
+
className="w-full bg-white dark:bg-bolt-elements-background-depth-4 relative px-2 py-1.5 rounded-md focus:outline-none placeholder-bolt-elements-textTertiary text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary border border-bolt-elements-borderColor"
|
| 141 |
+
type="search"
|
| 142 |
+
placeholder="Search"
|
| 143 |
+
onChange={handleSearchChange}
|
| 144 |
+
aria-label="Search chats"
|
| 145 |
+
/>
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
<div className="text-bolt-elements-textPrimary font-medium pl-6 pr-5 my-2">Your Chats</div>
|
| 149 |
<div className="flex-1 overflow-auto pl-4 pr-5 pb-5">
|
| 150 |
+
{filteredList.length === 0 && (
|
| 151 |
+
<div className="pl-2 text-bolt-elements-textTertiary">
|
| 152 |
+
{list.length === 0 ? 'No previous conversations' : 'No matches found'}
|
| 153 |
+
</div>
|
| 154 |
+
)}
|
| 155 |
<DialogRoot open={dialogContent !== null}>
|
| 156 |
+
{binDates(filteredList).map(({ category, items }) => (
|
| 157 |
<div key={category} className="mt-4 first:mt-0 space-y-1">
|
| 158 |
<div className="text-bolt-elements-textTertiary sticky top-0 z-1 bg-bolt-elements-background-depth-2 pl-2 pt-2 pb-1">
|
| 159 |
{category}
|
app/lib/hooks/useSearchFilter.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useMemo, useCallback } from 'react';
|
| 2 |
+
import { debounce } from '~/utils/debounce';
|
| 3 |
+
import type { ChatHistoryItem } from '~/lib/persistence';
|
| 4 |
+
|
| 5 |
+
interface UseSearchFilterOptions {
|
| 6 |
+
items: ChatHistoryItem[];
|
| 7 |
+
searchFields?: (keyof ChatHistoryItem)[];
|
| 8 |
+
debounceMs?: number;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export function useSearchFilter({
|
| 12 |
+
items = [],
|
| 13 |
+
searchFields = ['description'],
|
| 14 |
+
debounceMs = 300,
|
| 15 |
+
}: UseSearchFilterOptions) {
|
| 16 |
+
const [searchQuery, setSearchQuery] = useState('');
|
| 17 |
+
|
| 18 |
+
const debouncedSetSearch = useCallback(debounce(setSearchQuery, debounceMs), []);
|
| 19 |
+
|
| 20 |
+
const handleSearchChange = useCallback(
|
| 21 |
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
| 22 |
+
debouncedSetSearch(event.target.value);
|
| 23 |
+
},
|
| 24 |
+
[debouncedSetSearch],
|
| 25 |
+
);
|
| 26 |
+
|
| 27 |
+
const filteredItems = useMemo(() => {
|
| 28 |
+
if (!searchQuery.trim()) {
|
| 29 |
+
return items;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
const query = searchQuery.toLowerCase();
|
| 33 |
+
|
| 34 |
+
return items.filter((item) =>
|
| 35 |
+
searchFields.some((field) => {
|
| 36 |
+
const value = item[field];
|
| 37 |
+
|
| 38 |
+
if (typeof value === 'string') {
|
| 39 |
+
return value.toLowerCase().includes(query);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
return false;
|
| 43 |
+
}),
|
| 44 |
+
);
|
| 45 |
+
}, [items, searchQuery, searchFields]);
|
| 46 |
+
|
| 47 |
+
return {
|
| 48 |
+
searchQuery,
|
| 49 |
+
filteredItems,
|
| 50 |
+
handleSearchChange,
|
| 51 |
+
};
|
| 52 |
+
}
|
app/utils/constants.ts
CHANGED
|
@@ -283,9 +283,9 @@ const getOllamaBaseUrl = () => {
|
|
| 283 |
};
|
| 284 |
|
| 285 |
async function getOllamaModels(): Promise<ModelInfo[]> {
|
| 286 |
-
if (typeof window === 'undefined') {
|
| 287 |
-
return [];
|
| 288 |
-
}
|
| 289 |
|
| 290 |
try {
|
| 291 |
const baseUrl = getOllamaBaseUrl();
|
|
|
|
| 283 |
};
|
| 284 |
|
| 285 |
async function getOllamaModels(): Promise<ModelInfo[]> {
|
| 286 |
+
//if (typeof window === 'undefined') {
|
| 287 |
+
//return [];
|
| 288 |
+
//}
|
| 289 |
|
| 290 |
try {
|
| 291 |
const baseUrl = getOllamaBaseUrl();
|