mnoorchenar commited on
Commit
7b05407
Β·
1 Parent(s): 22685f1

Update 2026-01-30 02:00:47

Browse files
Files changed (2) hide show
  1. MIGRATION_GUIDE.md +0 -332
  2. app.py +123 -722
MIGRATION_GUIDE.md DELETED
@@ -1,332 +0,0 @@
1
- # TextArchive - Migration Guide & New Features
2
-
3
- ## πŸŽ‰ What Changed from "Prompt Manager" to "TextArchive"
4
-
5
- ### Name Change
6
- - **Old**: Prompt Manager
7
- - **New**: TextArchive
8
- - **Why**: More universal - supports all text types, not just prompts
9
-
10
- ### Database Changes
11
- - **Old**: `prompts.db` with table `prompts`
12
- - **New**: `textarchive.db` with table `text_items`
13
- - **Migration**: Automatic! Your old data is preserved and migrated
14
-
15
- ---
16
-
17
- ## ✨ NEW FEATURES IMPLEMENTED
18
-
19
- ### 1. βœ… Easy Content Copying
20
- **The Problem You Had:**
21
- - Hard to copy just the text content
22
- - Would copy entire row with ID, category, section
23
-
24
- **The Solution:**
25
- - Click any row in the table
26
- - Content appears in "Content Preview" box below table
27
- - Click the copy button (πŸ“‹) OR select text and Ctrl+C
28
- - Only the text content is copied - no metadata!
29
-
30
- **How to Use:**
31
- 1. Go to "View & Manage Items" tab
32
- 2. Click on any row
33
- 3. Look at "Content Preview" section below
34
- 4. Click copy button or manually select and copy
35
-
36
- ---
37
-
38
- ### 2. βœ… Auto-Filtering (AJAX-Style)
39
- **The Problem You Had:**
40
- - Had to click "Search" button every time
41
- - Not responsive
42
-
43
- **The Solution:**
44
- - Filters update automatically when you change them
45
- - No search button needed!
46
- - Instant feedback
47
-
48
- **How to Use:**
49
- 1. Just select a category β†’ table updates immediately
50
- 2. Select a section β†’ table updates immediately
51
- 3. Select a content type β†’ table updates immediately
52
- 4. Click "Clear Filters" to reset
53
-
54
- ---
55
-
56
- ### 3. βœ… Fixed Height Table with Scrolling
57
- **The Problem You Had:**
58
- - Table grew huge with many items
59
- - Hard to navigate
60
-
61
- **The Solution:**
62
- - Table has fixed height (400px)
63
- - Scrolls internally when content exceeds height
64
- - Maintains clean, consistent interface
65
-
66
- **Result:**
67
- - Interface stays organized
68
- - Easy to browse large collections
69
- - Better user experience
70
-
71
- ---
72
-
73
- ### 4. βœ… Section Dropdown Filtering
74
- **The Problem You Had:**
75
- - Section dropdown showed ALL sections
76
- - Confusing when you had many categories
77
-
78
- **The Solution:**
79
- - Section dropdown automatically filters by selected category
80
- - Only shows sections that exist in that category
81
- - Much cleaner and more intuitive
82
-
83
- **How It Works:**
84
- 1. Select a category
85
- 2. Section dropdown automatically updates to show only relevant sections
86
- 3. Works in both "Add" and "Edit" modes
87
-
88
- ---
89
-
90
- ### 5. βœ… Category & Section Renaming
91
- **Critical Feature You Were Missing:**
92
- - Could NOT rename categories/sections
93
- - Changing name created a new one, leaving old data orphaned
94
-
95
- **The Solution:**
96
- - New "Manage Categories & Sections" tab
97
- - Rename category β†’ updates ALL items with that category
98
- - Rename section β†’ updates ALL items with that section
99
- - Can rename section globally or within a specific category
100
-
101
- **How to Use:**
102
- 1. Go to "βš™οΈ Manage Categories & Sections" tab
103
- 2. Select category or section to rename
104
- 3. Enter new name
105
- 4. Click rename button
106
- 5. All items are updated automatically!
107
-
108
- ---
109
-
110
- ### 6. βœ… Bulk Deletion
111
- **Critical Feature You Were Missing:**
112
- - Could only delete one item at a time
113
- - No way to clean up entire categories/sections
114
-
115
- **The Solution:**
116
- - Delete entire category (all items in it)
117
- - Delete entire section (all items in it, optionally within a category)
118
- - Confirmation required to prevent accidents
119
- - Shows count of items that will be deleted
120
-
121
- **How to Use:**
122
- 1. Go to "βš™οΈ Manage Categories & Sections" tab
123
- 2. Select category or section to delete
124
- 3. Check the confirmation box
125
- 4. Click delete button
126
- 5. Status shows how many items were deleted
127
-
128
- ---
129
-
130
- ### 7. βœ… Content Type System
131
- **New Capability:**
132
- - Support for 11 different content types
133
- - Each type has icon and file extension
134
- - Better organization
135
-
136
- **Supported Types:**
137
- - πŸ’¬ Prompt (.txt)
138
- - πŸ“ Note (.md)
139
- - 🐍 Python (.py)
140
- - πŸ“œ JavaScript (.js)
141
- - πŸ“ LaTeX (.tex)
142
- - πŸ—„οΈ SQL (.sql)
143
- - πŸ’» Bash (.sh)
144
- - βš™οΈ Function (.txt)
145
- - 🌐 HTML (.html)
146
- - 🎨 CSS (.css)
147
- - πŸ“„ Other (.txt)
148
-
149
- **How to Use:**
150
- 1. When adding or editing, select content type
151
- 2. Type appears with icon in table view
152
- 3. File extension is automatically set
153
-
154
- ---
155
-
156
- ### 8. βœ… Title Field (Optional)
157
- **New Feature:**
158
- - Optional title field for better identification
159
- - Auto-generates from first 50 characters if left empty
160
- - Makes browsing easier
161
-
162
- **How to Use:**
163
- 1. Leave empty for auto-generation
164
- 2. OR enter custom title for important items
165
-
166
- ---
167
-
168
- ### 9. βœ… Better Statistics
169
- **Improvements:**
170
- - Now shows breakdown by content type
171
- - Shows type icons
172
- - More informative
173
-
174
- ---
175
-
176
- ## πŸ”§ All Fixed Issues
177
-
178
- ### Fixed: Category/Section Renaming
179
- - βœ… Can now rename without creating duplicates
180
- - βœ… All related items update automatically
181
- - βœ… No orphaned data
182
-
183
- ### Fixed: Bulk Operations
184
- - βœ… Can delete entire category
185
- - βœ… Can delete entire section
186
- - βœ… Confirmation prevents accidents
187
-
188
- ### Fixed: Section Dropdown
189
- - βœ… Filters by selected category
190
- - βœ… Only shows relevant sections
191
- - βœ… Much cleaner UX
192
-
193
- ### Fixed: Search UX
194
- - βœ… Auto-filtering (no search button)
195
- - βœ… Instant feedback
196
- - βœ… Better responsiveness
197
-
198
- ### Fixed: Content Viewing
199
- - βœ… Easy copy functionality
200
- - βœ… Content preview panel
201
- - βœ… Copy button for convenience
202
-
203
- ### Fixed: Table Display
204
- - βœ… Fixed height with scrolling
205
- - βœ… Consistent interface size
206
- - βœ… Better navigation
207
-
208
- ---
209
-
210
- ## πŸ“Š Database Migration Details
211
-
212
- ### What Happens Automatically:
213
- 1. App detects old `prompts.db`
214
- 2. Creates new `textarchive.db` schema
215
- 3. Copies all data:
216
- - `id` β†’ `id`
217
- - `category` β†’ `category`
218
- - `section` β†’ `section`
219
- - `prompt_text` β†’ `content`
220
- - First 50 chars β†’ `title`
221
- - Sets `content_type` to "prompt"
222
- - Sets `file_extension` to ".txt"
223
- - Preserves timestamps
224
-
225
- ### Your Data is Safe:
226
- - Old `prompts.db` is NOT deleted
227
- - You can keep both files as backup
228
- - All data is preserved
229
-
230
- ---
231
-
232
- ## 🎯 Comparison: Old vs New
233
-
234
- | Feature | Old (Prompt Manager) | New (TextArchive) |
235
- |---------|---------------------|-------------------|
236
- | **Content Types** | Prompts only | 11 types (prompts, code, notes, etc.) |
237
- | **Rename Category** | ❌ Creates duplicate | βœ… Updates all items |
238
- | **Rename Section** | ❌ Creates duplicate | βœ… Updates all items |
239
- | **Delete Category** | ❌ One by one only | βœ… Bulk delete with confirm |
240
- | **Delete Section** | ❌ One by one only | βœ… Bulk delete with confirm |
241
- | **Section Filter** | Shows all sections | βœ… Filters by category |
242
- | **Search** | Click button | βœ… Auto-updates (AJAX) |
243
- | **Copy Content** | Copy whole row | βœ… Copy button for text only |
244
- | **Table Size** | Grows indefinitely | βœ… Fixed with scrolling |
245
- | **Title Field** | ❌ Not available | βœ… Optional with auto-gen |
246
- | **Statistics** | Basic | βœ… Enhanced with types |
247
-
248
- ---
249
-
250
- ## πŸš€ Quick Start Guide
251
-
252
- ### If You're New:
253
- 1. Go to "Add New Item" tab
254
- 2. Enter category, section, choose type
255
- 3. Enter content
256
- 4. Click "Add Item"
257
- 5. Done!
258
-
259
- ### If You're Migrating:
260
- 1. Run the new app
261
- 2. Your data automatically migrates
262
- 3. Explore new "Manage Categories & Sections" tab
263
- 4. Try the new auto-filtering
264
- 5. Enjoy easy content copying!
265
-
266
- ---
267
-
268
- ## πŸ’‘ Pro Tips
269
-
270
- ### Tip 1: Use Content Types
271
- - Organize code separately from prompts
272
- - Use "note" type for documentation
273
- - Use proper types for better organization
274
-
275
- ### Tip 2: Rename Instead of Recreate
276
- - Use the rename feature in "Manage" tab
277
- - Don't create new category/section with similar name
278
- - Keeps data clean and organized
279
-
280
- ### Tip 3: Use the Content Preview
281
- - Click row to see full content
282
- - Use copy button for quick copying
283
- - No need to edit just to copy
284
-
285
- ### Tip 4: Take Advantage of Auto-Filtering
286
- - Set category filter
287
- - Section dropdown automatically shows only relevant sections
288
- - Quick way to navigate large collections
289
-
290
- ### Tip 5: Regular Backups
291
- - Periodically backup `textarchive.db`
292
- - Keep old `prompts.db` as safety net
293
- - Export important items
294
-
295
- ---
296
-
297
- ## ❓ FAQ
298
-
299
- **Q: Will I lose my old data?**
300
- A: No! The app automatically migrates your data from `prompts.db` to `textarchive.db`. Both files are kept.
301
-
302
- **Q: Can I still use it just for prompts?**
303
- A: Absolutely! Just always select "prompt" as content type. Works exactly like before, with bonus features.
304
-
305
- **Q: What if I have duplicate category names?**
306
- A: The new system handles case-sensitive duplicates better. Use the rename feature to consolidate.
307
-
308
- **Q: Can I undo a bulk delete?**
309
- A: No, that's why confirmation is required. Always backup your database before major deletions.
310
-
311
- **Q: Do I need to click "Search" anymore?**
312
- A: No! Filters update automatically. Just select your filters and the table updates instantly.
313
-
314
- **Q: How do I copy just the text content?**
315
- A: Click on a row β†’ Content appears in preview β†’ Click copy button (or select and Ctrl+C)
316
-
317
- **Q: Can I have the same section name in different categories?**
318
- A: Yes! Sections are independent. "General" can exist in both "Coding" and "Writing".
319
-
320
- ---
321
-
322
- ## 🎊 Conclusion
323
-
324
- TextArchive is a major upgrade that:
325
- - βœ… Fixes all critical issues
326
- - βœ… Adds powerful new features
327
- - βœ… Maintains backward compatibility
328
- - βœ… Improves user experience significantly
329
-
330
- Your old data is safe and automatically migrated. You can now manage text content of any type with powerful organization and management tools!
331
-
332
- Enjoy your new TextArchive! πŸš€
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,816 +1,217 @@
1
- # TextArchive - Universal Text Management System
2
- # new
 
3
  import gradio as gr
4
  import sqlite3
5
  import pandas as pd
6
  from datetime import datetime
7
- import os
8
- import json
9
 
10
- # Database setup
11
- DB_PATH = "textarchive.db"
12
-
13
- # Content types supported
14
- CONTENT_TYPES = {
15
- "prompt": {"extension": ".txt", "icon": "πŸ’¬", "label": "Prompt"},
16
- "note": {"extension": ".md", "icon": "πŸ“", "label": "Note"},
17
- "python": {"extension": ".py", "icon": "🐍", "label": "Python Code"},
18
- "javascript": {"extension": ".js", "icon": "πŸ“œ", "label": "JavaScript"},
19
- "latex": {"extension": ".tex", "icon": "πŸ“", "label": "LaTeX"},
20
- "sql": {"extension": ".sql", "icon": "πŸ—„οΈ", "label": "SQL"},
21
- "bash": {"extension": ".sh", "icon": "πŸ’»", "label": "Bash Script"},
22
- "function": {"extension": ".txt", "icon": "βš™οΈ", "label": "Function"},
23
- "html": {"extension": ".html", "icon": "🌐", "label": "HTML"},
24
- "css": {"extension": ".css", "icon": "🎨", "label": "CSS"},
25
- "other": {"extension": ".txt", "icon": "πŸ“„", "label": "Other"}
26
- }
27
 
28
  def init_db():
29
- """Initialize the database with text_items table"""
30
  conn = sqlite3.connect(DB_PATH)
31
  cursor = conn.cursor()
32
-
33
- # Check if old table exists and migrate
34
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='prompts'")
35
- old_table_exists = cursor.fetchone() is not None
36
-
37
- # Create new table
38
  cursor.execute('''
39
- CREATE TABLE IF NOT EXISTS text_items (
40
  id INTEGER PRIMARY KEY AUTOINCREMENT,
41
  category TEXT NOT NULL,
42
  section TEXT NOT NULL,
43
  title TEXT NOT NULL,
44
  content TEXT NOT NULL,
45
- content_type TEXT DEFAULT 'prompt',
46
- file_extension TEXT DEFAULT '.txt',
47
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
48
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
49
  )
50
  ''')
51
-
52
- # Migrate data from old table if exists
53
- if old_table_exists:
54
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='text_items'")
55
- new_table_exists = cursor.fetchone() is not None
56
-
57
- if new_table_exists:
58
- cursor.execute("SELECT COUNT(*) FROM text_items")
59
- if cursor.fetchone()[0] == 0: # Only migrate if new table is empty
60
- cursor.execute('''
61
- INSERT INTO text_items (id, category, section, title, content, content_type, file_extension, created_at, updated_at)
62
- SELECT id, category, section,
63
- substr(prompt_text, 1, 50) as title,
64
- prompt_text as content,
65
- 'prompt' as content_type,
66
- '.txt' as file_extension,
67
- created_at, updated_at
68
- FROM prompts
69
- ''')
70
-
71
- # Create indexes
72
- cursor.execute("CREATE INDEX IF NOT EXISTS idx_category ON text_items(category)")
73
- cursor.execute("CREATE INDEX IF NOT EXISTS idx_section ON text_items(section)")
74
- cursor.execute("CREATE INDEX IF NOT EXISTS idx_content_type ON text_items(content_type)")
75
-
76
  conn.commit()
77
  conn.close()
78
 
79
- def get_unique_categories():
80
- """Get all unique categories from the database"""
81
- conn = sqlite3.connect(DB_PATH)
82
- cursor = conn.cursor()
83
- cursor.execute("SELECT DISTINCT category FROM text_items ORDER BY category")
84
- categories = [row[0] for row in cursor.fetchall()]
85
- conn.close()
86
- return categories
87
-
88
- def get_sections_by_category(category):
89
- """Get sections for a specific category"""
90
- if not category or category.strip() == "":
91
- return []
92
- conn = sqlite3.connect(DB_PATH)
93
- cursor = conn.cursor()
94
- cursor.execute("SELECT DISTINCT section FROM text_items WHERE category=? ORDER BY section", (category,))
95
- sections = [row[0] for row in cursor.fetchall()]
96
- conn.close()
97
- return sections
98
-
99
- def get_all_unique_sections():
100
- """Get all unique sections from the database"""
101
- conn = sqlite3.connect(DB_PATH)
102
- cursor = conn.cursor()
103
- cursor.execute("SELECT DISTINCT section FROM text_items ORDER BY section")
104
- sections = [row[0] for row in cursor.fetchall()]
105
- conn.close()
106
- return sections
107
-
108
- def add_text_item(category, section, title, content, content_type):
109
- """Add a new text item to the database"""
110
- if not category or not section or not content:
111
- return "❌ Error: Category, Section, and Content are required!", search_text_items("", "", ""), gr.Dropdown(choices=get_unique_categories()), gr.Dropdown(choices=[])
112
-
113
- # Use first 50 chars of content as title if title is empty
114
- if not title or title.strip() == "":
115
- title = content.strip()[:50] + ("..." if len(content.strip()) > 50 else "")
116
-
117
- # Get file extension from content type
118
- file_ext = CONTENT_TYPES.get(content_type, CONTENT_TYPES["other"])["extension"]
119
 
120
  conn = sqlite3.connect(DB_PATH)
121
  cursor = conn.cursor()
122
  cursor.execute(
123
- "INSERT INTO text_items (category, section, title, content, content_type, file_extension) VALUES (?, ?, ?, ?, ?, ?)",
124
- (category.strip(), section.strip(), title.strip(), content.strip(), content_type, file_ext)
125
  )
126
  conn.commit()
127
  conn.close()
128
 
129
- return "βœ… Item added successfully!", search_text_items("", "", ""), gr.Dropdown(choices=get_unique_categories()), gr.Dropdown(choices=get_sections_by_category(category))
130
 
131
- def search_text_items(category_filter, section_filter, type_filter):
132
- """Search text items by category, section, and/or content type"""
133
  conn = sqlite3.connect(DB_PATH)
134
 
135
- query = "SELECT id, category, section, title, content, content_type, created_at, updated_at FROM text_items WHERE 1=1"
136
  params = []
137
 
138
- if category_filter and category_filter.strip():
139
- query += " AND category = ?"
140
- params.append(category_filter.strip())
141
 
142
- if section_filter and section_filter.strip():
143
- query += " AND section = ?"
144
- params.append(section_filter.strip())
145
 
146
- if type_filter and type_filter.strip() and type_filter != "All Types":
147
- query += " AND content_type = ?"
148
- params.append(type_filter.strip())
149
 
150
- query += " ORDER BY category, section, updated_at DESC"
151
 
152
  df = pd.read_sql_query(query, conn, params=params)
153
  conn.close()
154
 
155
- # Add icon to content type for display
156
- if not df.empty:
157
- df['Type'] = df['content_type'].apply(lambda x: f"{CONTENT_TYPES.get(x, CONTENT_TYPES['other'])['icon']} {CONTENT_TYPES.get(x, CONTENT_TYPES['other'])['label']}")
158
- # Reorder columns for better display (hide content in table view)
159
- df = df[['id', 'category', 'section', 'Type', 'title', 'created_at', 'updated_at']]
160
-
161
  return df
162
 
163
- def get_content_by_id(item_id):
164
- """Get the full content of a specific item by ID"""
165
- conn = sqlite3.connect(DB_PATH)
166
- cursor = conn.cursor()
167
- cursor.execute("SELECT content FROM text_items WHERE id=?", (item_id,))
168
- result = cursor.fetchone()
169
- conn.close()
170
- return result[0] if result else ""
171
-
172
- def update_text_item(item_id, category, section, title, content, content_type):
173
- """Update an existing text item"""
174
  if not item_id:
175
- return "❌ Error: Please select an item to update!", search_text_items("", "", ""), gr.Dropdown(choices=get_unique_categories()), gr.Dropdown(choices=[])
176
 
177
- if not category or not section or not content:
178
- return "❌ Error: Category, Section, and Content are required!", search_text_items("", "", ""), gr.Dropdown(choices=get_unique_categories()), gr.Dropdown(choices=[])
179
-
180
- # Use first 50 chars of content as title if title is empty
181
- if not title or title.strip() == "":
182
- title = content.strip()[:50] + ("..." if len(content.strip()) > 50 else "")
183
-
184
- # Get file extension from content type
185
- file_ext = CONTENT_TYPES.get(content_type, CONTENT_TYPES["other"])["extension"]
186
 
187
  conn = sqlite3.connect(DB_PATH)
188
  cursor = conn.cursor()
189
  cursor.execute(
190
- "UPDATE text_items SET category=?, section=?, title=?, content=?, content_type=?, file_extension=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
191
- (category.strip(), section.strip(), title.strip(), content.strip(), content_type, file_ext, item_id)
192
  )
193
  conn.commit()
194
  conn.close()
195
- return "βœ… Item updated successfully!", search_text_items("", "", ""), gr.Dropdown(choices=get_unique_categories()), gr.Dropdown(choices=get_sections_by_category(category))
 
196
 
197
- def delete_text_item(item_id):
198
- """Delete a text item from the database"""
199
  if not item_id:
200
- return "❌ Error: Please select an item to delete!", search_text_items("", "", ""), gr.Dropdown(choices=get_unique_categories()), gr.Dropdown(choices=[])
201
 
202
  conn = sqlite3.connect(DB_PATH)
203
  cursor = conn.cursor()
204
- cursor.execute("DELETE FROM text_items WHERE id=?", (item_id,))
205
  conn.commit()
206
  conn.close()
207
- return "βœ… Item deleted successfully!", search_text_items("", "", ""), gr.Dropdown(choices=get_unique_categories()), gr.Dropdown(choices=[])
208
-
209
- def load_item_for_editing(items_df, evt: gr.SelectData):
210
- """Load selected item data for editing"""
211
- if items_df is None or items_df.empty:
212
- return None, "", "", "", "", "prompt"
213
-
214
- row_index = evt.index[0]
215
- row = items_df.iloc[row_index]
216
- item_id = row['id']
217
-
218
- # Get full content from database
219
- content = get_content_by_id(item_id)
220
-
221
- # Extract content_type from Type column (remove icon)
222
- type_display = row['Type']
223
- content_type = next((k for k, v in CONTENT_TYPES.items() if v['label'] in type_display), 'other')
224
 
225
- return item_id, row['category'], row['section'], row['title'], content, content_type
226
 
227
- def rename_category(old_name, new_name):
228
- """Rename a category (updates all items with that category)"""
229
- if not old_name or not new_name:
230
- return "❌ Error: Both old and new category names are required!", search_text_items("", "", "")
231
-
232
- if old_name.strip() == new_name.strip():
233
- return "❌ Error: New name must be different from old name!", search_text_items("", "", "")
234
-
235
- conn = sqlite3.connect(DB_PATH)
236
- cursor = conn.cursor()
237
-
238
- # Check how many items will be affected
239
- cursor.execute("SELECT COUNT(*) FROM text_items WHERE category=?", (old_name.strip(),))
240
- count = cursor.fetchone()[0]
241
-
242
- if count == 0:
243
- conn.close()
244
- return f"❌ Error: Category '{old_name}' not found!", search_text_items("", "", "")
245
 
246
- # Perform the rename
247
- cursor.execute(
248
- "UPDATE text_items SET category=?, updated_at=CURRENT_TIMESTAMP WHERE category=?",
249
- (new_name.strip(), old_name.strip())
250
- )
251
- conn.commit()
252
- conn.close()
253
-
254
- return f"βœ… Category renamed! {count} items updated from '{old_name}' to '{new_name}'", search_text_items("", "", "")
255
 
256
- def rename_section(category, old_name, new_name):
257
- """Rename a section within a category (updates all items with that section)"""
258
- if not old_name or not new_name:
259
- return "❌ Error: Both old and new section names are required!", search_text_items("", "", "")
260
-
261
- if old_name.strip() == new_name.strip():
262
- return "❌ Error: New name must be different from old name!", search_text_items("", "", "")
263
-
264
- conn = sqlite3.connect(DB_PATH)
265
- cursor = conn.cursor()
266
-
267
- # Build query based on whether category is specified
268
- if category and category.strip():
269
- cursor.execute("SELECT COUNT(*) FROM text_items WHERE category=? AND section=?",
270
- (category.strip(), old_name.strip()))
271
- count = cursor.fetchone()[0]
272
-
273
- if count == 0:
274
- conn.close()
275
- return f"❌ Error: Section '{old_name}' not found in category '{category}'!", search_text_items("", "", "")
276
-
277
- cursor.execute(
278
- "UPDATE text_items SET section=?, updated_at=CURRENT_TIMESTAMP WHERE category=? AND section=?",
279
- (new_name.strip(), category.strip(), old_name.strip())
280
- )
281
- else:
282
- cursor.execute("SELECT COUNT(*) FROM text_items WHERE section=?", (old_name.strip(),))
283
- count = cursor.fetchone()[0]
284
-
285
- if count == 0:
286
- conn.close()
287
- return f"❌ Error: Section '{old_name}' not found!", search_text_items("", "", "")
288
-
289
- cursor.execute(
290
- "UPDATE text_items SET section=?, updated_at=CURRENT_TIMESTAMP WHERE section=?",
291
- (new_name.strip(), old_name.strip())
292
- )
293
-
294
- conn.commit()
295
- conn.close()
296
-
297
- return f"βœ… Section renamed! {count} items updated from '{old_name}' to '{new_name}'", search_text_items("", "", "")
298
 
299
- def delete_by_category(category, confirm=False):
300
- """Delete all items in a category"""
301
- if not category or not category.strip():
302
- return "❌ Error: Category is required!", search_text_items("", "", "")
303
 
304
- conn = sqlite3.connect(DB_PATH)
305
- cursor = conn.cursor()
 
 
 
 
306
 
307
- # Get count
308
- cursor.execute("SELECT COUNT(*) FROM text_items WHERE category=?", (category.strip(),))
309
- count = cursor.fetchone()[0]
310
 
311
- if count == 0:
312
- conn.close()
313
- return f"❌ Error: Category '{category}' not found or is empty!", search_text_items("", "", "")
314
 
315
- if not confirm:
316
- conn.close()
317
- return f"⚠️ Warning: This will delete {count} items in category '{category}'. Click again to confirm!", search_text_items("", "", "")
318
 
319
- # Perform deletion
320
- cursor.execute("DELETE FROM text_items WHERE category=?", (category.strip(),))
321
- conn.commit()
322
- conn.close()
 
 
 
323
 
324
- return f"βœ… Category deleted! {count} items removed from '{category}'", search_text_items("", "", "")
325
-
326
- def delete_by_section(category, section, confirm=False):
327
- """Delete all items in a section"""
328
- if not section or not section.strip():
329
- return "❌ Error: Section is required!", search_text_items("", "", "")
 
 
 
330
 
331
- conn = sqlite3.connect(DB_PATH)
332
- cursor = conn.cursor()
333
 
334
- # Build query based on whether category is specified
335
- if category and category.strip():
336
- cursor.execute("SELECT COUNT(*) FROM text_items WHERE category=? AND section=?",
337
- (category.strip(), section.strip()))
338
- count = cursor.fetchone()[0]
339
-
340
- if count == 0:
341
- conn.close()
342
- return f"❌ Error: Section '{section}' not found in category '{category}' or is empty!", search_text_items("", "", "")
343
-
344
- if not confirm:
345
- conn.close()
346
- return f"⚠️ Warning: This will delete {count} items in section '{section}' under '{category}'. Click again to confirm!", search_text_items("", "", "")
347
-
348
- cursor.execute("DELETE FROM text_items WHERE category=? AND section=?",
349
- (category.strip(), section.strip()))
350
- else:
351
- cursor.execute("SELECT COUNT(*) FROM text_items WHERE section=?", (section.strip(),))
352
- count = cursor.fetchone()[0]
353
-
354
- if count == 0:
355
- conn.close()
356
- return f"❌ Error: Section '{section}' not found or is empty!", search_text_items("", "", "")
357
-
358
- if not confirm:
359
- conn.close()
360
- return f"⚠️ Warning: This will delete {count} items in section '{section}' (across all categories). Click again to confirm!", search_text_items("", "", "")
361
-
362
- cursor.execute("DELETE FROM text_items WHERE section=?", (section.strip(),))
363
 
364
- conn.commit()
365
- conn.close()
 
 
366
 
367
- return f"βœ… Section deleted! {count} items removed from '{section}'", search_text_items("", "", "")
368
-
369
- # Initialize database
370
- init_db()
371
-
372
- # Create Gradio interface
373
- with gr.Blocks(title="TextArchive") as app:
374
- gr.Markdown("# πŸ“š TextArchive - Universal Text Management System")
375
- gr.Markdown("Organize your text content: **Category** (high-level) β†’ **Section** (subsection) β†’ **Content**")
376
-
377
- with gr.Tabs():
378
- # Add New Item Tab
379
- with gr.Tab("βž• Add New Item"):
380
- gr.Markdown("### Add a New Text Item")
381
- gr.Markdown("πŸ’‘ **Hierarchy**: Category β†’ Section β†’ Content Type β†’ Content")
382
- with gr.Row():
383
- with gr.Column():
384
- new_category = gr.Dropdown(
385
- label="Category (High-Level)",
386
- choices=get_unique_categories(),
387
- allow_custom_value=True,
388
- info="Enter a new category or select existing"
389
- )
390
- new_section = gr.Dropdown(
391
- label="Section (Subsection)",
392
- choices=[],
393
- allow_custom_value=True,
394
- info="Enter a new section or select existing"
395
- )
396
- new_content_type = gr.Dropdown(
397
- label="Content Type",
398
- choices=list(CONTENT_TYPES.keys()),
399
- value="prompt",
400
- info="Select the type of content"
401
- )
402
- new_title = gr.Textbox(
403
- label="Title (Optional)",
404
- placeholder="Leave empty to auto-generate from content...",
405
- lines=1
406
- )
407
- new_content = gr.Textbox(
408
- label="Content",
409
- placeholder="Enter your content here...",
410
- lines=12
411
- )
412
- add_btn = gr.Button("βž• Add Item", variant="primary", size="lg")
413
- add_status = gr.Textbox(label="Status", interactive=False)
414
-
415
- # View & Manage Items Tab
416
- with gr.Tab("πŸ“‹ View & Manage Items"):
417
- with gr.Row():
418
- with gr.Column(scale=1):
419
- gr.Markdown("### πŸ” Filter (Auto-updates)")
420
- filter_category = gr.Dropdown(
421
- label="Filter by Category",
422
- choices=[""] + get_unique_categories(),
423
- value="",
424
- allow_custom_value=False,
425
- info="Select category to filter"
426
- )
427
- filter_section = gr.Dropdown(
428
- label="Filter by Section",
429
- choices=[""],
430
- value="",
431
- allow_custom_value=False,
432
- info="Select section to filter"
433
- )
434
- filter_type = gr.Dropdown(
435
- label="Filter by Type",
436
- choices=["All Types"] + list(CONTENT_TYPES.keys()),
437
- value="All Types",
438
- info="Select content type"
439
- )
440
- clear_filter_btn = gr.Button("πŸ”„ Clear Filters", variant="secondary")
441
-
442
- with gr.Column(scale=3):
443
- gr.Markdown("### All Items (Click row to view/edit)")
444
- items_display = gr.Dataframe(
445
- value=search_text_items("", "", ""),
446
- label="Text Items - Category β†’ Section β†’ Type β†’ Title",
447
- interactive=False,
448
- wrap=True
449
- )
450
-
451
- gr.Markdown("---")
452
-
453
- # Content Preview and Copy Section
454
- with gr.Row():
455
- with gr.Column():
456
- gr.Markdown("### πŸ“„ Content Preview")
457
- content_preview = gr.Textbox(
458
- label="Selected Content (Click to select all, then Ctrl+C to copy)",
459
- lines=8,
460
- interactive=True
461
- )
462
-
463
- gr.Markdown("---")
464
- gr.Markdown("### ✏️ Edit Selected Item")
465
-
466
- with gr.Row():
467
- with gr.Column():
468
- edit_id = gr.Number(label="Item ID", visible=False)
469
- edit_category = gr.Dropdown(
470
- label="Category (High-Level)",
471
- choices=get_unique_categories(),
472
- allow_custom_value=True
473
- )
474
- edit_section = gr.Dropdown(
475
- label="Section (Subsection)",
476
- choices=[],
477
- allow_custom_value=True
478
- )
479
- edit_content_type = gr.Dropdown(
480
- label="Content Type",
481
- choices=list(CONTENT_TYPES.keys()),
482
- value="prompt"
483
- )
484
- edit_title = gr.Textbox(label="Title", lines=1)
485
- edit_content = gr.Textbox(label="Content", lines=10)
486
-
487
- with gr.Row():
488
- update_btn = gr.Button("πŸ’Ύ Update Item", variant="primary")
489
- delete_btn = gr.Button("πŸ—‘οΈ Delete Item", variant="stop")
490
-
491
- edit_status = gr.Textbox(label="Status", interactive=False)
492
-
493
- # Manage Categories & Sections Tab
494
- with gr.Tab("βš™οΈ Manage Categories & Sections"):
495
- gr.Markdown("### 🏷️ Rename or Delete Categories and Sections")
496
-
497
- with gr.Row():
498
- # Rename Category
499
- with gr.Column():
500
- gr.Markdown("#### Rename Category")
501
- rename_cat_old = gr.Dropdown(
502
- label="Select Category",
503
- choices=get_unique_categories(),
504
- allow_custom_value=False
505
- )
506
- rename_cat_new = gr.Textbox(
507
- label="New Category Name",
508
- placeholder="Enter new name..."
509
- )
510
- rename_cat_btn = gr.Button("πŸ”„ Rename Category", variant="primary")
511
- rename_cat_status = gr.Textbox(label="Status", interactive=False)
512
-
513
- # Rename Section
514
- with gr.Column():
515
- gr.Markdown("#### Rename Section")
516
- rename_sec_category = gr.Dropdown(
517
- label="Category (Optional)",
518
- choices=[""] + get_unique_categories(),
519
- allow_custom_value=False,
520
- info="Leave empty to rename across all categories"
521
- )
522
- rename_sec_old = gr.Dropdown(
523
- label="Select Section",
524
- choices=get_all_unique_sections(),
525
- allow_custom_value=False
526
- )
527
- rename_sec_new = gr.Textbox(
528
- label="New Section Name",
529
- placeholder="Enter new name..."
530
- )
531
- rename_sec_btn = gr.Button("πŸ”„ Rename Section", variant="primary")
532
- rename_sec_status = gr.Textbox(label="Status", interactive=False)
533
-
534
- gr.Markdown("---")
535
-
536
- with gr.Row():
537
- # Delete Category
538
- with gr.Column():
539
- gr.Markdown("#### Delete Entire Category")
540
- gr.Markdown("⚠️ **Warning**: This will delete ALL items in the category!")
541
- delete_cat_select = gr.Dropdown(
542
- label="Select Category",
543
- choices=get_unique_categories(),
544
- allow_custom_value=False
545
- )
546
- delete_cat_confirm = gr.Checkbox(label="I understand this will delete all items", value=False)
547
- delete_cat_btn = gr.Button("πŸ—‘οΈ Delete Category", variant="stop")
548
- delete_cat_status = gr.Textbox(label="Status", interactive=False)
549
-
550
- # Delete Section
551
- with gr.Column():
552
- gr.Markdown("#### Delete Entire Section")
553
- gr.Markdown("⚠️ **Warning**: This will delete ALL items in the section!")
554
- delete_sec_category = gr.Dropdown(
555
- label="Category (Optional)",
556
- choices=[""] + get_unique_categories(),
557
- allow_custom_value=False,
558
- info="Leave empty to delete across all categories"
559
- )
560
- delete_sec_select = gr.Dropdown(
561
- label="Select Section",
562
- choices=get_all_unique_sections(),
563
- allow_custom_value=False
564
- )
565
- delete_sec_confirm = gr.Checkbox(label="I understand this will delete all items", value=False)
566
- delete_sec_btn = gr.Button("πŸ—‘οΈ Delete Section", variant="stop")
567
- delete_sec_status = gr.Textbox(label="Status", interactive=False)
568
-
569
- # Statistics Tab
570
- with gr.Tab("πŸ“Š Statistics"):
571
- stats_display = gr.Markdown()
572
- refresh_stats_btn = gr.Button("πŸ”„ Refresh Statistics", variant="secondary")
573
-
574
- # Event handlers
575
- def clear_filters():
576
- return "", "", "All Types", search_text_items("", "", "")
577
-
578
- def get_statistics():
579
- conn = sqlite3.connect(DB_PATH)
580
- cursor = conn.cursor()
581
-
582
- cursor.execute("SELECT COUNT(*) FROM text_items")
583
- total = cursor.fetchone()[0]
584
-
585
- cursor.execute("SELECT COUNT(DISTINCT category) FROM text_items")
586
- categories = cursor.fetchone()[0]
587
-
588
- cursor.execute("SELECT COUNT(DISTINCT section) FROM text_items")
589
- sections = cursor.fetchone()[0]
590
-
591
- cursor.execute("SELECT category, COUNT(*) as count FROM text_items GROUP BY category ORDER BY count DESC LIMIT 10")
592
- top_categories = cursor.fetchall()
593
-
594
- cursor.execute("SELECT content_type, COUNT(*) as count FROM text_items GROUP BY content_type ORDER BY count DESC")
595
- by_type = cursor.fetchall()
596
-
597
- conn.close()
598
-
599
- stats = f"""
600
- ## πŸ“Š Database Statistics
601
-
602
- - **Total Items:** {total}
603
- - **Total Categories:** {categories}
604
- - **Total Sections:** {sections}
605
-
606
- ---
607
-
608
- ### πŸ† Top Categories by Item Count:
609
- """
610
- if top_categories:
611
- for cat, count in top_categories:
612
- stats += f"\n- **{cat}:** {count} items"
613
- else:
614
- stats += "\n*No data yet*"
615
-
616
- stats += "\n\n### πŸ“ Items by Content Type:\n"
617
- if by_type:
618
- for ctype, count in by_type:
619
- icon = CONTENT_TYPES.get(ctype, CONTENT_TYPES['other'])['icon']
620
- label = CONTENT_TYPES.get(ctype, CONTENT_TYPES['other'])['label']
621
- stats += f"\n- {icon} **{label}:** {count} items"
622
- else:
623
- stats += "\n*No data yet*"
624
-
625
- return stats
626
-
627
- def update_section_choices_on_category_change(category):
628
- """Update section dropdown when category changes"""
629
- sections = get_sections_by_category(category) if category else []
630
- return gr.Dropdown(choices=sections)
631
-
632
- def auto_filter(category, section, content_type):
633
- """Automatically filter when any filter changes"""
634
- return search_text_items(category, section, content_type)
635
-
636
- def update_content_preview(items_df, evt: gr.SelectData):
637
- """Update content preview when row is selected"""
638
- if items_df is None or items_df.empty:
639
- return ""
640
-
641
- row_index = evt.index[0]
642
- row = items_df.iloc[row_index]
643
- item_id = row['id']
644
-
645
- # Get full content from database
646
- content = get_content_by_id(item_id)
647
- return content
648
-
649
- # Connect events for Add tab
650
- new_category.change(
651
- fn=update_section_choices_on_category_change,
652
- inputs=[new_category],
653
- outputs=[new_section]
654
- )
655
 
656
- add_btn.click(
657
- fn=add_text_item,
658
- inputs=[new_category, new_section, new_title, new_content, new_content_type],
659
- outputs=[add_status, items_display, new_category, new_section]
660
- ).then(
661
- fn=lambda: ("", "", ""),
662
- outputs=[new_title, new_content, new_section]
663
- ).then(
664
- fn=lambda: [
665
- gr.Dropdown(choices=get_unique_categories()),
666
- gr.Dropdown(choices=[""] + get_unique_categories()),
667
- gr.Dropdown(choices=get_unique_categories()),
668
- gr.Dropdown(choices=[""] + get_unique_categories()),
669
- gr.Dropdown(choices=get_unique_categories()),
670
- gr.Dropdown(choices=get_unique_categories()),
671
- gr.Dropdown(choices=[""] + get_unique_categories()),
672
- gr.Dropdown(choices=get_unique_categories())
673
- ],
674
- outputs=[
675
- new_category, filter_category, edit_category,
676
- rename_sec_category, rename_cat_old,
677
- delete_cat_select, delete_sec_category, rename_sec_category
678
- ]
679
- )
680
 
681
- # Auto-filter when any filter changes
682
- filter_category.change(
683
- fn=lambda cat: gr.Dropdown(choices=[""] + get_sections_by_category(cat)),
684
- inputs=[filter_category],
685
- outputs=[filter_section]
686
  ).then(
687
- fn=auto_filter,
688
- inputs=[filter_category, filter_section, filter_type],
689
- outputs=items_display
690
- )
691
-
692
- filter_section.change(
693
- fn=auto_filter,
694
- inputs=[filter_category, filter_section, filter_type],
695
- outputs=items_display
696
- )
697
-
698
- filter_type.change(
699
- fn=auto_filter,
700
- inputs=[filter_category, filter_section, filter_type],
701
- outputs=items_display
702
- )
703
-
704
- clear_filter_btn.click(
705
- fn=clear_filters,
706
- outputs=[filter_category, filter_section, filter_type, items_display]
707
  )
708
 
709
- # Load item for editing and preview when row is selected
710
- items_display.select(
711
- fn=load_item_for_editing,
712
- inputs=[items_display],
713
- outputs=[edit_id, edit_category, edit_section, edit_title, edit_content, edit_content_type]
714
- )
 
715
 
716
- items_display.select(
717
- fn=update_content_preview,
718
- inputs=[items_display],
719
- outputs=[content_preview]
720
  )
721
 
722
- # Update section dropdown when edit category changes
723
- edit_category.change(
724
- fn=update_section_choices_on_category_change,
725
- inputs=[edit_category],
726
- outputs=[edit_section]
727
  )
728
 
729
  update_btn.click(
730
- fn=update_text_item,
731
- inputs=[edit_id, edit_category, edit_section, edit_title, edit_content, edit_content_type],
732
- outputs=[edit_status, items_display, edit_category, edit_section]
733
- ).then(
734
- fn=lambda: [
735
- gr.Dropdown(choices=get_unique_categories()),
736
- gr.Dropdown(choices=[""] + get_unique_categories())
737
- ],
738
- outputs=[new_category, filter_category]
739
  )
740
 
741
  delete_btn.click(
742
- fn=delete_text_item,
743
  inputs=[edit_id],
744
- outputs=[edit_status, items_display, edit_category, edit_section]
745
- ).then(
746
- fn=lambda: (None, "", "", "", "", "prompt"),
747
- outputs=[edit_id, edit_category, edit_section, edit_title, edit_content, edit_content_type]
748
- ).then(
749
- fn=lambda: [
750
- gr.Dropdown(choices=get_unique_categories()),
751
- gr.Dropdown(choices=[""] + get_unique_categories())
752
- ],
753
- outputs=[new_category, filter_category]
754
- )
755
-
756
- # Rename category
757
- rename_cat_btn.click(
758
- fn=rename_category,
759
- inputs=[rename_cat_old, rename_cat_new],
760
- outputs=[rename_cat_status, items_display]
761
- ).then(
762
- fn=lambda: [
763
- gr.Dropdown(choices=get_unique_categories()),
764
- gr.Dropdown(choices=get_unique_categories()),
765
- gr.Dropdown(choices=[""] + get_unique_categories()),
766
- gr.Dropdown(choices=get_unique_categories()),
767
- "", ""
768
- ],
769
- outputs=[rename_cat_old, new_category, filter_category, edit_category, rename_cat_old, rename_cat_new]
770
- )
771
-
772
- # Rename section
773
- rename_sec_btn.click(
774
- fn=rename_section,
775
- inputs=[rename_sec_category, rename_sec_old, rename_sec_new],
776
- outputs=[rename_sec_status, items_display]
777
- ).then(
778
- fn=lambda: ["", ""],
779
- outputs=[rename_sec_old, rename_sec_new]
780
- )
781
-
782
- # Delete category
783
- delete_cat_btn.click(
784
- fn=delete_by_category,
785
- inputs=[delete_cat_select, delete_cat_confirm],
786
- outputs=[delete_cat_status, items_display]
787
  ).then(
788
- fn=lambda: [
789
- gr.Dropdown(choices=get_unique_categories()),
790
- gr.Dropdown(choices=get_unique_categories()),
791
- gr.Dropdown(choices=[""] + get_unique_categories()),
792
- False, ""
793
- ],
794
- outputs=[delete_cat_select, new_category, filter_category, delete_cat_confirm, delete_cat_select]
795
  )
796
-
797
- # Delete section
798
- delete_sec_btn.click(
799
- fn=delete_by_section,
800
- inputs=[delete_sec_category, delete_sec_select, delete_sec_confirm],
801
- outputs=[delete_sec_status, items_display]
802
- ).then(
803
- fn=lambda: [False, ""],
804
- outputs=[delete_sec_confirm, delete_sec_select]
805
- )
806
-
807
- refresh_stats_btn.click(
808
- fn=get_statistics,
809
- outputs=stats_display
810
- )
811
-
812
- # Load initial statistics
813
- app.load(fn=get_statistics, outputs=stats_display)
814
 
815
  if __name__ == "__main__":
816
- app.launch(share=False)
 
1
+ """
2
+ Simple Text Archive - Minimal text management with searchable table
3
+ """
4
  import gradio as gr
5
  import sqlite3
6
  import pandas as pd
7
  from datetime import datetime
 
 
8
 
9
+ DB_PATH = "simple_archive.db"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  def init_db():
12
+ """Initialize database"""
13
  conn = sqlite3.connect(DB_PATH)
14
  cursor = conn.cursor()
 
 
 
 
 
 
15
  cursor.execute('''
16
+ CREATE TABLE IF NOT EXISTS items (
17
  id INTEGER PRIMARY KEY AUTOINCREMENT,
18
  category TEXT NOT NULL,
19
  section TEXT NOT NULL,
20
  title TEXT NOT NULL,
21
  content TEXT NOT NULL,
 
 
22
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
23
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
24
  )
25
  ''')
26
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_category ON items(category)")
27
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_section ON items(section)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  conn.commit()
29
  conn.close()
30
 
31
+ def add_item(category, section, title, content):
32
+ """Add new item"""
33
+ if not all([category, section, title, content]):
34
+ return "❌ All fields required!", load_table("", "", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  conn = sqlite3.connect(DB_PATH)
37
  cursor = conn.cursor()
38
  cursor.execute(
39
+ "INSERT INTO items (category, section, title, content) VALUES (?, ?, ?, ?)",
40
+ (category.strip(), section.strip(), title.strip(), content.strip())
41
  )
42
  conn.commit()
43
  conn.close()
44
 
45
+ return "βœ… Added!", load_table("", "", "")
46
 
47
+ def load_table(category_filter, section_filter, title_filter):
48
+ """Load and filter table data"""
49
  conn = sqlite3.connect(DB_PATH)
50
 
51
+ query = "SELECT id, category, section, title, content, created_at, updated_at FROM items WHERE 1=1"
52
  params = []
53
 
54
+ if category_filter:
55
+ query += " AND category LIKE ?"
56
+ params.append(f"%{category_filter}%")
57
 
58
+ if section_filter:
59
+ query += " AND section LIKE ?"
60
+ params.append(f"%{section_filter}%")
61
 
62
+ if title_filter:
63
+ query += " AND title LIKE ?"
64
+ params.append(f"%{title_filter}%")
65
 
66
+ query += " ORDER BY updated_at DESC"
67
 
68
  df = pd.read_sql_query(query, conn, params=params)
69
  conn.close()
70
 
 
 
 
 
 
 
71
  return df
72
 
73
+ def update_item(item_id, category, section, title, content):
74
+ """Update existing item"""
 
 
 
 
 
 
 
 
 
75
  if not item_id:
76
+ return "❌ Select an item first!", load_table("", "", "")
77
 
78
+ if not all([category, section, title, content]):
79
+ return "❌ All fields required!", load_table("", "", "")
 
 
 
 
 
 
 
80
 
81
  conn = sqlite3.connect(DB_PATH)
82
  cursor = conn.cursor()
83
  cursor.execute(
84
+ "UPDATE items SET category=?, section=?, title=?, content=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
85
+ (category.strip(), section.strip(), title.strip(), content.strip(), item_id)
86
  )
87
  conn.commit()
88
  conn.close()
89
+
90
+ return "βœ… Updated!", load_table("", "", "")
91
 
92
+ def delete_item(item_id):
93
+ """Delete item"""
94
  if not item_id:
95
+ return "❌ Select an item first!", load_table("", "", "")
96
 
97
  conn = sqlite3.connect(DB_PATH)
98
  cursor = conn.cursor()
99
+ cursor.execute("DELETE FROM items WHERE id=?", (item_id,))
100
  conn.commit()
101
  conn.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ return "βœ… Deleted!", load_table("", "", "")
104
 
105
+ def load_for_edit(df, evt: gr.SelectData):
106
+ """Load selected row for editing"""
107
+ if df is None or df.empty:
108
+ return None, "", "", "", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
+ row = df.iloc[evt.index[0]]
111
+ return row['id'], row['category'], row['section'], row['title'], row['content']
 
 
 
 
 
 
 
112
 
113
+ # Initialize
114
+ init_db()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
+ # UI
117
+ with gr.Blocks(title="Text Archive") as app:
118
+ gr.Markdown("# πŸ“ Simple Text Archive")
 
119
 
120
+ # Add Form
121
+ gr.Markdown("### βž• Add New Item")
122
+ with gr.Row():
123
+ add_category = gr.Textbox(label="Category", scale=1)
124
+ add_section = gr.Textbox(label="Section", scale=1)
125
+ add_title = gr.Textbox(label="Title", scale=2)
126
 
127
+ add_content = gr.Textbox(label="Content", lines=3)
 
 
128
 
129
+ with gr.Row():
130
+ add_btn = gr.Button("Add Item", variant="primary", scale=1)
131
+ add_status = gr.Textbox(label="Status", scale=2, interactive=False)
132
 
133
+ gr.Markdown("---")
 
 
134
 
135
+ # Search/Filter
136
+ gr.Markdown("### πŸ” Search & Filter")
137
+ with gr.Row():
138
+ search_category = gr.Textbox(label="Search Category", scale=1)
139
+ search_section = gr.Textbox(label="Search Section", scale=1)
140
+ search_title = gr.Textbox(label="Search Title", scale=1)
141
+ clear_btn = gr.Button("Clear Filters", scale=1)
142
 
143
+ # Table
144
+ gr.Markdown("### πŸ“‹ All Items (Click row to edit)")
145
+ table = gr.Dataframe(
146
+ value=load_table("", "", ""),
147
+ label="Items",
148
+ interactive=False,
149
+ wrap=True,
150
+ height=400
151
+ )
152
 
153
+ gr.Markdown("---")
 
154
 
155
+ # Edit Form
156
+ gr.Markdown("### ✏️ Edit Selected Item")
157
+ edit_id = gr.Number(label="ID", visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
+ with gr.Row():
160
+ edit_category = gr.Textbox(label="Category", scale=1)
161
+ edit_section = gr.Textbox(label="Section", scale=1)
162
+ edit_title = gr.Textbox(label="Title", scale=2)
163
 
164
+ edit_content = gr.Textbox(label="Content", lines=5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
+ with gr.Row():
167
+ update_btn = gr.Button("πŸ’Ύ Update", variant="primary")
168
+ delete_btn = gr.Button("πŸ—‘οΈ Delete", variant="stop")
169
+ edit_status = gr.Textbox(label="Status", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
+ # Events
172
+ add_btn.click(
173
+ fn=add_item,
174
+ inputs=[add_category, add_section, add_title, add_content],
175
+ outputs=[add_status, table]
176
  ).then(
177
+ fn=lambda: ("", "", "", ""),
178
+ outputs=[add_category, add_section, add_title, add_content]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  )
180
 
181
+ # Auto-search on filter change
182
+ for search_box in [search_category, search_section, search_title]:
183
+ search_box.change(
184
+ fn=load_table,
185
+ inputs=[search_category, search_section, search_title],
186
+ outputs=table
187
+ )
188
 
189
+ clear_btn.click(
190
+ fn=lambda: ("", "", "", load_table("", "", "")),
191
+ outputs=[search_category, search_section, search_title, table]
 
192
  )
193
 
194
+ # Load for editing
195
+ table.select(
196
+ fn=load_for_edit,
197
+ inputs=[table],
198
+ outputs=[edit_id, edit_category, edit_section, edit_title, edit_content]
199
  )
200
 
201
  update_btn.click(
202
+ fn=update_item,
203
+ inputs=[edit_id, edit_category, edit_section, edit_title, edit_content],
204
+ outputs=[edit_status, table]
 
 
 
 
 
 
205
  )
206
 
207
  delete_btn.click(
208
+ fn=delete_item,
209
  inputs=[edit_id],
210
+ outputs=[edit_status, table]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  ).then(
212
+ fn=lambda: (None, "", "", "", ""),
213
+ outputs=[edit_id, edit_category, edit_section, edit_title, edit_content]
 
 
 
 
 
214
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
  if __name__ == "__main__":
217
+ app.launch()