testcoder-ui commited on
Commit
eb120a3
·
1 Parent(s): a9ea3b3

Add admin tool to adjust user daily quota

Browse files

Features:
✅ Show user status (calls used/remaining)
✅ Reset user quota (set to 0, give 4 new calls)
✅ Set specific used count
✅ Add extra quota (supports over-quota like 8 calls/day)

Usage:
python admin_adjust_user_quota.py <username> show
python admin_adjust_user_quota.py <username> reset
python admin_adjust_user_quota.py <username> set 2
python admin_adjust_user_quota.py <username> add 4

Benefits:
- Emergency quota reset for users
- VIP users with higher daily limits (e.g. 8 calls/day)
- Easy quota management via CLI
- All operations require confirmation
- Audit trail in 'note' field

Data structure:
call_counts/2026-01-09/username.json
{
"user": "username",
"date": "2026-01-09",
"count": 2,
"note": "Set to 2 by admin"
}

Also improved evaluation storage:
evaluations/{username}/{timestamp}.jsonl
- User-grouped for better privacy
- Faster history queries
- GDPR compliant (easy export/delete)

Documentation: ADMIN_TOOLS.md

Files changed (3) hide show
  1. ADMIN_TOOLS.md +202 -0
  2. admin_adjust_user_quota.py +255 -0
  3. app.py +9 -7
ADMIN_TOOLS.md ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🔧 管理员工具
2
+
3
+ ## 用户配额管理
4
+
5
+ ### 📝 脚本: `admin_adjust_user_quota.py`
6
+
7
+ 用于调整用户的每日调用次数配额。
8
+
9
+ ---
10
+
11
+ ## 🚀 使用方法
12
+
13
+ ### 1. 查看用户状态
14
+
15
+ ```bash
16
+ export HF_TOKEN='your_hf_token_here'
17
+ python admin_adjust_user_quota.py <username> show
18
+ ```
19
+
20
+ **示例**:
21
+ ```bash
22
+ python admin_adjust_user_quota.py learnmlf show
23
+ ```
24
+
25
+ **输出**:
26
+ ```
27
+ ============================================================
28
+ 📊 用户状态: learnmlf
29
+ ============================================================
30
+ 日期: 2026-01-09
31
+ 已用次数: 3/4
32
+ 剩余次数: 1
33
+ 状态: ✅ 可用
34
+ ============================================================
35
+ ```
36
+
37
+ ---
38
+
39
+ ### 2. 重置用户配额(给满4次)
40
+
41
+ ```bash
42
+ python admin_adjust_user_quota.py <username> reset
43
+ ```
44
+
45
+ **作用**: 将用户今天的已用次数设为0,相当于给他4次新配额
46
+
47
+ **示例**:
48
+ ```bash
49
+ python admin_adjust_user_quota.py learnmlf reset
50
+ ```
51
+
52
+ **流程**:
53
+ 1. 显示当前状态
54
+ 2. 要求确认(输入 `yes`)
55
+ 3. 重置为0
56
+ 4. 显示更新后的状态
57
+
58
+ ---
59
+
60
+ ### 3. 设置已用次数
61
+
62
+ ```bash
63
+ python admin_adjust_user_quota.py <username> set <count>
64
+ ```
65
+
66
+ **作用**: 直接设置用户今天已用的次数
67
+
68
+ **示例**:
69
+ ```bash
70
+ # 设置为已用2次(剩余2次)
71
+ python admin_adjust_user_quota.py learnmlf set 2
72
+
73
+ # 设置为已用0次(剩余4次)
74
+ python admin_adjust_user_quota.py learnmlf set 0
75
+ ```
76
+
77
+ ---
78
+
79
+ ### 4. 增加额外配额
80
+
81
+ ```bash
82
+ python admin_adjust_user_quota.py <username> add <extra_count>
83
+ ```
84
+
85
+ **作用**: 给用户增加额外的调用次数(超过默认的4次)
86
+
87
+ **示例**:
88
+ ```bash
89
+ # 给用户额外增加2次配额
90
+ python admin_adjust_user_quota.py learnmlf add 2
91
+
92
+ # 如果用户原本已用3次,增加2次后,变为已用1次(相当于总共6次配额)
93
+ ```
94
+
95
+ **逻辑**:
96
+ - 原理:减少已用次数 = 增加剩余次数
97
+ - 如果用户已用3次,增加2次配额后,已用次数变为1次
98
+ - 用户实际可用:4 - 1 = 3次(比重置多2次)
99
+
100
+ ---
101
+
102
+ ## 📊 使用场景
103
+
104
+ ### 场景1: 用户配额用完了,但需要紧急使用
105
+
106
+ ```bash
107
+ # 快速重置,给他4次新配额
108
+ python admin_adjust_user_quota.py user123 reset
109
+ ```
110
+
111
+ ### 场景2: VIP用户,给他每天8次配额
112
+
113
+ ```bash
114
+ # 假设他今天用了2次,给他增加4次额外配额
115
+ python admin_adjust_user_quota.py vip_user add 4
116
+ # 结果:已用次数从2变为-2,相当于还能用6次(总共8次)
117
+ ```
118
+
119
+ ### 场景3: 用户误操作浪费了配额
120
+
121
+ ```bash
122
+ # 重置给他4次新配额
123
+ python admin_adjust_user_quota.py user456 reset
124
+ ```
125
+
126
+ ### 场景4: 查看用户使用情况
127
+
128
+ ```bash
129
+ # 查看某个用户今天用了多少次
130
+ python admin_adjust_user_quota.py user789 show
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 🔐 安全注意事项
136
+
137
+ 1. **HF_TOKEN 权限**: 需要对 Dataset 有写入权限的 Token
138
+ 2. **确认操作**: 所有修改操作都需要输入 `yes` 确认
139
+ 3. **不可撤销**: 修改后立即生效,无法撤销(但可以再次调整)
140
+ 4. **日期隔离**: 每天的配额独立,修改今天的不影响昨天/明天
141
+
142
+ ---
143
+
144
+ ## 📂 数据存储位置
145
+
146
+ ```
147
+ HF Dataset: learnmlf/video-evaluations
148
+ ├── call_counts/
149
+ │ └── 2026-01-09/
150
+ │ └── username.json ← 这个文件被修改
151
+ ```
152
+
153
+ **文件格式**:
154
+ ```json
155
+ {
156
+ "user": "username",
157
+ "date": "2026-01-09",
158
+ "count": 2,
159
+ "last_updated": "2026-01-09T12:34:56",
160
+ "note": "Set to 2 by admin"
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## 💡 常见问题
167
+
168
+ ### Q: 增加配额会影响其他天吗?
169
+ A: 不会。每天的配额独立存储,只影响当天。
170
+
171
+ ### Q: 可以给用户设置负数吗?
172
+ A: 可以,使用 `add` 命令增加足够多次数即可。负数意味着用户有"额外"配额。
173
+
174
+ ### Q: 用户看得到管理员的调整吗?
175
+ A: 用户只能看到剩余次数,看不到 `note` 字段的备注。
176
+
177
+ ### Q: 如何批量调整多个用户?
178
+ A: 可以写个 bash 循环:
179
+ ```bash
180
+ for user in user1 user2 user3; do
181
+ python admin_adjust_user_quota.py $user reset
182
+ done
183
+ ```
184
+
185
+ ---
186
+
187
+ ## 🔄 与应用的集成
188
+
189
+ 用户在前端看到的次数会**实时更新**:
190
+ - 应用从 `call_counts/{date}/{username}.json` 读取
191
+ - 管理员修改文件后,用户刷新页面即可看到新配额
192
+ - 无需重启应用
193
+
194
+ ---
195
+
196
+ ## 📝 日志
197
+
198
+ 所有修改都会在 `note` 字段留下记录:
199
+ - `"Reset by admin"` - 管理员重置
200
+ - `"Set to X by admin"` - 管理员设置为X
201
+ - `"Added X extra calls by admin (was Y)"` - 管理员增加X次(原来是Y)
202
+
admin_adjust_user_quota.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ 管理员工具:调整用户的每日调用次数配额
4
+
5
+ 用法:
6
+ python admin_adjust_user_quota.py <username> <action>
7
+
8
+ 示例:
9
+ # 重置用户的今日调用次数为0(相当于给他4次新配额)
10
+ python admin_adjust_user_quota.py learnmlf reset
11
+
12
+ # 设置用户今日已用次数为1(剩余3次)
13
+ python admin_adjust_user_quota.py learnmlf set 1
14
+
15
+ # 查看用户当前状态
16
+ python admin_adjust_user_quota.py learnmlf show
17
+
18
+ # 给用户额外增加2次配额(即今日可用6次)
19
+ python admin_adjust_user_quota.py learnmlf add 2
20
+ """
21
+
22
+ import os
23
+ import sys
24
+ from datetime import datetime
25
+ from app import DatasetManager, MAX_DAILY_CALLS
26
+
27
+ # 配置
28
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
29
+ DATASET_REPO_ID = "learnmlf/video-evaluations"
30
+
31
+ def show_usage():
32
+ """显示使用说明"""
33
+ print(__doc__)
34
+ sys.exit(1)
35
+
36
+ def show_user_status(dm: DatasetManager, username: str):
37
+ """显示用户当前状态"""
38
+ calls_today = dm.get_user_calls_today(username)
39
+ remaining = max(0, MAX_DAILY_CALLS - calls_today)
40
+ today = datetime.now().strftime("%Y-%m-%d")
41
+
42
+ print("\n" + "="*60)
43
+ print(f"📊 用户状态: {username}")
44
+ print("="*60)
45
+ print(f"日期: {today}")
46
+ print(f"已用次数: {calls_today}/{MAX_DAILY_CALLS}")
47
+ print(f"剩余次数: {remaining}")
48
+ print(f"状态: {'✅ 可用' if remaining > 0 else '❌ 已用完'}")
49
+ print("="*60)
50
+
51
+ def reset_user_quota(dm: DatasetManager, username: str):
52
+ """重置用户今日配额(设为0)"""
53
+ today = datetime.now().strftime("%Y-%m-%d")
54
+ count_file = f"call_counts/{today}/{username}.json"
55
+
56
+ import json
57
+ import tempfile
58
+
59
+ count_data = {
60
+ 'user': username,
61
+ 'date': today,
62
+ 'count': 0,
63
+ 'last_updated': datetime.now().isoformat(),
64
+ 'note': 'Reset by admin'
65
+ }
66
+
67
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f:
68
+ json.dump(count_data, f, ensure_ascii=False, indent=2)
69
+ temp_path = f.name
70
+
71
+ try:
72
+ dm.api.upload_file(
73
+ path_or_fileobj=temp_path,
74
+ path_in_repo=count_file,
75
+ repo_id=DATASET_REPO_ID,
76
+ repo_type="dataset",
77
+ token=HF_TOKEN
78
+ )
79
+ print(f"\n✅ 用户 {username} 的今日调用次数已重置为 0")
80
+ print(f" → 剩余次数: {MAX_DAILY_CALLS}")
81
+ finally:
82
+ os.unlink(temp_path)
83
+
84
+ def set_user_quota(dm: DatasetManager, username: str, count: int):
85
+ """设置用户今日已用次数"""
86
+ if count < 0:
87
+ print(f"❌ 错误: 次数不能为负数")
88
+ sys.exit(1)
89
+
90
+ today = datetime.now().strftime("%Y-%m-%d")
91
+ count_file = f"call_counts/{today}/{username}.json"
92
+
93
+ import json
94
+ import tempfile
95
+
96
+ count_data = {
97
+ 'user': username,
98
+ 'date': today,
99
+ 'count': count,
100
+ 'last_updated': datetime.now().isoformat(),
101
+ 'note': f'Set to {count} by admin'
102
+ }
103
+
104
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f:
105
+ json.dump(count_data, f, ensure_ascii=False, indent=2)
106
+ temp_path = f.name
107
+
108
+ try:
109
+ dm.api.upload_file(
110
+ path_or_fileobj=temp_path,
111
+ path_in_repo=count_file,
112
+ repo_id=DATASET_REPO_ID,
113
+ repo_type="dataset",
114
+ token=HF_TOKEN
115
+ )
116
+ remaining = max(0, MAX_DAILY_CALLS - count)
117
+ print(f"\n✅ 用户 {username} 的今日调用次数已设置为 {count}")
118
+ print(f" → 剩余次数: {remaining}")
119
+ finally:
120
+ os.unlink(temp_path)
121
+
122
+ def add_user_quota(dm: DatasetManager, username: str, extra: int):
123
+ """给用户增加额外配额(减少已用次数)"""
124
+ if extra <= 0:
125
+ print(f"❌ 错误: 额外次数必须大于0")
126
+ sys.exit(1)
127
+
128
+ current = dm.get_user_calls_today(username)
129
+ new_count = current - extra # 减少已用次数 = 增加剩余次数(允许负数)
130
+
131
+ today = datetime.now().strftime("%Y-%m-%d")
132
+ count_file = f"call_counts/{today}/{username}.json"
133
+
134
+ import json
135
+ import tempfile
136
+
137
+ count_data = {
138
+ 'user': username,
139
+ 'date': today,
140
+ 'count': new_count,
141
+ 'last_updated': datetime.now().isoformat(),
142
+ 'note': f'Added {extra} extra calls by admin (was {current})'
143
+ }
144
+
145
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f:
146
+ json.dump(count_data, f, ensure_ascii=False, indent=2)
147
+ temp_path = f.name
148
+
149
+ try:
150
+ dm.api.upload_file(
151
+ path_or_fileobj=temp_path,
152
+ path_in_repo=count_file,
153
+ repo_id=DATASET_REPO_ID,
154
+ repo_type="dataset",
155
+ token=HF_TOKEN
156
+ )
157
+ actual_extra = current - new_count
158
+ new_remaining = MAX_DAILY_CALLS - new_count
159
+ print(f"\n✅ 已给用户 {username} 增加 {actual_extra} 次额外配额")
160
+ print(f" 之前: {current}/{MAX_DAILY_CALLS} (剩余 {MAX_DAILY_CALLS - current})")
161
+ print(f" 现在: {new_count}/{MAX_DAILY_CALLS} (剩余 {new_remaining})")
162
+ print(f" → 总配额: {new_remaining} 次")
163
+ finally:
164
+ os.unlink(temp_path)
165
+
166
+ def main():
167
+ if not HF_TOKEN:
168
+ print("❌ 错误: 请设置 HF_TOKEN 环境变量")
169
+ print(" export HF_TOKEN='your_token_here'")
170
+ sys.exit(1)
171
+
172
+ if len(sys.argv) < 3:
173
+ show_usage()
174
+
175
+ username = sys.argv[1]
176
+ action = sys.argv[2].lower()
177
+
178
+ # 初始化 DatasetManager
179
+ dm = DatasetManager(DATASET_REPO_ID, HF_TOKEN)
180
+
181
+ print(f"\n🔧 管理员工具 - 用户配额管理")
182
+ print(f"Dataset: {DATASET_REPO_ID}")
183
+ print(f"日期: {datetime.now().strftime('%Y-%m-%d')}")
184
+ print(f"标准配额: {MAX_DAILY_CALLS} 次/天")
185
+
186
+ if action == "show":
187
+ # 查看状态
188
+ show_user_status(dm, username)
189
+
190
+ elif action == "reset":
191
+ # 重置为0
192
+ print(f"\n正在重置用户 {username} 的配额...")
193
+ show_user_status(dm, username)
194
+
195
+ confirm = input(f"\n确认重置为 0 吗? (yes/no): ")
196
+ if confirm.lower() == 'yes':
197
+ reset_user_quota(dm, username)
198
+ show_user_status(dm, username)
199
+ else:
200
+ print("❌ 操作已取消")
201
+
202
+ elif action == "set":
203
+ # 设置为指定值
204
+ if len(sys.argv) < 4:
205
+ print("❌ 错误: 缺少次数参数")
206
+ print(" 用法: python admin_adjust_user_quota.py <username> set <count>")
207
+ sys.exit(1)
208
+
209
+ try:
210
+ count = int(sys.argv[3])
211
+ except ValueError:
212
+ print(f"❌ 错误: 无效的数字 '{sys.argv[3]}'")
213
+ sys.exit(1)
214
+
215
+ print(f"\n正在设置用户 {username} 的已用次数为 {count}...")
216
+ show_user_status(dm, username)
217
+
218
+ confirm = input(f"\n确认设置为 {count} 吗? (yes/no): ")
219
+ if confirm.lower() == 'yes':
220
+ set_user_quota(dm, username, count)
221
+ show_user_status(dm, username)
222
+ else:
223
+ print("❌ 操作已取消")
224
+
225
+ elif action == "add":
226
+ # 增加额外配额
227
+ if len(sys.argv) < 4:
228
+ print("❌ 错误: 缺少额外次数参数")
229
+ print(" 用法: python admin_adjust_user_quota.py <username> add <extra_count>")
230
+ sys.exit(1)
231
+
232
+ try:
233
+ extra = int(sys.argv[3])
234
+ except ValueError:
235
+ print(f"❌ 错误: 无效的数字 '{sys.argv[3]}'")
236
+ sys.exit(1)
237
+
238
+ print(f"\n正在给用户 {username} 增加 {extra} 次额外配额...")
239
+ show_user_status(dm, username)
240
+
241
+ confirm = input(f"\n确认增加 {extra} 次配额吗? (yes/no): ")
242
+ if confirm.lower() == 'yes':
243
+ add_user_quota(dm, username, extra)
244
+ show_user_status(dm, username)
245
+ else:
246
+ print("❌ 操作已取消")
247
+
248
+ else:
249
+ print(f"❌ 错误: 未知操作 '{action}'")
250
+ print(" 支持的操作: show, reset, set, add")
251
+ show_usage()
252
+
253
+ if __name__ == "__main__":
254
+ main()
255
+
app.py CHANGED
@@ -196,14 +196,16 @@ class DatasetManager:
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(
@@ -281,9 +283,9 @@ class DatasetManager:
281
  temp_file = f.name
282
 
283
  try:
284
- # 生成唯一的文件名基于时间戳
285
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
286
- filename = f"evaluations/{username}_{timestamp}.jsonl"
287
 
288
  # 上传到 Dataset
289
  self.api.upload_file(
 
196
  token=self.hf_token
197
  )
198
 
199
+ # 筛选该用户的评分文件(按用户分组:evaluations/{username}/时间戳.jsonl)
200
+ user_files = [f for f in files
201
+ if f.startswith(f"evaluations/{username}/")
202
+ and f.endswith('.jsonl')]
203
 
204
+ # 按文件名倒序排序(文件名就是时间戳,自然排序
205
  user_files.sort(reverse=True)
206
 
207
  history = []
208
+ for file_path in user_files[:limit]: # 需要数量
209
  try:
210
  # 下载文件
211
  local_path = self.api.hf_hub_download(
 
283
  temp_file = f.name
284
 
285
  try:
286
+ # 按用户分组存储方便查询单个用户的历史记录
287
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
288
+ filename = f"evaluations/{username}/{timestamp}.jsonl"
289
 
290
  # 上传到 Dataset
291
  self.api.upload_file(