mirror of
https://github.com/stackblitz/bolt.new
synced 2025-02-06 04:48:04 +00:00
Merge branch 'coleam00:main' into main
This commit is contained in:
commit
41da9c8690
@ -8,6 +8,7 @@ import { cubicEasingFn } from '~/utils/easings';
|
|||||||
import { logger } from '~/utils/logger';
|
import { logger } from '~/utils/logger';
|
||||||
import { HistoryItem } from './HistoryItem';
|
import { HistoryItem } from './HistoryItem';
|
||||||
import { binDates } from './date-binning';
|
import { binDates } from './date-binning';
|
||||||
|
import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
|
||||||
|
|
||||||
const menuVariants = {
|
const menuVariants = {
|
||||||
closed: {
|
closed: {
|
||||||
@ -39,6 +40,11 @@ export function Menu() {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [dialogContent, setDialogContent] = useState<DialogContent>(null);
|
const [dialogContent, setDialogContent] = useState<DialogContent>(null);
|
||||||
|
|
||||||
|
const { filteredItems: filteredList, handleSearchChange } = useSearchFilter({
|
||||||
|
items: list,
|
||||||
|
searchFields: ['description'],
|
||||||
|
});
|
||||||
|
|
||||||
const loadEntries = useCallback(() => {
|
const loadEntries = useCallback(() => {
|
||||||
if (db) {
|
if (db) {
|
||||||
getAll(db)
|
getAll(db)
|
||||||
@ -115,11 +121,11 @@ export function Menu() {
|
|||||||
initial="closed"
|
initial="closed"
|
||||||
animate={open ? 'open' : 'closed'}
|
animate={open ? 'open' : 'closed'}
|
||||||
variants={menuVariants}
|
variants={menuVariants}
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
<div className="flex items-center h-[var(--header-height)]">{/* Placeholder */}</div>
|
<div className="flex items-center h-[var(--header-height)]">{/* Placeholder */}</div>
|
||||||
<div className="flex-1 flex flex-col h-full w-full overflow-hidden">
|
<div className="flex-1 flex flex-col h-full w-full overflow-hidden">
|
||||||
<div className="p-4">
|
<div className="p-4 select-none">
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
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"
|
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() {
|
|||||||
Start new chat
|
Start new chat
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="pl-4 pr-4 my-2">
|
||||||
|
<div className="relative w-full">
|
||||||
|
<input
|
||||||
|
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"
|
||||||
|
type="search"
|
||||||
|
placeholder="Search"
|
||||||
|
onChange={handleSearchChange}
|
||||||
|
aria-label="Search chats"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="text-bolt-elements-textPrimary font-medium pl-6 pr-5 my-2">Your Chats</div>
|
<div className="text-bolt-elements-textPrimary font-medium pl-6 pr-5 my-2">Your Chats</div>
|
||||||
<div className="flex-1 overflow-auto pl-4 pr-5 pb-5">
|
<div className="flex-1 overflow-auto pl-4 pr-5 pb-5">
|
||||||
{list.length === 0 && <div className="pl-2 text-bolt-elements-textTertiary">No previous conversations</div>}
|
{filteredList.length === 0 && (
|
||||||
|
<div className="pl-2 text-bolt-elements-textTertiary">
|
||||||
|
{list.length === 0 ? 'No previous conversations' : 'No matches found'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<DialogRoot open={dialogContent !== null}>
|
<DialogRoot open={dialogContent !== null}>
|
||||||
{binDates(list).map(({ category, items }) => (
|
{binDates(filteredList).map(({ category, items }) => (
|
||||||
<div key={category} className="mt-4 first:mt-0 space-y-1">
|
<div key={category} className="mt-4 first:mt-0 space-y-1">
|
||||||
<div className="text-bolt-elements-textTertiary sticky top-0 z-1 bg-bolt-elements-background-depth-2 pl-2 pt-2 pb-1">
|
<div className="text-bolt-elements-textTertiary sticky top-0 z-1 bg-bolt-elements-background-depth-2 pl-2 pt-2 pb-1">
|
||||||
{category}
|
{category}
|
||||||
|
52
app/lib/hooks/useSearchFilter.ts
Normal file
52
app/lib/hooks/useSearchFilter.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { useState, useMemo, useCallback } from 'react';
|
||||||
|
import { debounce } from '~/utils/debounce';
|
||||||
|
import type { ChatHistoryItem } from '~/lib/persistence';
|
||||||
|
|
||||||
|
interface UseSearchFilterOptions {
|
||||||
|
items: ChatHistoryItem[];
|
||||||
|
searchFields?: (keyof ChatHistoryItem)[];
|
||||||
|
debounceMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSearchFilter({
|
||||||
|
items = [],
|
||||||
|
searchFields = ['description'],
|
||||||
|
debounceMs = 300,
|
||||||
|
}: UseSearchFilterOptions) {
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
|
const debouncedSetSearch = useCallback(debounce(setSearchQuery, debounceMs), []);
|
||||||
|
|
||||||
|
const handleSearchChange = useCallback(
|
||||||
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
debouncedSetSearch(event.target.value);
|
||||||
|
},
|
||||||
|
[debouncedSetSearch],
|
||||||
|
);
|
||||||
|
|
||||||
|
const filteredItems = useMemo(() => {
|
||||||
|
if (!searchQuery.trim()) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = searchQuery.toLowerCase();
|
||||||
|
|
||||||
|
return items.filter((item) =>
|
||||||
|
searchFields.some((field) => {
|
||||||
|
const value = item[field];
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value.toLowerCase().includes(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [items, searchQuery, searchFields]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchQuery,
|
||||||
|
filteredItems,
|
||||||
|
handleSearchChange,
|
||||||
|
};
|
||||||
|
}
|
@ -283,9 +283,9 @@ const getOllamaBaseUrl = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function getOllamaModels(): Promise<ModelInfo[]> {
|
async function getOllamaModels(): Promise<ModelInfo[]> {
|
||||||
if (typeof window === 'undefined') {
|
//if (typeof window === 'undefined') {
|
||||||
return [];
|
//return [];
|
||||||
}
|
//}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const baseUrl = getOllamaBaseUrl();
|
const baseUrl = getOllamaBaseUrl();
|
||||||
|
Loading…
Reference in New Issue
Block a user