mnoorchenar commited on
Commit
8865430
Β·
1 Parent(s): 69b51ae

Update 2026-01-30 10:15:16

Browse files
Files changed (1) hide show
  1. app.py +114 -151
app.py CHANGED
@@ -1,20 +1,21 @@
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
  import os
9
- import shutil
10
 
11
  DB_PATH = "simple_archive.db"
12
 
 
13
  def init_db():
14
- """Initialize database"""
15
  conn = sqlite3.connect(DB_PATH)
16
  cursor = conn.cursor()
17
- cursor.execute('''
 
18
  CREATE TABLE IF NOT EXISTS items (
19
  id INTEGER PRIMARY KEY AUTOINCREMENT,
20
  category TEXT NOT NULL,
@@ -24,145 +25,133 @@ def init_db():
24
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
25
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
26
  )
27
- ''')
 
28
  cursor.execute("CREATE INDEX IF NOT EXISTS idx_category ON items(category)")
29
  cursor.execute("CREATE INDEX IF NOT EXISTS idx_section ON items(section)")
30
  cursor.execute("CREATE INDEX IF NOT EXISTS idx_title ON items(title)")
31
  conn.commit()
32
  conn.close()
33
 
34
- def upload_database(file):
35
- """Upload and replace database"""
36
- if file is None:
37
- return "❌ No file selected", load_table("", "", ""), get_stats()
38
-
39
- try:
40
- shutil.copy(file.name, DB_PATH)
41
- return "βœ… Database uploaded!", load_table("", "", ""), get_stats()
42
- except Exception as e:
43
- return f"❌ Error: {str(e)}", load_table("", "", ""), get_stats()
44
 
45
  def add_item(category, section, title, content):
46
- """Add new item"""
47
  if not all([category, section, title, content]):
48
  return "❌ All fields required!", load_table("", "", "")
49
-
50
  conn = sqlite3.connect(DB_PATH)
51
  cursor = conn.cursor()
52
  cursor.execute(
53
  "INSERT INTO items (category, section, title, content) VALUES (?, ?, ?, ?)",
54
- (category.strip(), section.strip(), title.strip(), content.strip())
55
  )
56
  conn.commit()
57
  conn.close()
58
-
59
  return "βœ… Added!", load_table("", "", "")
60
 
 
61
  def load_table(category_filter, section_filter, title_filter):
62
- """Load and filter table data with truncated content preview"""
63
  conn = sqlite3.connect(DB_PATH)
64
-
65
  query = """
66
- SELECT id, category, section, title, content,
67
  datetime(updated_at, 'localtime') as updated
68
- FROM items WHERE 1=1
 
69
  """
70
  params = []
71
-
72
  if category_filter:
73
  query += " AND category LIKE ?"
74
  params.append(f"%{category_filter}%")
75
-
76
  if section_filter:
77
  query += " AND section LIKE ?"
78
  params.append(f"%{section_filter}%")
79
-
80
  if title_filter:
81
  query += " AND title LIKE ?"
82
  params.append(f"%{title_filter}%")
83
-
84
  query += " ORDER BY updated_at DESC"
85
-
86
  df = pd.read_sql_query(query, conn, params=params)
87
  conn.close()
88
-
89
- # Truncate content for display (show first 100 chars)
90
  if not df.empty:
91
- df['content'] = df['content'].apply(lambda x: (x[:100] + '...') if len(x) > 100 else x)
92
-
 
 
93
  return df
94
 
 
95
  def update_item(item_id, category, section, title, content):
96
- """Update existing item"""
97
- if not item_id:
98
  return "❌ Select an item first!", load_table("", "", "")
99
-
100
  if not all([category, section, title, content]):
101
  return "❌ All fields required!", load_table("", "", "")
102
-
103
  conn = sqlite3.connect(DB_PATH)
104
  cursor = conn.cursor()
105
  cursor.execute(
106
- "UPDATE items SET category=?, section=?, title=?, content=?, updated_at=CURRENT_TIMESTAMP WHERE id=?",
107
- (category.strip(), section.strip(), title.strip(), content.strip(), int(item_id))
 
 
 
 
 
 
 
 
108
  )
109
  conn.commit()
110
  conn.close()
111
-
112
  return "βœ… Updated!", load_table("", "", "")
113
 
 
114
  def delete_item(item_id):
115
- """Delete item"""
116
- if not item_id:
117
  return "❌ Select an item first!", load_table("", "", "")
118
-
119
  conn = sqlite3.connect(DB_PATH)
120
  cursor = conn.cursor()
121
  cursor.execute("DELETE FROM items WHERE id=?", (int(item_id),))
122
  conn.commit()
123
  conn.close()
124
-
125
  return "βœ… Deleted!", load_table("", "", "")
126
 
127
- def load_for_edit(df_data):
128
- """Load selected row for editing - fetch full content from database"""
129
- if df_data is None:
130
- return None, "", "", "", ""
131
-
132
- try:
133
- # df_data is the SelectData event
134
- row_index = df_data.index[0]
135
-
136
- # Get the dataframe from the component
137
- # We need to access the actual dataframe, not the event
138
- return None, "", "", "", ""
139
- except:
140
- return None, "", "", "", ""
141
 
142
  def load_item_by_id(item_id):
143
- """Load item by ID from database"""
144
- if not item_id:
145
  return "", "", "", ""
146
-
147
  conn = sqlite3.connect(DB_PATH)
148
  cursor = conn.cursor()
149
- cursor.execute("SELECT category, section, title, content FROM items WHERE id=?", (int(item_id),))
 
 
 
150
  result = cursor.fetchone()
151
  conn.close()
152
-
153
  if result:
154
  return result[0], result[1], result[2], result[3]
155
  return "", "", "", ""
156
 
 
157
  def get_char_count(text):
158
- """Count characters"""
159
  return f"πŸ“Š {len(text)} chars" if text else "πŸ“Š 0 chars"
160
 
 
161
  def get_stats():
162
- """Get database statistics"""
163
  if not os.path.exists(DB_PATH):
164
  return "πŸ’Ύ Database: not created yet"
165
-
166
  conn = sqlite3.connect(DB_PATH)
167
  cursor = conn.cursor()
168
  cursor.execute("SELECT COUNT(*) FROM items")
@@ -170,9 +159,20 @@ def get_stats():
170
  cursor.execute("SELECT COUNT(DISTINCT category) FROM items")
171
  categories = cursor.fetchone()[0]
172
  conn.close()
173
-
174
- file_size = os.path.getsize(DB_PATH) / 1024
175
- return f"πŸ’Ύ Database: {total} items, {categories} categories ({file_size:.1f} KB)"
 
 
 
 
 
 
 
 
 
 
 
176
 
177
  # Initialize
178
  init_db()
@@ -180,28 +180,13 @@ init_db()
180
  # UI
181
  with gr.Blocks(title="Text Archive") as app:
182
  gr.Markdown("# πŸ“ Simple Text Archive")
183
- gr.Markdown("""
184
- ⚠️ **Important**: The DB file exists but isn't auto-committed to git on HF Spaces.
185
-
186
- **Workflow:**
187
- 1. Work on your Space β†’ Download DB before closing
188
- 2. On local: Place downloaded DB in same folder as app.py
189
- 3. Commit & push: `git add simple_archive.db && git commit -m "Update DB" && git push`
190
- 4. Next session: Your DB will be there
191
-
192
- Or use Upload DB button to restore a saved version.
193
- """)
194
-
195
  with gr.Row():
196
  db_stats = gr.Markdown(get_stats())
197
  download_btn = gr.DownloadButton("πŸ’Ύ Download DB", value=DB_PATH, size="sm", scale=0)
198
- with gr.Column(scale=0, min_width=120):
199
- upload_file = gr.File(label="πŸ“€ Upload DB", file_types=[".db"], file_count="single", type="filepath", container=False)
200
-
201
- upload_status = gr.Textbox(label="", container=False, show_label=False, interactive=False)
202
-
203
  gr.Markdown("---")
204
-
205
  # Add Form
206
  with gr.Row():
207
  with gr.Column(scale=3):
@@ -210,16 +195,16 @@ with gr.Blocks(title="Text Archive") as app:
210
  add_category = gr.Textbox(label="Category", placeholder="e.g., Python, Notes, SQL", scale=1)
211
  add_section = gr.Textbox(label="Section", placeholder="e.g., Functions, Snippets", scale=1)
212
  add_title = gr.Textbox(label="Title", placeholder="Short description", scale=2)
213
-
214
  add_content = gr.Textbox(label="Content", placeholder="Your text here...", lines=4)
215
  add_char_count = gr.Markdown("πŸ“Š 0 chars")
216
-
217
  with gr.Row():
218
  add_btn = gr.Button("βž• Add Item", variant="primary", size="sm")
219
  add_status = gr.Textbox(label="", container=False, show_label=False, interactive=False)
220
-
221
  gr.Markdown("---")
222
-
223
  # Search/Filter
224
  gr.Markdown("### πŸ” Search & Filter")
225
  with gr.Row():
@@ -227,134 +212,112 @@ with gr.Blocks(title="Text Archive") as app:
227
  search_section = gr.Textbox(label="πŸ“ Section", placeholder="Filter...", scale=1)
228
  search_title = gr.Textbox(label="πŸ“Œ Title", placeholder="Filter...", scale=1)
229
  clear_btn = gr.Button("πŸ”„ Clear", size="sm", scale=1)
230
-
231
  # Table
232
- gr.Markdown("### πŸ“‹ All Items (Click row to load in edit form)")
233
  table = gr.Dataframe(
234
  value=load_table("", "", ""),
235
  label="",
236
  interactive=False,
237
- column_widths=["5%", "12%", "12%", "20%", "40%", "11%"]
238
  )
239
-
240
  gr.Markdown("---")
241
-
242
  # Edit Form
243
- gr.Markdown("### ✏️ Edit Selected Item (Enter ID or click table row)")
244
-
245
  with gr.Row():
246
  edit_id = gr.Number(label="Item ID", precision=0, scale=1)
247
  load_id_btn = gr.Button("πŸ”„ Load by ID", size="sm", scale=0)
248
-
249
  with gr.Row():
250
  edit_category = gr.Textbox(label="Category", scale=1)
251
  edit_section = gr.Textbox(label="Section", scale=1)
252
  edit_title = gr.Textbox(label="Title", scale=2)
253
-
254
  edit_content = gr.Textbox(label="Content", lines=6, max_lines=20)
255
  edit_char_count = gr.Markdown("πŸ“Š 0 chars")
256
-
257
  with gr.Row():
258
  update_btn = gr.Button("πŸ’Ύ Save Changes", variant="primary", size="sm")
259
  delete_btn = gr.Button("πŸ—‘οΈ Delete", variant="stop", size="sm")
260
  edit_status = gr.Textbox(label="", container=False, show_label=False, interactive=False)
261
-
262
  # Events
263
- upload_file.change(
264
- fn=upload_database,
265
- inputs=upload_file,
266
- outputs=[upload_status, table, db_stats]
267
- )
268
-
269
- add_content.change(
270
- fn=get_char_count,
271
- inputs=add_content,
272
- outputs=add_char_count
273
- )
274
-
275
- edit_content.change(
276
- fn=get_char_count,
277
- inputs=edit_content,
278
- outputs=edit_char_count
279
- )
280
-
281
  add_btn.click(
282
  fn=add_item,
283
  inputs=[add_category, add_section, add_title, add_content],
284
- outputs=[add_status, table]
285
  ).then(
286
  fn=lambda: ("", "", "", "", "πŸ“Š 0 chars"),
287
- outputs=[add_category, add_section, add_title, add_content, add_char_count]
288
  ).then(
289
  fn=get_stats,
290
- outputs=db_stats
291
  )
292
-
293
- # Auto-search on filter change
294
  for search_box in [search_category, search_section, search_title]:
295
  search_box.change(
296
  fn=load_table,
297
  inputs=[search_category, search_section, search_title],
298
- outputs=table
299
  )
300
-
301
  clear_btn.click(
302
  fn=lambda: ("", "", "", load_table("", "", "")),
303
- outputs=[search_category, search_section, search_title, table]
304
  )
305
-
306
- # Load by ID button
307
  load_id_btn.click(
308
  fn=load_item_by_id,
309
  inputs=edit_id,
310
- outputs=[edit_category, edit_section, edit_title, edit_content]
311
  ).then(
312
  fn=get_char_count,
313
  inputs=edit_content,
314
- outputs=edit_char_count
315
  )
316
-
317
- # Table row selection - extracts ID and loads
318
- def handle_table_select(evt: gr.SelectData):
319
- """Handle table row selection"""
320
- return evt.index[0] + 1 # Return row number as ID hint
321
-
322
  table.select(
323
  fn=handle_table_select,
324
- outputs=edit_id
 
325
  ).then(
326
  fn=load_item_by_id,
327
  inputs=edit_id,
328
- outputs=[edit_category, edit_section, edit_title, edit_content]
329
  ).then(
330
  fn=get_char_count,
331
  inputs=edit_content,
332
- outputs=edit_char_count
333
  )
334
-
335
  update_btn.click(
336
  fn=update_item,
337
  inputs=[edit_id, edit_category, edit_section, edit_title, edit_content],
338
- outputs=[edit_status, table]
339
  ).then(
340
  fn=get_stats,
341
- outputs=db_stats
342
  )
343
-
344
  delete_btn.click(
345
  fn=delete_item,
346
  inputs=[edit_id],
347
- outputs=[edit_status, table]
348
  ).then(
349
  fn=lambda: (None, "", "", "", "", "πŸ“Š 0 chars"),
350
- outputs=[edit_id, edit_category, edit_section, edit_title, edit_content, edit_char_count]
351
  ).then(
352
  fn=get_stats,
353
- outputs=db_stats
354
  )
355
-
356
- # Load initial stats
357
  app.load(fn=get_stats, outputs=db_stats)
358
 
359
  if __name__ == "__main__":
360
- app.launch()
 
1
  """
2
+ Simple Text Archive
3
+ Minimal text management with searchable table and SQLite persistence.
4
  """
5
+
6
  import gradio as gr
7
  import sqlite3
8
  import pandas as pd
 
9
  import os
 
10
 
11
  DB_PATH = "simple_archive.db"
12
 
13
+
14
  def init_db():
 
15
  conn = sqlite3.connect(DB_PATH)
16
  cursor = conn.cursor()
17
+ cursor.execute(
18
+ """
19
  CREATE TABLE IF NOT EXISTS items (
20
  id INTEGER PRIMARY KEY AUTOINCREMENT,
21
  category TEXT NOT NULL,
 
25
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
26
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
27
  )
28
+ """
29
+ )
30
  cursor.execute("CREATE INDEX IF NOT EXISTS idx_category ON items(category)")
31
  cursor.execute("CREATE INDEX IF NOT EXISTS idx_section ON items(section)")
32
  cursor.execute("CREATE INDEX IF NOT EXISTS idx_title ON items(title)")
33
  conn.commit()
34
  conn.close()
35
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  def add_item(category, section, title, content):
 
38
  if not all([category, section, title, content]):
39
  return "❌ All fields required!", load_table("", "", "")
40
+
41
  conn = sqlite3.connect(DB_PATH)
42
  cursor = conn.cursor()
43
  cursor.execute(
44
  "INSERT INTO items (category, section, title, content) VALUES (?, ?, ?, ?)",
45
+ (category.strip(), section.strip(), title.strip(), content.strip()),
46
  )
47
  conn.commit()
48
  conn.close()
49
+
50
  return "βœ… Added!", load_table("", "", "")
51
 
52
+
53
  def load_table(category_filter, section_filter, title_filter):
 
54
  conn = sqlite3.connect(DB_PATH)
55
+
56
  query = """
57
+ SELECT id, category, section, title, content,
58
  datetime(updated_at, 'localtime') as updated
59
+ FROM items
60
+ WHERE 1=1
61
  """
62
  params = []
63
+
64
  if category_filter:
65
  query += " AND category LIKE ?"
66
  params.append(f"%{category_filter}%")
67
+
68
  if section_filter:
69
  query += " AND section LIKE ?"
70
  params.append(f"%{section_filter}%")
71
+
72
  if title_filter:
73
  query += " AND title LIKE ?"
74
  params.append(f"%{title_filter}%")
75
+
76
  query += " ORDER BY updated_at DESC"
77
+
78
  df = pd.read_sql_query(query, conn, params=params)
79
  conn.close()
80
+
 
81
  if not df.empty:
82
+ df["content"] = df["content"].apply(
83
+ lambda x: (x[:100] + "...") if isinstance(x, str) and len(x) > 100 else x
84
+ )
85
+
86
  return df
87
 
88
+
89
  def update_item(item_id, category, section, title, content):
90
+ if item_id is None or item_id == "":
 
91
  return "❌ Select an item first!", load_table("", "", "")
92
+
93
  if not all([category, section, title, content]):
94
  return "❌ All fields required!", load_table("", "", "")
95
+
96
  conn = sqlite3.connect(DB_PATH)
97
  cursor = conn.cursor()
98
  cursor.execute(
99
+ """
100
+ UPDATE items
101
+ SET category=?,
102
+ section=?,
103
+ title=?,
104
+ content=?,
105
+ updated_at=CURRENT_TIMESTAMP
106
+ WHERE id=?
107
+ """,
108
+ (category.strip(), section.strip(), title.strip(), content.strip(), int(item_id)),
109
  )
110
  conn.commit()
111
  conn.close()
112
+
113
  return "βœ… Updated!", load_table("", "", "")
114
 
115
+
116
  def delete_item(item_id):
117
+ if item_id is None or item_id == "":
 
118
  return "❌ Select an item first!", load_table("", "", "")
119
+
120
  conn = sqlite3.connect(DB_PATH)
121
  cursor = conn.cursor()
122
  cursor.execute("DELETE FROM items WHERE id=?", (int(item_id),))
123
  conn.commit()
124
  conn.close()
125
+
126
  return "βœ… Deleted!", load_table("", "", "")
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
  def load_item_by_id(item_id):
130
+ if item_id is None or item_id == "":
 
131
  return "", "", "", ""
132
+
133
  conn = sqlite3.connect(DB_PATH)
134
  cursor = conn.cursor()
135
+ cursor.execute(
136
+ "SELECT category, section, title, content FROM items WHERE id=?",
137
+ (int(item_id),),
138
+ )
139
  result = cursor.fetchone()
140
  conn.close()
141
+
142
  if result:
143
  return result[0], result[1], result[2], result[3]
144
  return "", "", "", ""
145
 
146
+
147
  def get_char_count(text):
 
148
  return f"πŸ“Š {len(text)} chars" if text else "πŸ“Š 0 chars"
149
 
150
+
151
  def get_stats():
 
152
  if not os.path.exists(DB_PATH):
153
  return "πŸ’Ύ Database: not created yet"
154
+
155
  conn = sqlite3.connect(DB_PATH)
156
  cursor = conn.cursor()
157
  cursor.execute("SELECT COUNT(*) FROM items")
 
159
  cursor.execute("SELECT COUNT(DISTINCT category) FROM items")
160
  categories = cursor.fetchone()[0]
161
  conn.close()
162
+
163
+ file_size_kb = os.path.getsize(DB_PATH) / 1024
164
+ return f"πŸ’Ύ Database: {total} items, {categories} categories ({file_size_kb:.1f} KB)"
165
+
166
+
167
+ def handle_table_select(df: pd.DataFrame, evt: gr.SelectData):
168
+ if df is None or len(df) == 0:
169
+ return None
170
+ row_idx = evt.index[0]
171
+ try:
172
+ return int(df.iloc[row_idx, 0])
173
+ except Exception:
174
+ return None
175
+
176
 
177
  # Initialize
178
  init_db()
 
180
  # UI
181
  with gr.Blocks(title="Text Archive") as app:
182
  gr.Markdown("# πŸ“ Simple Text Archive")
183
+
 
 
 
 
 
 
 
 
 
 
 
184
  with gr.Row():
185
  db_stats = gr.Markdown(get_stats())
186
  download_btn = gr.DownloadButton("πŸ’Ύ Download DB", value=DB_PATH, size="sm", scale=0)
187
+
 
 
 
 
188
  gr.Markdown("---")
189
+
190
  # Add Form
191
  with gr.Row():
192
  with gr.Column(scale=3):
 
195
  add_category = gr.Textbox(label="Category", placeholder="e.g., Python, Notes, SQL", scale=1)
196
  add_section = gr.Textbox(label="Section", placeholder="e.g., Functions, Snippets", scale=1)
197
  add_title = gr.Textbox(label="Title", placeholder="Short description", scale=2)
198
+
199
  add_content = gr.Textbox(label="Content", placeholder="Your text here...", lines=4)
200
  add_char_count = gr.Markdown("πŸ“Š 0 chars")
201
+
202
  with gr.Row():
203
  add_btn = gr.Button("βž• Add Item", variant="primary", size="sm")
204
  add_status = gr.Textbox(label="", container=False, show_label=False, interactive=False)
205
+
206
  gr.Markdown("---")
207
+
208
  # Search/Filter
209
  gr.Markdown("### πŸ” Search & Filter")
210
  with gr.Row():
 
212
  search_section = gr.Textbox(label="πŸ“ Section", placeholder="Filter...", scale=1)
213
  search_title = gr.Textbox(label="πŸ“Œ Title", placeholder="Filter...", scale=1)
214
  clear_btn = gr.Button("πŸ”„ Clear", size="sm", scale=1)
215
+
216
  # Table
217
+ gr.Markdown("### πŸ“‹ All Items. Click a row to load it into the edit form.")
218
  table = gr.Dataframe(
219
  value=load_table("", "", ""),
220
  label="",
221
  interactive=False,
222
+ column_widths=["5%", "12%", "12%", "20%", "40%", "11%"],
223
  )
224
+
225
  gr.Markdown("---")
226
+
227
  # Edit Form
228
+ gr.Markdown("### ✏️ Edit Selected Item")
229
+
230
  with gr.Row():
231
  edit_id = gr.Number(label="Item ID", precision=0, scale=1)
232
  load_id_btn = gr.Button("πŸ”„ Load by ID", size="sm", scale=0)
233
+
234
  with gr.Row():
235
  edit_category = gr.Textbox(label="Category", scale=1)
236
  edit_section = gr.Textbox(label="Section", scale=1)
237
  edit_title = gr.Textbox(label="Title", scale=2)
238
+
239
  edit_content = gr.Textbox(label="Content", lines=6, max_lines=20)
240
  edit_char_count = gr.Markdown("πŸ“Š 0 chars")
241
+
242
  with gr.Row():
243
  update_btn = gr.Button("πŸ’Ύ Save Changes", variant="primary", size="sm")
244
  delete_btn = gr.Button("πŸ—‘οΈ Delete", variant="stop", size="sm")
245
  edit_status = gr.Textbox(label="", container=False, show_label=False, interactive=False)
246
+
247
  # Events
248
+ add_content.change(fn=get_char_count, inputs=add_content, outputs=add_char_count)
249
+ edit_content.change(fn=get_char_count, inputs=edit_content, outputs=edit_char_count)
250
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  add_btn.click(
252
  fn=add_item,
253
  inputs=[add_category, add_section, add_title, add_content],
254
+ outputs=[add_status, table],
255
  ).then(
256
  fn=lambda: ("", "", "", "", "πŸ“Š 0 chars"),
257
+ outputs=[add_category, add_section, add_title, add_content, add_char_count],
258
  ).then(
259
  fn=get_stats,
260
+ outputs=db_stats,
261
  )
262
+
 
263
  for search_box in [search_category, search_section, search_title]:
264
  search_box.change(
265
  fn=load_table,
266
  inputs=[search_category, search_section, search_title],
267
+ outputs=table,
268
  )
269
+
270
  clear_btn.click(
271
  fn=lambda: ("", "", "", load_table("", "", "")),
272
+ outputs=[search_category, search_section, search_title, table],
273
  )
274
+
 
275
  load_id_btn.click(
276
  fn=load_item_by_id,
277
  inputs=edit_id,
278
+ outputs=[edit_category, edit_section, edit_title, edit_content],
279
  ).then(
280
  fn=get_char_count,
281
  inputs=edit_content,
282
+ outputs=edit_char_count,
283
  )
284
+
 
 
 
 
 
285
  table.select(
286
  fn=handle_table_select,
287
+ inputs=table,
288
+ outputs=edit_id,
289
  ).then(
290
  fn=load_item_by_id,
291
  inputs=edit_id,
292
+ outputs=[edit_category, edit_section, edit_title, edit_content],
293
  ).then(
294
  fn=get_char_count,
295
  inputs=edit_content,
296
+ outputs=edit_char_count,
297
  )
298
+
299
  update_btn.click(
300
  fn=update_item,
301
  inputs=[edit_id, edit_category, edit_section, edit_title, edit_content],
302
+ outputs=[edit_status, table],
303
  ).then(
304
  fn=get_stats,
305
+ outputs=db_stats,
306
  )
307
+
308
  delete_btn.click(
309
  fn=delete_item,
310
  inputs=[edit_id],
311
+ outputs=[edit_status, table],
312
  ).then(
313
  fn=lambda: (None, "", "", "", "", "πŸ“Š 0 chars"),
314
+ outputs=[edit_id, edit_category, edit_section, edit_title, edit_content, edit_char_count],
315
  ).then(
316
  fn=get_stats,
317
+ outputs=db_stats,
318
  )
319
+
 
320
  app.load(fn=get_stats, outputs=db_stats)
321
 
322
  if __name__ == "__main__":
323
+ app.launch()