fix: Update private key field in chat integration documentation

- Change the private key field name from "prvkey" to "prvkey_hex" in the CHAT_INTEGRATION.md file for clarity and consistency with expected data formats.

feat: Add Fuzzy Search Component and Composable

- Introduce a new FuzzySearch component for Vue 3, leveraging Fuse.js for intelligent search capabilities.
- Implement a useFuzzySearch composable for flexible search functionality, allowing configuration of search options.
- Create demo and README files to showcase usage and features of the fuzzy search implementation.
- Update package.json and package-lock.json to include @vueuse/integrations version 13.6.0 for enhanced performance.
This commit is contained in:
padreug 2025-08-08 14:17:11 +02:00
parent 37a539bc2d
commit 3d1bc94183
8 changed files with 783 additions and 1 deletions

View file

@ -0,0 +1,133 @@
<script setup lang="ts">
import { computed, watch } from 'vue'
import { useFuzzySearch, type FuzzySearchOptions } from '@/composables/useFuzzySearch'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Search, X } from 'lucide-vue-next'
interface Props<T = any> {
/**
* The data to search through
*/
data: T[]
/**
* Configuration options for the fuzzy search
*/
options?: FuzzySearchOptions<T>
/**
* Placeholder text for the search input
*/
placeholder?: string
/**
* Whether to show a clear button
*/
showClearButton?: boolean
/**
* Whether to show the result count
*/
showResultCount?: boolean
/**
* Custom class for the search container
*/
class?: string
/**
* Whether the search input should be disabled
*/
disabled?: boolean
}
interface Emits<T = any> {
(e: 'update:modelValue', value: string): void
(e: 'search', query: string): void
(e: 'results', results: T[]): void
(e: 'clear'): void
}
const props = withDefaults(defineProps<Props>(), {
placeholder: 'Search...',
showClearButton: true,
showResultCount: true,
disabled: false
})
const emit = defineEmits<Emits>()
// Create reactive data ref for the composable
const dataRef = computed(() => props.data)
// Use the fuzzy search composable
const {
searchQuery,
results,
filteredItems,
isSearching,
resultCount,
clearSearch,
setSearchQuery
} = useFuzzySearch(dataRef, props.options)
// Emit events when search changes
const handleSearchChange = (value: string | number) => {
const stringValue = String(value)
setSearchQuery(stringValue)
emit('update:modelValue', stringValue)
emit('search', stringValue)
emit('results', filteredItems.value)
}
const handleClear = () => {
clearSearch()
emit('update:modelValue', '')
emit('search', '')
emit('results', filteredItems.value)
emit('clear')
}
// Watch for changes in filtered items and emit results
watch(filteredItems, (items) => {
emit('results', items)
}, { immediate: true })
</script>
<template>
<div :class="['fuzzy-search', class]">
<!-- Search Input -->
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Search class="h-4 w-4 text-muted-foreground" />
</div>
<Input
:model-value="searchQuery"
@update:model-value="handleSearchChange"
:placeholder="placeholder"
:disabled="disabled"
class="pl-10 pr-10"
/>
<!-- Clear Button -->
<div v-if="showClearButton && searchQuery" class="absolute inset-y-0 right-0 pr-3 flex items-center">
<Button
variant="ghost"
size="sm"
@click="handleClear"
class="h-6 w-6 p-0 hover:bg-muted"
>
<X class="h-3 w-3" />
<span class="sr-only">Clear search</span>
</Button>
</div>
</div>
<!-- Result Count -->
<div v-if="showResultCount && isSearching" class="mt-2 text-sm text-muted-foreground">
{{ resultCount }} result{{ resultCount === 1 ? '' : 's' }} found
</div>
</div>
</template>
<style scoped>
.fuzzy-search {
@apply w-full;
}
</style>

View file

@ -0,0 +1,116 @@
<script setup lang="ts">
import { ref } from 'vue'
import { FuzzySearch, useFuzzySearch } from './index'
// Sample data for demonstration
interface Product {
id: number
name: string
description: string
category: string
price: number
}
const products = ref<Product[]>([
{ id: 1, name: 'Laptop', description: 'High-performance laptop for work and gaming', category: 'Electronics', price: 1200 },
{ id: 2, name: 'Smartphone', description: 'Latest smartphone with advanced features', category: 'Electronics', price: 800 },
{ id: 3, name: 'Headphones', description: 'Wireless noise-cancelling headphones', category: 'Audio', price: 200 },
{ id: 4, name: 'Coffee Maker', description: 'Automatic coffee maker for home use', category: 'Kitchen', price: 150 },
{ id: 5, name: 'Running Shoes', description: 'Comfortable running shoes for athletes', category: 'Sports', price: 120 },
{ id: 6, name: 'Backpack', description: 'Durable backpack for travel and daily use', category: 'Travel', price: 80 },
{ id: 7, name: 'Tablet', description: 'Portable tablet for entertainment and work', category: 'Electronics', price: 500 },
{ id: 8, name: 'Blender', description: 'High-speed blender for smoothies and shakes', category: 'Kitchen', price: 100 },
])
// Fuzzy search configuration
const searchOptions = {
fuseOptions: {
keys: ['name', 'description', 'category'],
threshold: 0.3,
distance: 100,
ignoreLocation: true,
useExtendedSearch: false,
minMatchCharLength: 1,
shouldSort: true,
findAllMatches: false,
location: 0,
isCaseSensitive: false,
},
resultLimit: 10,
matchAllWhenSearchEmpty: true,
minSearchLength: 1,
}
// Use the fuzzy search composable
const {
searchQuery,
results,
filteredItems,
isSearching,
resultCount,
clearSearch
} = useFuzzySearch(products, searchOptions)
// Handle search results
const handleSearchResults = (results: Product[]) => {
console.log('Search results:', results)
}
const handleSearch = (query: string) => {
console.log('Search query:', query)
}
</script>
<template>
<div class="p-6 max-w-4xl mx-auto">
<h1 class="text-3xl font-bold mb-6">Fuzzy Search Demo</h1>
<!-- Fuzzy Search Component -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-4">Search Products</h2>
<FuzzySearch
:data="products"
:options="searchOptions"
placeholder="Search products by name, description, or category..."
@search="handleSearch"
@results="handleSearchResults"
class="max-w-md"
/>
</div>
<!-- Search Results -->
<div class="mb-8">
<h2 class="text-xl font-semibold mb-4">
Results ({{ resultCount }} found)
<span v-if="isSearching" class="text-sm font-normal text-muted-foreground">
- Searching for "{{ searchQuery }}"
</span>
</h2>
<div v-if="filteredItems.length === 0 && isSearching" class="text-center py-8 text-muted-foreground">
No products found matching your search.
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div
v-for="product in filteredItems"
:key="product.id"
class="border rounded-lg p-4 hover:shadow-md transition-shadow"
>
<h3 class="font-semibold text-lg">{{ product.name }}</h3>
<p class="text-sm text-muted-foreground mb-2">{{ product.description }}</p>
<div class="flex justify-between items-center">
<span class="text-sm bg-secondary px-2 py-1 rounded">{{ product.category }}</span>
<span class="font-semibold">${{ product.price }}</span>
</div>
</div>
</div>
</div>
<!-- Raw Results (for debugging) -->
<div v-if="isSearching" class="mt-8 p-4 bg-muted rounded-lg">
<h3 class="font-semibold mb-2">Raw Search Results (with scoring)</h3>
<pre class="text-xs overflow-auto">{{ JSON.stringify(results, null, 2) }}</pre>
</div>
</div>
</template>

View file

@ -0,0 +1,236 @@
# Fuzzy Search Component
A powerful fuzzy search implementation for Vue 3 using Fuse.js and VueUse integrations.
## Features
- 🔍 **Fuzzy Search**: Intelligent search with typo tolerance and scoring
- ⚡ **VueUse Integration**: Built on top of `@vueuse/integrations` for optimal performance
- 🎯 **Configurable**: Customizable search options and behavior
- 🎨 **Vue 3 Compatible**: Built with Vue 3 Composition API and TypeScript
- 📦 **Reusable**: Both composable and component versions available
## Installation
The fuzzy search functionality is already installed and available in this project. It uses:
- `fuse.js` - For fuzzy search algorithms
- `@vueuse/integrations` - For Vue 3 integration
## Usage
### Using the Composable
```vue
<script setup lang="ts">
import { ref } from 'vue'
import { useFuzzySearch } from '@/composables/useFuzzySearch'
interface Product {
id: number
name: string
description: string
category: string
}
const products = ref<Product[]>([
{ id: 1, name: 'Laptop', description: 'High-performance laptop', category: 'Electronics' },
{ id: 2, name: 'Smartphone', description: 'Latest smartphone', category: 'Electronics' },
// ... more products
])
const { searchQuery, filteredItems, isSearching, resultCount, clearSearch } = useFuzzySearch(
products,
{
fuseOptions: {
keys: ['name', 'description', 'category'],
threshold: 0.3,
},
resultLimit: 10,
minSearchLength: 2,
}
)
</script>
<template>
<div>
<input v-model="searchQuery" placeholder="Search products..." />
<p>Found {{ resultCount }} results</p>
<div v-for="product in filteredItems" :key="product.id">
{{ product.name }}
</div>
</div>
</template>
```
### Using the Component
```vue
<script setup lang="ts">
import { ref } from 'vue'
import { FuzzySearch } from '@/components/ui/fuzzy-search'
interface Product {
id: number
name: string
description: string
}
const products = ref<Product[]>([
{ id: 1, name: 'Laptop', description: 'High-performance laptop' },
{ id: 2, name: 'Smartphone', description: 'Latest smartphone' },
])
const handleSearchResults = (results: Product[]) => {
console.log('Search results:', results)
}
</script>
<template>
<FuzzySearch
:data="products"
:options="{
fuseOptions: {
keys: ['name', 'description'],
threshold: 0.3,
},
resultLimit: 10,
}"
placeholder="Search products..."
@results="handleSearchResults"
/>
</template>
```
## API Reference
### `useFuzzySearch` Composable
#### Parameters
- `data: Ref<T[]> | T[]` - The data to search through
- `options?: FuzzySearchOptions<T>` - Configuration options
#### Returns
- `searchQuery: Ref<string>` - Current search query
- `results: ComputedRef<FuseResult<T>[]>` - Search results with scoring
- `filteredItems: ComputedRef<T[]>` - Filtered items (just the items)
- `isSearching: ComputedRef<boolean>` - Whether search is active
- `resultCount: ComputedRef<number>` - Number of results found
- `clearSearch: () => void` - Clear the current search
- `setSearchQuery: (query: string) => void` - Set the search query
- `updateData: (data: T[]) => void` - Update the data to search through
### `FuzzySearch` Component
#### Props
- `data: T[]` - The data to search through
- `options?: FuzzySearchOptions<T>` - Configuration options
- `placeholder?: string` - Placeholder text for the search input
- `showClearButton?: boolean` - Whether to show a clear button
- `showResultCount?: boolean` - Whether to show the result count
- `class?: string` - Custom class for the search container
- `disabled?: boolean` - Whether the search input should be disabled
#### Events
- `update:modelValue` - Emitted when the search query changes
- `search` - Emitted when a search is performed
- `results` - Emitted when search results change
- `clear` - Emitted when the search is cleared
### `FuzzySearchOptions`
```typescript
interface FuzzySearchOptions<T = any> {
/**
* Fuse.js options for configuring the search behavior
*/
fuseOptions?: FuseOptions<T>
/**
* Maximum number of results to return
*/
resultLimit?: number
/**
* Whether to return all items when search is empty
*/
matchAllWhenSearchEmpty?: boolean
/**
* Debounce delay in milliseconds for search input
*/
debounceMs?: number
/**
* Minimum search length before triggering search
*/
minSearchLength?: number
}
```
## Configuration Examples
### Basic Search
```typescript
const options = {
fuseOptions: {
keys: ['name', 'description'],
threshold: 0.3,
},
}
```
### Advanced Search
```typescript
const options = {
fuseOptions: {
keys: [
{ name: 'name', weight: 0.7 },
{ name: 'description', weight: 0.3 },
{ name: 'category', weight: 0.2 },
],
threshold: 0.2,
distance: 100,
ignoreLocation: true,
useExtendedSearch: false,
minMatchCharLength: 2,
shouldSort: true,
findAllMatches: false,
location: 0,
isCaseSensitive: false,
},
resultLimit: 20,
matchAllWhenSearchEmpty: true,
minSearchLength: 2,
}
```
### Search with Weighted Keys
```typescript
const options = {
fuseOptions: {
keys: [
{ name: 'title', weight: 0.8 },
{ name: 'content', weight: 0.5 },
{ name: 'tags', weight: 0.3 },
],
threshold: 0.4,
},
}
```
## Performance Tips
1. **Use `resultLimit`** to limit the number of results for better performance
2. **Set appropriate `threshold`** values (0.0 = perfect match, 1.0 = match anything)
3. **Use weighted keys** to prioritize certain fields
4. **Consider `minSearchLength`** to avoid searching on very short queries
5. **Use `ignoreLocation: true`** for better performance when location doesn't matter
## Examples
See `FuzzySearchDemo.vue` for a complete example of how to use the fuzzy search functionality.

View file

@ -0,0 +1,2 @@
export { default as FuzzySearch } from './FuzzySearch.vue'
export { useFuzzySearch, type FuzzySearchOptions, type UseFuzzySearchReturn } from '@/composables/useFuzzySearch'