diff --git a/.mimzi/tasks/task-content-processing.yaml b/.mimzi/tasks/task-content-processing.yaml
index fc89871..02d1450 100644
--- a/.mimzi/tasks/task-content-processing.yaml
+++ b/.mimzi/tasks/task-content-processing.yaml
@@ -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
diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js
index b5e8111..533d357 100644
--- a/src/components/SearchBar.js
+++ b/src/components/SearchBar.js
@@ -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}
/>
+
diff --git a/src/models/JargonEntry.js b/src/models/JargonEntry.js
new file mode 100644
index 0000000..0036b95
--- /dev/null
+++ b/src/models/JargonEntry.js
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/pages/Home.js b/src/pages/Home.js
index 808bf59..07432bf 100644
--- a/src/pages/Home.js
+++ b/src/pages/Home.js
@@ -1,38 +1,60 @@
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
- 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"
+ // 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,
+ 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);
}
- ];
+ };
- setEntries(mockEntries);
- setFilteredEntries(mockEntries);
+ 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 (
+
+
+
Explore Hacker Culture
+
Discover the terminology that defines computer hacker culture
+
+
Loading jargon entries...
+
+ );
+ }
+
return (
diff --git a/src/pages/SearchResults.js b/src/pages/SearchResults.js
new file mode 100644
index 0000000..f3caad2
--- /dev/null
+++ b/src/pages/SearchResults.js
@@ -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 (
+
+
+
Search Results
+
Find jargon terms and definitions
+
+
+
+
+ );
+};
+
+export default SearchResults;
\ No newline at end of file
diff --git a/src/services/contentLoader.js b/src/services/contentLoader.js
new file mode 100644
index 0000000..a3d155d
--- /dev/null
+++ b/src/services/contentLoader.js
@@ -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
} 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} 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} 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();
\ No newline at end of file
diff --git a/src/utils/contentParser.js b/src/utils/contentParser.js
new file mode 100644
index 0000000..9adc189
--- /dev/null
+++ b/src/utils/contentParser.js
@@ -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 , , 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
+ };
+};
\ No newline at end of file
diff --git a/src/utils/searchUtils.js b/src/utils/searchUtils.js
new file mode 100644
index 0000000..599fc93
--- /dev/null
+++ b/src/utils/searchUtils.js
@@ -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
+ };
+};
\ No newline at end of file