Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| from datetime import datetime, timedelta | |
| import io | |
| import os | |
| from huggingface_hub import HfApi, hf_hub_download, login | |
| from typing import List, Tuple, Optional | |
| import tempfile | |
| import zipfile | |
| class DataManager: | |
| def __init__(self, token=None): | |
| self.repo_id = "CIV3283/Data_Storage" | |
| self.branch = "data_branch" | |
| # 优先使用传入的token,否则从环境变量获取 | |
| self.token = token or os.getenv('CIV3283_access') | |
| # 如果有token,进行认证 | |
| if self.token: | |
| try: | |
| login(token=self.token) | |
| print("✅ 认证成功") | |
| except Exception as e: | |
| print(f"❌ 认证失败: {e}") | |
| self.api = HfApi(token=self.token) | |
| def get_available_files(self) -> Tuple[List[str], List[str]]: | |
| """获取所有可用的feedback和query文件""" | |
| try: | |
| # 尝试获取仓库中的所有文件 | |
| repo_files = self.api.list_repo_files( | |
| repo_id=self.repo_id, | |
| revision=self.branch, | |
| token=self.token, | |
| repo_type="space" # 明确指定这是一个space | |
| ) | |
| feedback_files = [] | |
| query_files = [] | |
| for file in repo_files: | |
| if file.endswith('_feedback_log.csv') and 'CIV3283_Student_' in file: | |
| feedback_files.append(file) | |
| elif file.endswith('_query_log.csv') and 'CIV3283_Student_' in file: | |
| query_files.append(file) | |
| return sorted(feedback_files), sorted(query_files) | |
| except Exception as e: | |
| print(f"❌ 获取文件列表错误: {e}") | |
| # 如果API调用失败,尝试手动生成可能的文件名列表 | |
| return self._generate_possible_filenames() | |
| def _generate_possible_filenames(self) -> Tuple[List[str], List[str]]: | |
| """生成可能的文件名列表作为后备方案""" | |
| feedback_files = [] | |
| query_files = [] | |
| # 生成01到95的所有可能文件名 | |
| for i in range(1, 96): | |
| student_id = f"{i:02d}" # 格式化为两位数 | |
| feedback_file = f"CIV3283_Student_{student_id}_feedback_log.csv" | |
| query_file = f"CIV3283_Student_{student_id}_query_log.csv" | |
| feedback_files.append(feedback_file) | |
| query_files.append(query_file) | |
| return feedback_files, query_files | |
| def download_and_read_csv(self, filename: str) -> pd.DataFrame: | |
| """下载并读取CSV文件""" | |
| try: | |
| # 下载文件到临时位置 | |
| local_file = hf_hub_download( | |
| repo_id=self.repo_id, | |
| filename=filename, | |
| revision=self.branch, | |
| repo_type="space", | |
| token=self.token, | |
| cache_dir=tempfile.gettempdir() | |
| ) | |
| # 读取CSV | |
| df = pd.read_csv(local_file) | |
| # 确保timestamp列存在并转换为datetime | |
| if 'timestamp' in df.columns: | |
| df['timestamp'] = pd.to_datetime(df['timestamp']) | |
| return df | |
| except Exception as e: | |
| print(f"❌ 读取文件 {filename} 失败: {e}") | |
| # 返回空DataFrame而不是完全失败 | |
| return pd.DataFrame() | |
| def filter_data_by_date(self, df: pd.DataFrame, start_date: str, end_date: str) -> pd.DataFrame: | |
| """根据日期范围筛选数据""" | |
| if df.empty or 'timestamp' not in df.columns: | |
| return df | |
| try: | |
| # 转换输入的日期字符串 | |
| start_dt = pd.to_datetime(start_date) | |
| if end_date == "现在": | |
| end_dt = pd.Timestamp.now() | |
| else: | |
| end_dt = pd.to_datetime(end_date) + timedelta(days=1) - timedelta(seconds=1) # 包含整天 | |
| # 筛选数据 | |
| mask = (df['timestamp'] >= start_dt) & (df['timestamp'] <= end_dt) | |
| return df[mask] | |
| except Exception as e: | |
| print(f"Error filtering data: {e}") | |
| return df | |
| def process_files(self, file_type: str, start_date: str, end_date: str) -> Tuple[str, str, str]: | |
| """处理文件并生成下载内容""" | |
| feedback_files, query_files = self.get_available_files() | |
| result_data = [] | |
| file_info = [] | |
| total_records = 0 | |
| # 根据选择的文件类型确定要处理的文件 | |
| files_to_process = [] | |
| if file_type in ["反馈文件", "两者都要"]: | |
| files_to_process.extend([(f, "feedback") for f in feedback_files]) | |
| if file_type in ["查询文件", "两者都要"]: | |
| files_to_process.extend([(f, "query") for f in query_files]) | |
| # 处理每个文件 | |
| for filename, ftype in files_to_process: | |
| df = self.download_and_read_csv(filename) | |
| if not df.empty: | |
| original_count = len(df) | |
| filtered_df = self.filter_data_by_date(df, start_date, end_date) | |
| filtered_count = len(filtered_df) | |
| if filtered_count > 0: | |
| result_data.append(filtered_df) | |
| total_records += filtered_count | |
| file_info.append(f"{filename}: {original_count}条记录 → {filtered_count}条符合条件") | |
| # 合并所有数据 | |
| if result_data: | |
| combined_df = pd.concat(result_data, ignore_index=True) | |
| # 生成CSV内容 | |
| csv_buffer = io.StringIO() | |
| combined_df.to_csv(csv_buffer, index=False, encoding='utf-8') | |
| csv_content = csv_buffer.getvalue() | |
| # 生成文件名 | |
| end_date_str = "现在" if end_date == "现在" else end_date | |
| filename = f"CIV3283_数据导出_{file_type}_{start_date}_to_{end_date_str}.csv" | |
| # 生成状态信息 | |
| status = f"✅ 成功处理 {len(files_to_process)} 个文件\n" | |
| status += f"📊 总共找到 {total_records} 条符合条件的记录\n" | |
| status += f"📅 时间范围: {start_date} 到 {end_date_str}\n\n" | |
| status += "文件详情:\n" + "\n".join(file_info) | |
| return csv_content, filename, status | |
| else: | |
| return "", "", "❌ 没有找到符合条件的数据" | |
| # 全局变量存储数据管理器 | |
| data_manager = None | |
| def initialize_data_manager(hf_token=None): | |
| """初始化数据管理器""" | |
| global data_manager | |
| data_manager = DataManager(token=hf_token) | |
| return data_manager | |
| def process_and_download(file_type, start_date, end_date, hf_token): | |
| """处理请求并返回下载文件""" | |
| try: | |
| # 确保数据管理器已初始化 | |
| global data_manager | |
| env_token = os.getenv('CIV3283_access') | |
| current_token = hf_token or env_token | |
| if data_manager is None or data_manager.token != current_token: | |
| data_manager = initialize_data_manager(current_token) | |
| csv_content, filename, status = data_manager.process_files(file_type, start_date, end_date) | |
| if csv_content: | |
| # 创建临时文件 | |
| temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False, encoding='utf-8') | |
| temp_file.write(csv_content) | |
| temp_file.close() | |
| return status, temp_file.name, filename | |
| else: | |
| return status, None, None | |
| except Exception as e: | |
| return f"❌ 处理过程中出现错误: {str(e)}", None, None | |
| def get_file_summary(hf_token=None): | |
| """获取文件概览""" | |
| try: | |
| # 如果还没有初始化或token发生变化,重新初始化 | |
| global data_manager | |
| env_token = os.getenv('CIV3283_access') | |
| current_token = hf_token or env_token | |
| if data_manager is None or data_manager.token != current_token: | |
| data_manager = initialize_data_manager(current_token) | |
| feedback_files, query_files = data_manager.get_available_files() | |
| summary = f"📁 当前可用文件:\n" | |
| summary += f" 反馈文件: {len(feedback_files)} 个\n" | |
| summary += f" 查询文件: {len(query_files)} 个\n" | |
| summary += f" 总计: {len(feedback_files) + len(query_files)} 个文件" | |
| if env_token: | |
| summary += f"\n✅ 使用环境变量 CIV3283_access 认证" | |
| elif hf_token: | |
| summary += f"\n✅ 使用手动输入的token认证" | |
| else: | |
| summary += f"\n⚠️ 未找到认证token(可能无法访问私有仓库)" | |
| return summary | |
| except Exception as e: | |
| return f"❌ 获取文件列表失败: {str(e)}" | |
| # 创建Gradio界面 | |
| def create_interface(): | |
| with gr.Blocks(title="CIV3283 数据管理器", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 🗃️ CIV3283 数据存储管理器") | |
| gr.Markdown("从 Hugging Face Space 的 data_branch 分支整理和下载学生数据") | |
| # 认证部分 | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| hf_token = gr.Textbox( | |
| label="🔐 Hugging Face Token (可选)", | |
| placeholder="hf_xxxxxxxxxxxx", | |
| type="password", | |
| info="可选:手动输入token,留空将使用环境变量 CIV3283_access" | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown(f""" | |
| **环境变量状态:** | |
| {'✅ CIV3283_access 已设置' if os.getenv('CIV3283_access') else '❌ CIV3283_access 未找到'} | |
| """) | |
| # 文件概览 | |
| with gr.Row(): | |
| file_summary = gr.Textbox( | |
| label="📊 文件概览", | |
| value="点击刷新按钮获取文件列表", | |
| interactive=False, | |
| max_lines=6 | |
| ) | |
| refresh_btn = gr.Button("🔄 刷新文件列表", variant="secondary") | |
| # 自动刷新一次(使用环境变量) | |
| demo.load( | |
| fn=lambda: get_file_summary(None), | |
| outputs=file_summary | |
| ) | |
| refresh_btn.click( | |
| fn=get_file_summary, | |
| inputs=[hf_token], | |
| outputs=file_summary | |
| ) | |
| # 主要控制面板 | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| file_type = gr.Radio( | |
| choices=["查询文件", "反馈文件", "两者都要"], | |
| value="查询文件", | |
| label="📂 选择文件类型" | |
| ) | |
| start_date = gr.Textbox( | |
| label="📅 开始日期", | |
| placeholder="2025-06-16", | |
| value="2025-06-16", | |
| info="格式: YYYY-MM-DD" | |
| ) | |
| end_date = gr.Textbox( | |
| label="📅 结束日期", | |
| placeholder="2025-06-20 或 现在", | |
| value="现在", | |
| info="格式: YYYY-MM-DD 或输入'现在'" | |
| ) | |
| process_btn = gr.Button("🚀 处理并生成下载文件", variant="primary", size="lg") | |
| with gr.Column(scale=2): | |
| status_box = gr.Textbox( | |
| label="📋 处理状态", | |
| max_lines=15, | |
| interactive=False | |
| ) | |
| download_file = gr.File( | |
| label="📥 下载文件", | |
| interactive=False | |
| ) | |
| # 使用说明 | |
| with gr.Accordion("📖 使用说明", open=False): | |
| gr.Markdown(""" | |
| ### 认证说明 | |
| - **环境变量优先**: 系统会自动使用环境变量 `CIV3283_access` 中的token | |
| - **手动覆盖**: 如果需要使用不同的token,可以在上方手动输入 | |
| - **获取token**: 如果需要新token,访问 https://huggingface.co/settings/tokens | |
| ### 功能说明 | |
| 1. **文件类型选择**: 选择要导出的数据类型 | |
| - 查询文件: 包含搜索信息和查询响应 | |
| - 反馈文件: 包含学生反馈评论 | |
| - 两者都要: 合并所有数据 | |
| 2. **日期范围**: | |
| - 开始日期: 必须填写,格式 YYYY-MM-DD | |
| - 结束日期: 可以是具体日期或"现在" | |
| 3. **数据格式**: | |
| - Query文件列: student_space, student_id, timestamp, search_info, query_and_response, thumb_feedback | |
| - Feedback文件列: student_space, student_id, timestamp, comment | |
| ### 注意事项 | |
| - 系统会自动发现所有可用的学生文件(01-95,跳过缺失的编号) | |
| - 时间戳格式: 2025-06-16 08:55:33 | |
| - 导出的数据保持原始顺序,不进行时间排序 | |
| - 每次处理会显示详细的文件统计信息 | |
| - 如果无法访问仓库API,系统会尝试预设的文件名列表 | |
| """) | |
| # 绑定处理函数 | |
| process_btn.click( | |
| fn=process_and_download, | |
| inputs=[file_type, start_date, end_date, hf_token], | |
| outputs=[status_box, download_file, gr.State()] | |
| ) | |
| return demo | |
| # 启动应用 | |
| if __name__ == "__main__": | |
| demo = create_interface() | |
| demo.launch( | |
| ) |