worker-1: completed task task-content-processing [failed]
This commit is contained in:
parent
23a9dab8fd
commit
4460724fdb
@ -15,6 +15,7 @@ description: |
|
||||
- Search implementation
|
||||
- Content loading system
|
||||
assigned_to: worker-1
|
||||
status: in_progress
|
||||
status: failed
|
||||
result: 'qwen: agent loop: agent exceeded max steps (20)'
|
||||
created_at: 2026-04-08T00:01:49.285216405Z
|
||||
updated_at: 2026-04-08T00:05:44.477892521Z
|
||||
updated_at: 2026-04-08T00:09:25.144326221Z
|
||||
|
||||
@ -1,12 +1,21 @@
|
||||
import React, { useState } from 'react';
|
||||
import './SearchBar.css';
|
||||
|
||||
const SearchBar = ({ onSearch }) => {
|
||||
const SearchBar = ({ onSearch, categories }) => {
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedCategory, setSelectedCategory] = useState('All');
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
onSearch(searchTerm);
|
||||
onSearch(searchTerm, selectedCategory);
|
||||
};
|
||||
|
||||
const handleSearchChange = (e) => {
|
||||
setSearchTerm(e.target.value);
|
||||
};
|
||||
|
||||
const handleCategoryChange = (e) => {
|
||||
setSelectedCategory(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -16,8 +25,14 @@ const SearchBar = ({ onSearch }) => {
|
||||
type="text"
|
||||
placeholder="Search jargon terms..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
<select value={selectedCategory} onChange={handleCategoryChange}>
|
||||
<option value="All">All Categories</option>
|
||||
{categories && categories.map(category => (
|
||||
<option key={category} value={category}>{category}</option>
|
||||
))}
|
||||
</select>
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
172
src/models/JargonEntry.js
Normal file
172
src/models/JargonEntry.js
Normal file
@ -0,0 +1,172 @@
|
||||
/**
|
||||
* Data model for Jargon File entries
|
||||
*/
|
||||
|
||||
export class JargonEntry {
|
||||
constructor(data) {
|
||||
this.id = data.id || Date.now() + Math.floor(Math.random() * 1000);
|
||||
this.term = data.term || '';
|
||||
this.definition = data.definition || '';
|
||||
this.category = data.category || 'Uncategorized';
|
||||
this.relatedTerms = data.relatedTerms || [];
|
||||
this.dateAdded = data.dateAdded || new Date().toISOString().split('T')[0];
|
||||
this.dateModified = data.dateModified || new Date().toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the entry has required fields
|
||||
* @returns {boolean} True if valid
|
||||
*/
|
||||
isValid() {
|
||||
return this.term && this.definition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the entry with new data
|
||||
* @param {Object} newData - New data to update
|
||||
*/
|
||||
update(newData) {
|
||||
Object.assign(this, newData);
|
||||
this.dateModified = new Date().toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the entry to a plain object
|
||||
* @returns {Object} Plain object representation
|
||||
*/
|
||||
toObject() {
|
||||
return {
|
||||
id: this.id,
|
||||
term: this.term,
|
||||
definition: this.definition,
|
||||
category: this.category,
|
||||
relatedTerms: this.relatedTerms,
|
||||
dateAdded: this.dateAdded,
|
||||
dateModified: this.dateModified
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted string representation
|
||||
* @returns {string} Formatted string
|
||||
*/
|
||||
toString() {
|
||||
return `${this.term}: ${this.definition.substring(0, 100)}...`;
|
||||
}
|
||||
}
|
||||
|
||||
export class JargonEntryCollection {
|
||||
constructor(entries = []) {
|
||||
this.entries = entries.map(entry => new JargonEntry(entry));
|
||||
this.categories = this._buildCategories();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds categories from entries
|
||||
* @private
|
||||
*/
|
||||
_buildCategories() {
|
||||
const categories = {};
|
||||
this.entries.forEach(entry => {
|
||||
const category = entry.category;
|
||||
if (!categories[category]) {
|
||||
categories[category] = [];
|
||||
}
|
||||
categories[category].push(entry);
|
||||
});
|
||||
return categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new entry
|
||||
* @param {Object} data - Entry data
|
||||
* @returns {JargonEntry} The created entry
|
||||
*/
|
||||
addEntry(data) {
|
||||
const entry = new JargonEntry(data);
|
||||
if (entry.isValid()) {
|
||||
this.entries.push(entry);
|
||||
this.categories = this._buildCategories(); // Rebuild categories
|
||||
return entry;
|
||||
}
|
||||
throw new Error('Invalid entry data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an entry by ID
|
||||
* @param {number} id - Entry ID
|
||||
* @returns {JargonEntry} The entry or undefined
|
||||
*/
|
||||
getEntryById(id) {
|
||||
return this.entries.find(entry => entry.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an entry by term
|
||||
* @param {string} term - Term to search for
|
||||
* @returns {JargonEntry} The entry or undefined
|
||||
*/
|
||||
getEntryByTerm(term) {
|
||||
return this.entries.find(entry =>
|
||||
entry.term.toLowerCase() === term.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets entries by category
|
||||
* @param {string} category - Category name
|
||||
* @returns {Array} Array of entries in the category
|
||||
*/
|
||||
getEntriesByCategory(category) {
|
||||
return this.entries.filter(entry =>
|
||||
entry.category.toLowerCase() === category.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all categories
|
||||
* @returns {Array} Array of category names
|
||||
*/
|
||||
getCategories() {
|
||||
return Object.keys(this.categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all entries
|
||||
* @returns {Array} Array of all entries
|
||||
*/
|
||||
getAllEntries() {
|
||||
return this.entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an entry
|
||||
* @param {number} id - Entry ID to remove
|
||||
* @returns {boolean} True if removed
|
||||
*/
|
||||
removeEntry(id) {
|
||||
const index = this.entries.findIndex(entry => entry.id === id);
|
||||
if (index !== -1) {
|
||||
this.entries.splice(index, 1);
|
||||
this.categories = this._buildCategories(); // Rebuild categories
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an entry
|
||||
* @param {number} id - Entry ID to update
|
||||
* @param {Object} newData - New data
|
||||
* @returns {JargonEntry} Updated entry or undefined
|
||||
*/
|
||||
updateEntry(id, newData) {
|
||||
const entry = this.getEntryById(id);
|
||||
if (entry) {
|
||||
entry.update(newData);
|
||||
this.categories = this._buildCategories(); // Rebuild categories
|
||||
return entry;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,32 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import EntryList from '../components/EntryList';
|
||||
import SearchBar from '../components/SearchBar';
|
||||
import { contentLoader } from '../services/contentLoader';
|
||||
import { searchEntries } from '../utils/searchUtils';
|
||||
import './Home.css';
|
||||
|
||||
const Home = () => {
|
||||
const [entries, setEntries] = useState([]);
|
||||
const [filteredEntries, setFilteredEntries] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// In a real app, this would fetch from an API or data file
|
||||
// For now, we'll use mock data
|
||||
// Load entries from content loader
|
||||
const loadEntries = async () => {
|
||||
try {
|
||||
// Load the existing data from jargonEntries.json
|
||||
const response = await fetch('/src/data/jargonEntries.json');
|
||||
const data = await response.json();
|
||||
|
||||
// Store in content loader for future use
|
||||
await contentLoader.loadContent(JSON.stringify(data), 'json');
|
||||
|
||||
setEntries(data);
|
||||
setFilteredEntries(data);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Error loading entries:', error);
|
||||
// Fallback to mock data
|
||||
const mockEntries = [
|
||||
{
|
||||
id: 1,
|
||||
@ -33,6 +50,11 @@ const Home = () => {
|
||||
|
||||
setEntries(mockEntries);
|
||||
setFilteredEntries(mockEntries);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadEntries();
|
||||
}, []);
|
||||
|
||||
const handleSearch = (searchTerm) => {
|
||||
@ -41,14 +63,22 @@ const Home = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const filtered = entries.filter(entry =>
|
||||
entry.term.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
entry.definition.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const filtered = searchEntries(entries, searchTerm);
|
||||
setFilteredEntries(filtered);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="home">
|
||||
<div className="hero">
|
||||
<h2>Explore Hacker Culture</h2>
|
||||
<p>Discover the terminology that defines computer hacker culture</p>
|
||||
</div>
|
||||
<p>Loading jargon entries...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="home">
|
||||
<div className="hero">
|
||||
|
||||
57
src/pages/SearchResults.js
Normal file
57
src/pages/SearchResults.js
Normal file
@ -0,0 +1,57 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import EntryList from '../components/EntryList';
|
||||
import SearchBar from '../components/SearchBar';
|
||||
import { contentLoader } from '../services/contentLoader';
|
||||
import { searchEntries } from '../utils/searchUtils';
|
||||
import './SearchResults.css';
|
||||
|
||||
const SearchResults = () => {
|
||||
const [entries, setEntries] = useState([]);
|
||||
const [filteredEntries, setFilteredEntries] = useState([]);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
// Load entries from content loader
|
||||
const loadEntries = async () => {
|
||||
// In a real app, we'd load from actual data source
|
||||
// For now, using the existing data in the data file
|
||||
const entriesData = contentLoader.getAllEntries();
|
||||
setEntries(entriesData);
|
||||
setFilteredEntries(entriesData);
|
||||
};
|
||||
|
||||
loadEntries();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Handle search from URL parameters
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const search = urlParams.get('q');
|
||||
if (search) {
|
||||
setSearchTerm(search);
|
||||
const results = searchEntries(entries, search);
|
||||
setFilteredEntries(results);
|
||||
}
|
||||
}, [location.search, entries]);
|
||||
|
||||
const handleSearch = (searchTerm) => {
|
||||
setSearchTerm(searchTerm);
|
||||
const results = searchEntries(entries, searchTerm);
|
||||
setFilteredEntries(results);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="search-results">
|
||||
<div className="hero">
|
||||
<h2>Search Results</h2>
|
||||
<p>Find jargon terms and definitions</p>
|
||||
</div>
|
||||
<SearchBar onSearch={handleSearch} />
|
||||
<EntryList entries={filteredEntries} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchResults;
|
||||
150
src/services/contentLoader.js
Normal file
150
src/services/contentLoader.js
Normal file
@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Content Loading System for Jargon File
|
||||
* Handles loading, parsing, and managing jargon entries
|
||||
*/
|
||||
|
||||
import { parseContent, structureData } from '../utils/contentParser';
|
||||
import { JargonEntryCollection } from '../models/JargonEntry';
|
||||
|
||||
/**
|
||||
* Content Loader Class
|
||||
*/
|
||||
export class ContentLoader {
|
||||
constructor() {
|
||||
this.entries = [];
|
||||
this.collection = null;
|
||||
this.isLoaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load content from a source
|
||||
* @param {string} source - Source content
|
||||
* @param {string} format - Format of the source ('json', 'text', 'html')
|
||||
* @returns {Promise<boolean>} True if successful
|
||||
*/
|
||||
async loadContent(source, format = 'json') {
|
||||
try {
|
||||
const parsedEntries = parseContent(source, format);
|
||||
this.entries = parsedEntries;
|
||||
this.collection = new JargonEntryCollection(parsedEntries);
|
||||
this.isLoaded = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error loading content:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load content from a file (browser environment)
|
||||
* @param {File} file - File to load
|
||||
* @returns {Promise<boolean>} True if successful
|
||||
*/
|
||||
async loadFromFile(file) {
|
||||
try {
|
||||
const reader = new FileReader();
|
||||
const content = await new Promise((resolve, reject) => {
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.onerror = (e) => reject(e);
|
||||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
const format = this._detectFormat(file.name);
|
||||
return await this.loadContent(content, format);
|
||||
} catch (error) {
|
||||
console.error('Error loading file:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load content from URL
|
||||
* @param {string} url - URL to fetch content from
|
||||
* @returns {Promise<boolean>} True if successful
|
||||
*/
|
||||
async loadFromURL(url) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const content = await response.text();
|
||||
const format = this._detectFormat(url);
|
||||
return await this.loadContent(content, format);
|
||||
} catch (error) {
|
||||
console.error('Error loading from URL:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect format from filename or URL
|
||||
* @private
|
||||
* @param {string} source - Source name or URL
|
||||
* @returns {string} Detected format
|
||||
*/
|
||||
_detectFormat(source) {
|
||||
if (source.endsWith('.json')) {
|
||||
return 'json';
|
||||
} else if (source.endsWith('.txt')) {
|
||||
return 'text';
|
||||
} else if (source.endsWith('.html') || source.endsWith('.htm')) {
|
||||
return 'html';
|
||||
}
|
||||
return 'json'; // Default
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all loaded entries
|
||||
* @returns {Array} Array of entries
|
||||
*/
|
||||
getEntries() {
|
||||
return this.entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entry collection
|
||||
* @returns {JargonEntryCollection} The collection
|
||||
*/
|
||||
getCollection() {
|
||||
return this.collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content is loaded
|
||||
* @returns {boolean} True if loaded
|
||||
*/
|
||||
isContentLoaded() {
|
||||
return this.isLoaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories
|
||||
* @returns {Array} Array of categories
|
||||
*/
|
||||
getCategories() {
|
||||
return this.collection ? this.collection.getCategories() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entries by category
|
||||
* @param {string} category - Category name
|
||||
* @returns {Array} Array of entries
|
||||
*/
|
||||
getEntriesByCategory(category) {
|
||||
return this.collection ? this.collection.getEntriesByCategory(category) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all entries (alias for getEntries)
|
||||
* @returns {Array} Array of all entries
|
||||
*/
|
||||
getAllEntries() {
|
||||
return this.collection ? this.collection.getAllEntries() : [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance of ContentLoader
|
||||
*/
|
||||
export const contentLoader = new ContentLoader();
|
||||
149
src/utils/contentParser.js
Normal file
149
src/utils/contentParser.js
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Content Parser for Jargon File entries
|
||||
* This utility handles parsing content from various formats into structured data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parse JSON format jargon entries
|
||||
* @param {string} content - JSON string containing jargon entries
|
||||
* @returns {Array} Array of parsed jargon entries
|
||||
*/
|
||||
export const parseJSON = (content) => {
|
||||
try {
|
||||
const data = JSON.parse(content);
|
||||
return data.map(entry => ({
|
||||
id: entry.id,
|
||||
term: entry.term,
|
||||
definition: entry.definition,
|
||||
category: entry.category || 'Uncategorized',
|
||||
relatedTerms: entry.relatedTerms || [],
|
||||
dateAdded: entry.dateAdded || new Date().toISOString().split('T')[0]
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error parsing JSON:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse text format jargon entries (simple format)
|
||||
* @param {string} content - Text content with entries
|
||||
* @returns {Array} Array of parsed jargon entries
|
||||
*/
|
||||
export const parseText = (content) => {
|
||||
// Simple text parsing logic
|
||||
const entries = [];
|
||||
const lines = content.split('\n');
|
||||
let currentEntry = null;
|
||||
|
||||
lines.forEach(line => {
|
||||
if (line.trim().startsWith('TERM:')) {
|
||||
if (currentEntry) {
|
||||
entries.push(currentEntry);
|
||||
}
|
||||
currentEntry = {
|
||||
term: line.replace('TERM:', '').trim(),
|
||||
definition: '',
|
||||
category: 'Uncategorized',
|
||||
relatedTerms: [],
|
||||
dateAdded: new Date().toISOString().split('T')[0]
|
||||
};
|
||||
} else if (line.trim().startsWith('DEFINITION:')) {
|
||||
if (currentEntry) {
|
||||
currentEntry.definition = line.replace('DEFINITION:', '').trim();
|
||||
}
|
||||
} else if (line.trim().startsWith('CATEGORY:')) {
|
||||
if (currentEntry) {
|
||||
currentEntry.category = line.replace('CATEGORY:', '').trim();
|
||||
}
|
||||
} else if (line.trim().startsWith('RELATED:')) {
|
||||
if (currentEntry) {
|
||||
currentEntry.relatedTerms = line.replace('RELATED:', '').trim().split(',').map(term => term.trim());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (currentEntry) {
|
||||
entries.push(currentEntry);
|
||||
}
|
||||
|
||||
return entries;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse HTML format jargon entries
|
||||
* @param {string} content - HTML content containing jargon entries
|
||||
* @returns {Array} Array of parsed jargon entries
|
||||
*/
|
||||
export const parseHTML = (content) => {
|
||||
// For now, we'll provide a basic structure - in practice this would parse HTML
|
||||
// This is a simplified version that would need to be expanded
|
||||
const entries = [];
|
||||
|
||||
// This is a placeholder - would need more sophisticated HTML parsing
|
||||
// For the purpose of this implementation, we'll just return empty entries
|
||||
// In a real implementation, we would parse HTML tags like <term>, <definition>, etc.
|
||||
|
||||
return entries;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generic parse function that tries to determine the format and parse accordingly
|
||||
* @param {string} content - Content to parse
|
||||
* @param {string} format - Format to parse as ('json', 'text', 'html')
|
||||
* @returns {Array} Array of parsed jargon entries
|
||||
*/
|
||||
export const parseContent = (content, format) => {
|
||||
switch (format.toLowerCase()) {
|
||||
case 'json':
|
||||
return parseJSON(content);
|
||||
case 'text':
|
||||
return parseText(content);
|
||||
case 'html':
|
||||
return parseHTML(content);
|
||||
default:
|
||||
// Try to auto-detect format
|
||||
try {
|
||||
JSON.parse(content);
|
||||
return parseJSON(content);
|
||||
} catch {
|
||||
return parseText(content);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure data for efficient storage and retrieval
|
||||
* @param {Array} entries - Array of jargon entries
|
||||
* @returns {Object} Structured data with categories and indexes
|
||||
*/
|
||||
export const structureData = (entries) => {
|
||||
const categories = {};
|
||||
const termIndex = {};
|
||||
const categoryIndex = {};
|
||||
|
||||
entries.forEach(entry => {
|
||||
// Build category mapping
|
||||
const category = entry.category;
|
||||
if (!categories[category]) {
|
||||
categories[category] = [];
|
||||
}
|
||||
categories[category].push(entry.id);
|
||||
|
||||
// Build term index for fast lookup
|
||||
termIndex[entry.term.toLowerCase()] = entry.id;
|
||||
|
||||
// Build category index
|
||||
if (!categoryIndex[category]) {
|
||||
categoryIndex[category] = [];
|
||||
}
|
||||
categoryIndex[category].push(entry.id);
|
||||
});
|
||||
|
||||
return {
|
||||
entries,
|
||||
categories,
|
||||
termIndex,
|
||||
categoryIndex
|
||||
};
|
||||
};
|
||||
108
src/utils/searchUtils.js
Normal file
108
src/utils/searchUtils.js
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Search Utilities for Jargon File
|
||||
* Handles searching through jargon entries
|
||||
*/
|
||||
|
||||
/**
|
||||
* Search through jargon entries
|
||||
* @param {Array} entries - Array of jargon entries
|
||||
* @param {string} searchTerm - Search term
|
||||
* @returns {Array} Filtered entries
|
||||
*/
|
||||
export const searchEntries = (entries, searchTerm) => {
|
||||
if (!searchTerm || searchTerm.trim() === '') {
|
||||
return entries;
|
||||
}
|
||||
|
||||
const term = searchTerm.toLowerCase().trim();
|
||||
|
||||
return entries.filter(entry => {
|
||||
// Search in term, definition, and related terms
|
||||
const termMatch = entry.term.toLowerCase().includes(term);
|
||||
const definitionMatch = entry.definition.toLowerCase().includes(term);
|
||||
const relatedTermsMatch = entry.relatedTerms.some(term =>
|
||||
term.toLowerCase().includes(term)
|
||||
);
|
||||
|
||||
return termMatch || definitionMatch || relatedTermsMatch;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Advanced search with category filtering
|
||||
* @param {Array} entries - Array of jargon entries
|
||||
* @param {string} searchTerm - Search term
|
||||
* @param {string} category - Category to filter by (optional)
|
||||
* @returns {Array} Filtered entries
|
||||
*/
|
||||
export const advancedSearch = (entries, searchTerm, category = null) => {
|
||||
let results = entries;
|
||||
|
||||
// Apply category filter if specified
|
||||
if (category && category !== 'All') {
|
||||
results = results.filter(entry =>
|
||||
entry.category.toLowerCase() === category.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Apply search term filter
|
||||
if (searchTerm && searchTerm.trim() !== '') {
|
||||
results = searchEntries(results, searchTerm);
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get search suggestions based on current entries
|
||||
* @param {Array} entries - Array of jargon entries
|
||||
* @param {string} searchTerm - Current search term
|
||||
* @returns {Array} Suggested terms
|
||||
*/
|
||||
export const getSearchSuggestions = (entries, searchTerm) => {
|
||||
if (!searchTerm || searchTerm.trim() === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const term = searchTerm.toLowerCase().trim();
|
||||
const suggestions = new Set();
|
||||
|
||||
entries.forEach(entry => {
|
||||
// Add exact matches
|
||||
if (entry.term.toLowerCase().startsWith(term)) {
|
||||
suggestions.add(entry.term);
|
||||
}
|
||||
|
||||
// Add partial matches in definition
|
||||
if (entry.definition.toLowerCase().includes(term)) {
|
||||
// Extract potential terms from definition for suggestions
|
||||
// This is a simple implementation - more sophisticated would use NLP
|
||||
const words = entry.definition.toLowerCase().split(/\s+/);
|
||||
words.forEach(word => {
|
||||
if (word.startsWith(term) && word.length > 2) {
|
||||
suggestions.add(word);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(suggestions).slice(0, 5); // Return top 5 suggestions
|
||||
};
|
||||
|
||||
/**
|
||||
* Get search statistics
|
||||
* @param {Array} entries - Array of jargon entries
|
||||
* @param {string} searchTerm - Search term
|
||||
* @returns {Object} Search statistics
|
||||
*/
|
||||
export const getSearchStats = (entries, searchTerm) => {
|
||||
const totalEntries = entries.length;
|
||||
const filteredEntries = searchEntries(entries, searchTerm);
|
||||
|
||||
return {
|
||||
totalEntries,
|
||||
filteredEntries: filteredEntries.length,
|
||||
searchTerm,
|
||||
searchRatio: totalEntries > 0 ? (filteredEntries.length / totalEntries * 100).toFixed(1) : 0
|
||||
};
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user