worker-1: completed task task-content-processing [failed]
This commit is contained in:
parent
23a9dab8fd
commit
4460724fdb
@ -15,6 +15,7 @@ description: |
|
|||||||
- Search implementation
|
- Search implementation
|
||||||
- Content loading system
|
- Content loading system
|
||||||
assigned_to: worker-1
|
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
|
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 React, { useState } from 'react';
|
||||||
import './SearchBar.css';
|
import './SearchBar.css';
|
||||||
|
|
||||||
const SearchBar = ({ onSearch }) => {
|
const SearchBar = ({ onSearch, categories }) => {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState('All');
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSearch(searchTerm);
|
onSearch(searchTerm, selectedCategory);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchChange = (e) => {
|
||||||
|
setSearchTerm(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCategoryChange = (e) => {
|
||||||
|
setSelectedCategory(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -16,8 +25,14 @@ const SearchBar = ({ onSearch }) => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Search jargon terms..."
|
placeholder="Search jargon terms..."
|
||||||
value={searchTerm}
|
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>
|
<button type="submit">Search</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</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,38 +1,60 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import EntryList from '../components/EntryList';
|
import EntryList from '../components/EntryList';
|
||||||
import SearchBar from '../components/SearchBar';
|
import SearchBar from '../components/SearchBar';
|
||||||
|
import { contentLoader } from '../services/contentLoader';
|
||||||
|
import { searchEntries } from '../utils/searchUtils';
|
||||||
import './Home.css';
|
import './Home.css';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const [entries, setEntries] = useState([]);
|
const [entries, setEntries] = useState([]);
|
||||||
const [filteredEntries, setFilteredEntries] = useState([]);
|
const [filteredEntries, setFilteredEntries] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// In a real app, this would fetch from an API or data file
|
// Load entries from content loader
|
||||||
// For now, we'll use mock data
|
const loadEntries = async () => {
|
||||||
const mockEntries = [
|
try {
|
||||||
{
|
// Load the existing data from jargonEntries.json
|
||||||
id: 1,
|
const response = await fetch('/src/data/jargonEntries.json');
|
||||||
term: "Hacker",
|
const data = await response.json();
|
||||||
definition: "A person who enjoys exploring the details of programmable systems and how to stretch their capabilities.",
|
|
||||||
category: "Core Terms"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
term: "Guru",
|
|
||||||
definition: "A person who has achieved a high level of skill in a particular field or domain.",
|
|
||||||
category: "Core Terms"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
term: "Foo",
|
|
||||||
definition: "A generic term used as a placeholder in examples and documentation.",
|
|
||||||
category: "Technical Terms"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
setEntries(mockEntries);
|
// Store in content loader for future use
|
||||||
setFilteredEntries(mockEntries);
|
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,
|
||||||
|
term: "Hacker",
|
||||||
|
definition: "A person who enjoys exploring the details of programmable systems and how to stretch their capabilities.",
|
||||||
|
category: "Core Terms"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
term: "Guru",
|
||||||
|
definition: "A person who has achieved a high level of skill in a particular field or domain.",
|
||||||
|
category: "Core Terms"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
term: "Foo",
|
||||||
|
definition: "A generic term used as a placeholder in examples and documentation.",
|
||||||
|
category: "Technical Terms"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
setEntries(mockEntries);
|
||||||
|
setFilteredEntries(mockEntries);
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadEntries();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSearch = (searchTerm) => {
|
const handleSearch = (searchTerm) => {
|
||||||
@ -41,14 +63,22 @@ const Home = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtered = entries.filter(entry =>
|
const filtered = searchEntries(entries, searchTerm);
|
||||||
entry.term.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
entry.definition.toLowerCase().includes(searchTerm.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
setFilteredEntries(filtered);
|
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 (
|
return (
|
||||||
<div className="home">
|
<div className="home">
|
||||||
<div className="hero">
|
<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