mingyang22 commited on
Commit
e5e777c
·
verified ·
1 Parent(s): 4ec678b

fix: stabilize note editor save/sync and filters

Browse files
Files changed (1) hide show
  1. app.py +61 -50
app.py CHANGED
@@ -1,50 +1,18 @@
1
  import gradio as gr
2
  import os
3
  import json
4
- import sqlite3
5
- import pandas as pd
6
  from huggingface_hub import HfApi, hf_hub_download
7
  from datetime import datetime
8
  import shutil
9
  from pathlib import Path
10
- from fastapi.responses import Response
11
 
12
  # --- 配置 (优先从环境变量读取) ---
13
  DATASET_REPO_ID = os.environ.get("DATASET_REPO_ID", "mingyang22/huggingface-notes")
14
  HF_TOKEN = os.environ.get("HF_TOKEN") # 必须在 Space 设置中配置
15
  REMOTE_NOTES_PATH = "db/notes.json"
16
 
17
- APP_MANIFEST = {
18
- "name": "HF 笔记 Pro",
19
- "short_name": "HF笔记",
20
- "start_url": "/",
21
- "display": "standalone",
22
- "background_color": "#0a0a0a",
23
- "theme_color": "#3b82f6",
24
- "description": "Hugging Face 高级云端同步笔记",
25
- "icons": [
26
- {
27
- "src": "/pwa-icon.svg",
28
- "sizes": "any",
29
- "type": "image/svg+xml",
30
- "purpose": "any maskable"
31
- }
32
- ],
33
- }
34
-
35
- PWA_ICON_SVG = """<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'>
36
- <rect width='128' height='128' rx='28' fill='#0a0a0a' stroke='#333' stroke-width='2'/>
37
- <circle cx='64' cy='64' r='40' fill='url(#grad)'/>
38
- <defs>
39
- <linearGradient id='grad' x1='0' y1='0' x2='1' y2='1'>
40
- <stop offset='0%' stop-color='#7c3aed'/><stop offset='100%' stop-color='#db2777'/>
41
- </linearGradient>
42
- </defs>
43
- <path d='M45 45h38v38H45z' fill='#fff' opacity='0.9'/>
44
- </svg>"""
45
-
46
  PWA_HEAD = """
47
- <link rel="manifest" href="/manifest.webmanifest" />
48
  <meta name="theme-color" content="#3b82f6" />
49
  <style>
50
  /* 玻璃质感与高级深色主题 CSS */
@@ -264,20 +232,22 @@ def get_note_detail(note_id):
264
  return n["title"], n["content"], n["updated_at"]
265
  return "", "", ""
266
 
267
- def handle_save(note_id, title, content):
268
- if not title and not content: return "无内容可保存", load_notes_list()
 
 
269
  notes = read_notes()
270
  now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
271
-
272
  found = False
273
  for n in notes:
274
  if n["id"] == note_id:
275
  n["title"], n["content"], n["updated_at"] = title, content, now
276
  found = True
277
  break
278
-
279
  if not found:
280
- new_id = datetime.now().strftime("%Y%m%d%H%M%S")
281
  new_note = {
282
  "id": new_id,
283
  "title": title or "新笔记",
@@ -288,10 +258,16 @@ def handle_save(note_id, title, content):
288
  }
289
  notes.insert(0, new_note)
290
  note_id = new_id
291
-
292
  write_notes(notes)
293
- _, msg = sync_manager.push()
294
- return f"已本地保存 | {msg}", load_notes_list(), note_id
 
 
 
 
 
 
295
 
296
  def handle_delete(note_id, current_filter):
297
  if not note_id: return "未选择笔记", load_notes_list(current_filter), ""
@@ -316,7 +292,7 @@ def handle_pin(note_id, current_filter):
316
  n["is_pinned"] = not n.get("is_pinned", False)
317
  break
318
  write_notes(notes)
319
- backup_msg = sync_manager.push()[1]
320
  return load_notes_list(current_filter)
321
 
322
  # --- Gradio UI ---
@@ -351,6 +327,7 @@ with gr.Blocks(theme=gr.themes.Default(), head=PWA_HEAD) as demo:
351
  # 3. 编辑器栏
352
  with gr.Column(scale=4):
353
  with gr.Row():
 
354
  btn_pin = gr.Button("📌 置顶", variant="secondary", size="sm")
355
  btn_del = gr.Button("🗑️ 删除", variant="stop", size="sm")
356
  btn_ai = gr.Button("AI 润色", variant="primary", size="sm", elem_id="ai_btn")
@@ -360,27 +337,61 @@ with gr.Blocks(theme=gr.themes.Default(), head=PWA_HEAD) as demo:
360
  edit_date = gr.Markdown("", elem_id="note_date")
361
 
362
  # --- 交互事件 ---
363
-
364
  def on_note_select(evt: gr.SelectData, filt):
365
  curr_list = load_notes_list(filt)
 
 
366
  note_id = curr_list[evt.index[0]][0]
367
  title, content, date = get_note_detail(note_id)
368
  return note_id, title, content, f"最后修改: {date}"
369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  note_list.select(on_note_select, [current_filter_state], [selected_note_id, edit_title, edit_content, edit_date])
371
-
372
  search_box.change(load_notes_list, [current_filter_state, search_box], [note_list])
373
-
374
  # 离开焦点时保存
375
- edit_title.blur(handle_save, [selected_note_id, edit_title, edit_content], [status_text, note_list, selected_note_id])
376
- edit_content.blur(handle_save, [selected_note_id, edit_title, edit_content], [status_text, note_list, selected_note_id])
377
-
 
 
 
 
 
 
378
  btn_new.click(lambda: ("", "新笔记", "", ""), None, [selected_note_id, edit_title, edit_content, edit_date])
379
  btn_pin.click(handle_pin, [selected_note_id, current_filter_state], [note_list])
380
  btn_del.click(handle_delete, [selected_note_id, current_filter_state], [status_text, note_list, selected_note_id])
381
-
382
  btn_sync_pull.click(lambda: (sync_manager.pull()[1], load_notes_list()), None, [status_text, note_list])
383
-
384
  def ai_polish(content):
385
  if not content: return content
386
  return f"✨ [AI 润色已模拟完成]\n\n{content}\n\n(请在本地动作中使用完整的 DeepSeek 润色服务)"
 
1
  import gradio as gr
2
  import os
3
  import json
 
 
4
  from huggingface_hub import HfApi, hf_hub_download
5
  from datetime import datetime
6
  import shutil
7
  from pathlib import Path
8
+ from uuid import uuid4
9
 
10
  # --- 配置 (优先从环境变量读取) ---
11
  DATASET_REPO_ID = os.environ.get("DATASET_REPO_ID", "mingyang22/huggingface-notes")
12
  HF_TOKEN = os.environ.get("HF_TOKEN") # 必须在 Space 设置中配置
13
  REMOTE_NOTES_PATH = "db/notes.json"
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  PWA_HEAD = """
 
16
  <meta name="theme-color" content="#3b82f6" />
17
  <style>
18
  /* 玻璃质感与高级深色主题 CSS */
 
232
  return n["title"], n["content"], n["updated_at"]
233
  return "", "", ""
234
 
235
+ def handle_save(note_id, title, content, push_cloud=False):
236
+ if not title and not content:
237
+ return "无内容可保存", load_notes_list(), note_id
238
+
239
  notes = read_notes()
240
  now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
241
+
242
  found = False
243
  for n in notes:
244
  if n["id"] == note_id:
245
  n["title"], n["content"], n["updated_at"] = title, content, now
246
  found = True
247
  break
248
+
249
  if not found:
250
+ new_id = uuid4().hex
251
  new_note = {
252
  "id": new_id,
253
  "title": title or "新笔记",
 
258
  }
259
  notes.insert(0, new_note)
260
  note_id = new_id
261
+
262
  write_notes(notes)
263
+
264
+ if push_cloud:
265
+ _, msg = sync_manager.push()
266
+ status = f"已保存并同步 | {msg}"
267
+ else:
268
+ status = "已自动保存到本地"
269
+
270
+ return status, load_notes_list(), note_id
271
 
272
  def handle_delete(note_id, current_filter):
273
  if not note_id: return "未选择笔记", load_notes_list(current_filter), ""
 
292
  n["is_pinned"] = not n.get("is_pinned", False)
293
  break
294
  write_notes(notes)
295
+ sync_manager.push()
296
  return load_notes_list(current_filter)
297
 
298
  # --- Gradio UI ---
 
327
  # 3. 编辑器栏
328
  with gr.Column(scale=4):
329
  with gr.Row():
330
+ btn_save = gr.Button("💾 保存", variant="primary", size="sm")
331
  btn_pin = gr.Button("📌 置顶", variant="secondary", size="sm")
332
  btn_del = gr.Button("🗑️ 删除", variant="stop", size="sm")
333
  btn_ai = gr.Button("AI 润色", variant="primary", size="sm", elem_id="ai_btn")
 
337
  edit_date = gr.Markdown("", elem_id="note_date")
338
 
339
  # --- 交互事件 ---
340
+
341
  def on_note_select(evt: gr.SelectData, filt):
342
  curr_list = load_notes_list(filt)
343
+ if not curr_list or not evt.index or evt.index[0] >= len(curr_list):
344
+ return "", "", "", ""
345
  note_id = curr_list[evt.index[0]][0]
346
  title, content, date = get_note_detail(note_id)
347
  return note_id, title, content, f"最后修改: {date}"
348
 
349
+ def handle_autosave(note_id, title, content):
350
+ return handle_save(note_id, title, content, push_cloud=False)
351
+
352
+ def handle_manual_save(note_id, title, content):
353
+ return handle_save(note_id, title, content, push_cloud=True)
354
+
355
+ def switch_filter(filter_type, search_query):
356
+ return (
357
+ filter_type,
358
+ load_notes_list(filter_type, search_query),
359
+ "",
360
+ "",
361
+ "",
362
+ "",
363
+ f"已切换到:{filter_type}"
364
+ )
365
+
366
+ def switch_all(search_query):
367
+ return switch_filter("all", search_query)
368
+
369
+ def switch_pinned(search_query):
370
+ return switch_filter("pinned", search_query)
371
+
372
+ def switch_trash(search_query):
373
+ return switch_filter("trash", search_query)
374
+
375
  note_list.select(on_note_select, [current_filter_state], [selected_note_id, edit_title, edit_content, edit_date])
376
+
377
  search_box.change(load_notes_list, [current_filter_state, search_box], [note_list])
378
+
379
  # 离开焦点时保存
380
+ edit_title.blur(handle_autosave, [selected_note_id, edit_title, edit_content], [status_text, note_list, selected_note_id])
381
+ edit_content.blur(handle_autosave, [selected_note_id, edit_title, edit_content], [status_text, note_list, selected_note_id])
382
+ # 显式保存按钮(PWA/移动端更可靠)
383
+ btn_save.click(handle_manual_save, [selected_note_id, edit_title, edit_content], [status_text, note_list, selected_note_id])
384
+
385
+ btn_all.click(switch_all, [search_box], [current_filter_state, note_list, selected_note_id, edit_title, edit_content, edit_date, status_text])
386
+ btn_pinned.click(switch_pinned, [search_box], [current_filter_state, note_list, selected_note_id, edit_title, edit_content, edit_date, status_text])
387
+ btn_trash.click(switch_trash, [search_box], [current_filter_state, note_list, selected_note_id, edit_title, edit_content, edit_date, status_text])
388
+
389
  btn_new.click(lambda: ("", "新笔记", "", ""), None, [selected_note_id, edit_title, edit_content, edit_date])
390
  btn_pin.click(handle_pin, [selected_note_id, current_filter_state], [note_list])
391
  btn_del.click(handle_delete, [selected_note_id, current_filter_state], [status_text, note_list, selected_note_id])
392
+
393
  btn_sync_pull.click(lambda: (sync_manager.pull()[1], load_notes_list()), None, [status_text, note_list])
394
+
395
  def ai_polish(content):
396
  if not content: return content
397
  return f"✨ [AI 润色已模拟完成]\n\n{content}\n\n(请在本地动作中使用完整的 DeepSeek 润色服务)"