testcoder-ui commited on
Commit
85fe453
·
1 Parent(s): f064405

Add 3 major improvements:

Browse files

1. Fix frontend freeze - ensure proper gr.Request handling
2. Add temp file cleanup documentation (Gradio auto-cleans, S3 uses memory)
3. Add history tab - users can view their past generations

Features:
- New history tab with HTML display of user's past evaluations
- get_user_history() method to fetch records from HF Dataset
- Refresh button to load history on demand
- Each history entry shows: timestamp, prompt, scores, video URLs
- Updated README with new features

Technical:
- Fixed UI tab structure and indentation
- Added get_history_html() function for rendering
- History data fetched from evaluations/*.json files in Dataset
- Limit to 10 most recent records

Files changed (2) hide show
  1. README.md +3 -1
  2. app.py +198 -52
README.md CHANGED
@@ -17,10 +17,12 @@ hf_oauth: true
17
  ## ✨ 功能
18
 
19
  - 🔐 强制登录(HF OAuth)
20
- - 📊 次数限制(每天 4 次)
21
  - 🚀 4 个模型同时生成(Sora 2 pro, Seedance Pro, Veo 3.1, Kling 2.6)
22
  - 💾 评分数据保存到 Private Dataset
23
  - ☁️ 图片和视频自动上传到 S3
 
 
24
 
25
  ## 📋 环境变量配置
26
 
 
17
  ## ✨ 功能
18
 
19
  - 🔐 强制登录(HF OAuth)
20
+ - 📊 次数限制(每天 4 次,持久化到 HF Private Dataset,完全免费)
21
  - 🚀 4 个模型同时生成(Sora 2 pro, Seedance Pro, Veo 3.1, Kling 2.6)
22
  - 💾 评分数据保存到 Private Dataset
23
  - ☁️ 图片和视频自动上传到 S3
24
+ - 📜 历史记录查看(用户可查看自己之前的生成记录)
25
+ - 🧹 自动清理(Gradio自动清理上传临时文件,S3上传使用内存操作无本地临时文件)
26
 
27
  ## 📋 环境变量配置
28
 
app.py CHANGED
@@ -173,6 +173,72 @@ class DatasetManager:
173
  logger.error(f"更新用户调用次数失败: {e}")
174
  return False
175
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  def save_evaluation(
177
  self,
178
  username: str,
@@ -532,6 +598,65 @@ def get_user_info(request: gr.Request = None) -> str:
532
  return f"{status_icon} 用户: {username}\n📊 今日已用: {calls_today}/{MAX_DAILY_CALLS}\n✨ 剩余次数: {remaining}"
533
 
534
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
535
  # 创建 Gradio 界面
536
  with gr.Blocks(title="Video Model Evaluator") as demo:
537
  gr.Markdown("""
@@ -569,60 +694,72 @@ with gr.Blocks(title="Video Model Evaluator") as demo:
569
  )
570
  refresh_user_btn = gr.Button("🔄 刷新", size="sm", scale=1)
571
 
572
- with gr.Row():
573
- with gr.Column(scale=1):
574
-
575
- prompt_input = gr.Textbox(
576
- label="提示词 (Prompt)",
577
- placeholder="输入视频生成的提示词...",
578
- lines=3
579
- )
580
-
581
- input_image = gr.Image(
582
- label="输入图片 (可选,用于图生视频 i2v)",
583
- type="filepath",
584
- sources=["upload"]
585
- )
586
-
587
- generate_btn = gr.Button("🚀 生成视频", variant="primary", size="lg")
588
-
589
- status_output = gr.Textbox(
590
- label="生成状态",
591
- interactive=False,
592
- lines=5
593
- )
594
-
595
- with gr.Column(scale=1):
596
- # 视频展示区域
597
- gr.Markdown("### 🎬 生成的视频")
598
-
599
- # 动态创建视频和评分组件
600
- video_components = {}
601
- score_components = {}
602
-
603
- for model_name in MODELS_TO_CALL:
604
- display_name = MODEL_DISPLAY_NAMES.get(model_name, model_name)
605
- video_components[model_name] = gr.Video(
606
- label=display_name,
607
- visible=False
608
- )
609
- score_components[model_name] = gr.Slider(
610
- label=f"{display_name} 评分",
611
- minimum=0,
612
- maximum=10,
613
- step=0.1,
614
- value=5.0,
615
- visible=False
616
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
617
 
618
- # 评分区域
619
- gr.Markdown("### 📊 评分 (0-10分)")
620
 
621
- submit_btn = gr.Button("💾 提交评分", variant="secondary", visible=False)
622
- submit_status = gr.Textbox(
623
- label="提交状态",
624
- interactive=False
625
- )
626
 
627
  # 存储中间数据
628
  model_results_state = gr.State({})
@@ -694,6 +831,8 @@ with gr.Blocks(title="Video Model Evaluator") as demo:
694
  outputs=[submit_status]
695
  )
696
 
 
 
697
  # 刷新用户信息按钮
698
  refresh_user_btn.click(
699
  fn=get_user_info,
@@ -701,6 +840,13 @@ with gr.Blocks(title="Video Model Evaluator") as demo:
701
  outputs=[user_info]
702
  )
703
 
 
 
 
 
 
 
 
704
  # 页面加载时显示初始提示(不绑定函数,避免 "no backend method" 错误)
705
  # 用户信息会在用户操作时更新
706
 
 
173
  logger.error(f"更新用户调用次数失败: {e}")
174
  return False
175
 
176
+ def get_user_history(self, username: str, limit: int = 10) -> List[Dict]:
177
+ """
178
+ 获取用户的历史评分记录
179
+
180
+ Args:
181
+ username: Hugging Face 用户名
182
+ limit: 返回记录数量限制
183
+
184
+ Returns:
185
+ 历史记录列表
186
+ """
187
+ if not self.api or not self.repo_id:
188
+ logger.warning("Dataset API 未配置,无法获取历史记录")
189
+ return []
190
+
191
+ try:
192
+ # 列出评分文件
193
+ files = self.api.list_repo_files(
194
+ repo_id=self.repo_id,
195
+ repo_type="dataset",
196
+ token=self.hf_token
197
+ )
198
+
199
+ # 筛选该用户的评分文件
200
+ user_files = [f for f in files if f.startswith(f"evaluations/") and f.endswith('.json')]
201
+
202
+ # 按时间倒序排序(文件名包含时间戳)
203
+ user_files.sort(reverse=True)
204
+
205
+ history = []
206
+ for file_path in user_files[:limit * 2]: # 多读一些,因为有些可能不是该用户的
207
+ try:
208
+ # 下载文件
209
+ local_path = self.api.hf_hub_download(
210
+ repo_id=self.repo_id,
211
+ filename=file_path,
212
+ repo_type="dataset",
213
+ token=self.hf_token
214
+ )
215
+
216
+ # 读取数据
217
+ with open(local_path, 'r', encoding='utf-8') as f:
218
+ data = json.load(f)
219
+
220
+ # 只返回该用户的记录
221
+ if data.get('user') == username:
222
+ history.append({
223
+ 'timestamp': data.get('timestamp'),
224
+ 'prompt': data.get('prompt', ''),
225
+ 'scores': data.get('scores', {}),
226
+ 'video_urls': data.get('video_urls', {})
227
+ })
228
+
229
+ if len(history) >= limit:
230
+ break
231
+
232
+ except Exception as e:
233
+ logger.warning(f"读取历史记录失败 {file_path}: {e}")
234
+ continue
235
+
236
+ return history
237
+
238
+ except Exception as e:
239
+ logger.error(f"获取用户历史记录失败: {e}")
240
+ return []
241
+
242
  def save_evaluation(
243
  self,
244
  username: str,
 
598
  return f"{status_icon} 用户: {username}\n📊 今日已用: {calls_today}/{MAX_DAILY_CALLS}\n✨ 剩余次数: {remaining}"
599
 
600
 
601
+ def get_history_html(request: gr.Request = None) -> str:
602
+ """获取用户历史记录的HTML"""
603
+ if not request:
604
+ return "<p>⏳ 正在加载...</p>"
605
+
606
+ username, _ = check_user_access(request)
607
+
608
+ if not username:
609
+ return "<p>❌ 请先登录 Hugging Face 账户</p>"
610
+
611
+ if not dataset_manager:
612
+ return "<p>❌ Dataset 未配置</p>"
613
+
614
+ try:
615
+ history = dataset_manager.get_user_history(username, limit=10)
616
+
617
+ if not history:
618
+ return f"<p>📭 暂无历史记录(用户: {username})</p>"
619
+
620
+ html = f"<h3>用户: {username} 的历史记录</h3>"
621
+ html += f"<p>共 {len(history)} 条记录</p>"
622
+ html += "<div style='max-height: 600px; overflow-y: auto;'>"
623
+
624
+ for i, record in enumerate(history, 1):
625
+ timestamp = record.get('timestamp', '未知时间')
626
+ prompt = record.get('prompt', '')
627
+ scores = record.get('scores', {})
628
+ video_urls = record.get('video_urls', {})
629
+
630
+ html += f"""
631
+ <div style='border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 8px; background: #f9f9f9;'>
632
+ <h4>📝 记录 #{i} - {timestamp}</h4>
633
+ <p><strong>提示词:</strong> {prompt[:100]}{'...' if len(prompt) > 100 else ''}</p>
634
+ <p><strong>评分:</strong></p>
635
+ <ul>
636
+ """
637
+
638
+ for model, score in scores.items():
639
+ display_name = MODEL_DISPLAY_NAMES.get(model, model)
640
+ html += f"<li>{display_name}: {score} 分</li>"
641
+
642
+ html += "</ul>"
643
+
644
+ if video_urls:
645
+ html += "<p><strong>生成的视频:</strong></p>"
646
+ for model, url in video_urls.items():
647
+ display_name = MODEL_DISPLAY_NAMES.get(model, model)
648
+ html += f'<p>🎬 <a href="{url}" target="_blank">{display_name}</a></p>'
649
+
650
+ html += "</div>"
651
+
652
+ html += "</div>"
653
+ return html
654
+
655
+ except Exception as e:
656
+ logger.error(f"获取历史记录失败: {e}")
657
+ return f"<p>❌ 获取历史记录失败: {str(e)}</p>"
658
+
659
+
660
  # 创建 Gradio 界面
661
  with gr.Blocks(title="Video Model Evaluator") as demo:
662
  gr.Markdown("""
 
694
  )
695
  refresh_user_btn = gr.Button("🔄 刷新", size="sm", scale=1)
696
 
697
+ with gr.Tabs():
698
+ with gr.Tab("🎬 生成视频"):
699
+ with gr.Row():
700
+ with gr.Column(scale=1):
701
+ prompt_input = gr.Textbox(
702
+ label="提示词 (Prompt)",
703
+ placeholder="输入视频生成的提示词...",
704
+ lines=3
705
+ )
706
+
707
+ input_image = gr.Image(
708
+ label="输入图片 (可选,用于图生视频 i2v)",
709
+ type="filepath",
710
+ sources=["upload"]
711
+ )
712
+
713
+ generate_btn = gr.Button("🚀 生成视频", variant="primary", size="lg")
714
+
715
+ status_output = gr.Textbox(
716
+ label="生成状态",
717
+ interactive=False,
718
+ lines=5
719
+ )
720
+
721
+ with gr.Column(scale=1):
722
+ # 视频展示区域
723
+ gr.Markdown("### 🎬 生成的视频")
724
+
725
+ # 动态创建视频和评分组件
726
+ video_components = {}
727
+ score_components = {}
728
+
729
+ for model_name in MODELS_TO_CALL:
730
+ display_name = MODEL_DISPLAY_NAMES.get(model_name, model_name)
731
+ video_components[model_name] = gr.Video(
732
+ label=display_name,
733
+ visible=False
734
+ )
735
+ score_components[model_name] = gr.Slider(
736
+ label=f"{display_name} 评分",
737
+ minimum=0,
738
+ maximum=10,
739
+ step=0.1,
740
+ value=5.0,
741
+ visible=False
742
+ )
743
+
744
+ # 评分区域
745
+ gr.Markdown("### 📊 评分 (0-10分)")
746
+
747
+ submit_btn = gr.Button("💾 提交评分", variant="secondary", visible=False)
748
+ submit_status = gr.Textbox(
749
+ label="提交状态",
750
+ interactive=False
751
+ )
752
+
753
+ # 历史记录Tab
754
+ with gr.Tab("📜 历史记录"):
755
+ gr.Markdown("""
756
+ ### 📜 我的生成历史
757
 
758
+ 这里显示您最近的视频生成记录(最多10条)
759
+ """)
760
 
761
+ history_refresh_btn = gr.Button("🔄 刷新历史记录", variant="primary")
762
+ history_output = gr.HTML(label="历史记录", value="<p>点击刷新按钮加载历史记录</p>")
 
 
 
763
 
764
  # 存储中间数据
765
  model_results_state = gr.State({})
 
831
  outputs=[submit_status]
832
  )
833
 
834
+ # 注意: gr.Request 会被 Gradio 自动注入到 on_submit 的 request 参数
835
+
836
  # 刷新用户信息按钮
837
  refresh_user_btn.click(
838
  fn=get_user_info,
 
840
  outputs=[user_info]
841
  )
842
 
843
+ # 刷新历史记录按钮
844
+ history_refresh_btn.click(
845
+ fn=get_history_html,
846
+ inputs=None,
847
+ outputs=[history_output]
848
+ )
849
+
850
  # 页面加载时显示初始提示(不绑定函数,避免 "no backend method" 错误)
851
  # 用户信息会在用户操作时更新
852