commit 598617025f5d2f581664e81788837fcf224aad3c Author: oliviamn Date: Thu May 15 10:14:45 2025 +0800 Initial commit diff --git a/app.js b/app.js new file mode 100644 index 0000000..3fef1f6 --- /dev/null +++ b/app.js @@ -0,0 +1,114 @@ +// --- Configuration --- +// IMPORTANT: For production, use a SEARCH-ONLY API Key. +// NEVER expose your Admin API Key in client-side code. +// Best practice is to proxy requests through your backend +// or use Typesense Scoped Search Keys. +const TYPESENSE_CONFIG = { + nodes: [{ + host: '192.168.2.203', // Or your Typesense host + port: '8108', + protocol: 'http' + }], + apiKey: 'imdiowenxodjioqwenlj', // Generate a search-only API key in Typesense + connectionTimeoutSeconds: 2 +}; + +const typesenseClient = new Typesense.Client(TYPESENSE_CONFIG); + +// --- DOM Elements --- +const searchInput = document.getElementById('user-search-input'); +const suggestionsList = document.getElementById('suggestions-list'); +const selectedUserIdEl = document.getElementById('selected-user-id'); +const selectedUserNameEl = document.getElementById('selected-user-name'); + +// --- Debounce Function (to limit API calls) --- +let debounceTimer; +function debounce(func, delay) { + return function(...args) { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => func.apply(this, args), delay); + }; +} + +// --- Fetch and Display Suggestions --- +async function fetchSuggestions(query) { + if (!query.trim()) { + suggestionsList.innerHTML = ''; + suggestionsList.style.display = 'none'; + return; + } + + const searchParameters = { + 'q': query, + 'query_by': 'user_name', // Field to search in + 'per_page': 5, // Number of suggestions to show + // 'sort_by': '_text_match:desc', // Default, or 'user_name_lowercase_sort:asc' if you use that field + 'infix': 'always', // Ensure infix search is active if not default for the field + // 'filter_by': 'is_active:=true' // Example: if you have an is_active boolean field + }; + + try { + const searchResults = await typesenseClient.collections('users').documents().search(searchParameters); + displaySuggestions(searchResults.hits || []); + } catch (error) { + console.error('Typesense search error:', error); + suggestionsList.innerHTML = '
  • Error fetching suggestions
  • '; + suggestionsList.style.display = 'block'; + } +} + +function displaySuggestions(hits) { + suggestionsList.innerHTML = ''; // Clear previous suggestions + if (hits.length === 0) { + suggestionsList.style.display = 'none'; + return; + } + + hits.forEach(hit => { + const user = hit.document; // The actual document data + const li = document.createElement('li'); + + // Create a highlighted version of the name if available + let displayName = user.user_name; + if (hit.highlights && hit.highlights.length > 0) { + const nameHighlight = hit.highlights.find(h => h.field === 'user_name'); + if (nameHighlight && nameHighlight.snippet) { + displayName = nameHighlight.snippet; // Use Typesense's highlighted version + } + } + li.innerHTML = displayName; // Display name (can be highlighted) + + // Store full user data (or just ID) for selection + li.dataset.userId = user.user_id; + li.dataset.userName = user.user_name; // Store the original, non-highlighted name + + li.addEventListener('click', () => { + searchInput.value = user.user_name; // Fill input with selected name + selectedUserIdEl.textContent = user.user_id; + selectedUserNameEl.textContent = user.user_name; + suggestionsList.innerHTML = ''; // Clear suggestions + suggestionsList.style.display = 'none'; + + // You now have user.user_id and user.user_name + console.log('Selected:', { id: user.user_id, name: user.user_name }); + // Here, you might want to submit a form, make another API call, etc. + }); + suggestionsList.appendChild(li); + }); + suggestionsList.style.display = 'block'; +} + +// --- Event Listeners --- +searchInput.addEventListener('input', debounce((event) => { + fetchSuggestions(event.target.value); +}, 300)); // 300ms debounce + +// Optional: Hide suggestions when clicking outside +document.addEventListener('click', (event) => { + if (!searchInput.contains(event.target) && !suggestionsList.contains(event.target)) { + suggestionsList.style.display = 'none'; + } +}); + +console.log("Autocomplete App Initialized. Make sure your Typesense server is running and accessible."); +console.log("And ensure you've replaced 'YOUR_SEARCH_ONLY_API_KEY' with a valid key."); \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..4f239f2 --- /dev/null +++ b/index.html @@ -0,0 +1,42 @@ + + + + + + Typesense Autocomplete + + + +

    User Autocomplete

    + + + + +
    +

    Selected User ID: N/A

    +

    Selected User Name: N/A

    +
    + + + + + \ No newline at end of file diff --git a/typesense_data_add.py b/typesense_data_add.py new file mode 100644 index 0000000..e8830c2 --- /dev/null +++ b/typesense_data_add.py @@ -0,0 +1,69 @@ +# Using Python client for schema creation +import typesense + +# Sample user data +users_data = [ + {'user_id': 'uid101', 'user_name': 'Alice Wonderland'}, + {'user_id': 'uid102', 'user_name': 'Bob The Builder'}, + {'user_id': 'uid103', 'user_name': 'Charlie Chaplin'}, + {'user_id': 'uid104', 'user_name': 'Diana Prince'}, + {'user_id': 'uid105', 'user_name': 'Edward Scissorhands'}, + {'user_id': 'uid106', 'user_name': 'alice cooper'}, # For case testing + {'user_id': 'uid107', 'user_name': '任小寅'}, + {'user_id': 'uid108', 'user_name': '任大寅'}, + {'user_id': 'uid109', 'user_name': '张三'}, + {'user_id': 'uid110', 'user_name': '李四'}, + {'user_id': 'uid111', 'user_name': '王五'}, + {'user_id': 'uid112', 'user_name': '赵六'}, + {'user_id': 'uid113', 'user_name': '孙七'}, + {'user_id': 'uid114', 'user_name': '周八'}, + {'user_id': 'uid115', 'user_name': '吴九'}, + {'user_id': 'uid116', 'user_name': '郑十'}, + +] +users_schema = { + 'name': 'users', + 'fields': [ + {'name': 'user_id', 'type': 'string', 'index': True, 'facet': False}, + {'name': 'user_name', 'type': 'string', 'index': True, 'facet': False, 'infix': True}, + # Optional: for cleaner prefix sorting if needed, otherwise _text_match often suffices + {'name': 'user_name_lowercase_sort', 'type': 'string', 'sort': True, 'optional': True} + ] +} +client = typesense.Client({ + 'nodes': [{ + 'host': '192.168.2.203', # Or your Typesense host + 'port': '8108', + 'protocol': 'http' + }], + 'api_key': 'imdiowenxodjioqwenlj', # Use admin key for schema operations + 'connection_timeout_seconds': 2 +}) + +# Prepare documents for Typesense (add the optional sort field if you defined it) +documents_to_index = [] +for user in users_data: + doc = { + 'user_id': user['user_id'], + 'user_name': user['user_name'], + } + # If using the optional sort field: + if 'user_name_lowercase_sort' in [f['name'] for f in users_schema['fields']]: + doc['user_name_lowercase_sort'] = user['user_name'].lower() + documents_to_index.append(doc) + + +# Index the documents (batch import is recommended for larger datasets) +try: + results = client.collections['users'].documents.import_(documents_to_index, {'action': 'upsert'}) + print("Documents indexed successfully.") + # print(results) # You can inspect the results if needed + successful_imports = sum(1 for r in results if r.get('success', False)) + print(f"Successfully imported {successful_imports}/{len(results)} documents.") + for r in results: + if not r.get('success', False): + print(f"Failed import: {r.get('error', 'Unknown error')} - Document: {r.get('document', '{}')}") + + +except Exception as e: + print(f"Error indexing documents: {e}") \ No newline at end of file diff --git a/typesense_schema.py b/typesense_schema.py new file mode 100644 index 0000000..ec70328 --- /dev/null +++ b/typesense_schema.py @@ -0,0 +1,37 @@ +# Using Python client for schema creation +import typesense + +client = typesense.Client({ + 'nodes': [{ + 'host': '192.168.2.203', # Or your Typesense host + 'port': '8108', + 'protocol': 'http' + }], + 'api_key': 'imdiowenxodjioqwenlj', # Use admin key for schema operations + 'connection_timeout_seconds': 2 +}) + +# Define the schema +users_schema = { + 'name': 'users', + 'fields': [ + {'name': 'user_id', 'type': 'string', 'index': True, 'facet': False}, + {'name': 'user_name', 'type': 'string', 'index': True, 'facet': False, 'infix': True}, + # Optional: for cleaner prefix sorting if needed, otherwise _text_match often suffices + {'name': 'user_name_lowercase_sort', 'type': 'string', 'sort': True, 'optional': True} + ] +} + +# Delete collection if it already exists (for development) +try: + client.collections['users'].delete() + print("Collection 'users' deleted.") +except Exception as e: + print(f"Collection 'users' does not exist or could not be deleted: {e}") + +# Create the collection +try: + client.collections.create(users_schema) + print("Collection 'users' created successfully.") +except Exception as e: + print(f"Error creating collection 'users': {e}") \ No newline at end of file