Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion api/chat2api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import asyncio
import datetime
import json
import types

from apscheduler.schedulers.asyncio import AsyncIOScheduler
Expand Down Expand Up @@ -46,6 +48,17 @@ async def process(request_data, req_token):
chat_service = await to_send_conversation(request_data, req_token)
await chat_service.prepare_send_conversation()
res = await chat_service.send_conversation()

# 添加对话统计
today = datetime.now().strftime("%Y-%m-%d")
if today not in globals.conversation_stats:
globals.conversation_stats[today] = 0
globals.conversation_stats[today] += 1

# 保存统计数据
with open(globals.CONVERSATION_STATS_FILE, "w", encoding="utf-8") as f:
json.dump(globals.conversation_stats, f, ensure_ascii=False, indent=2)

return chat_service, res


Expand Down Expand Up @@ -133,4 +146,66 @@ async def clear_seed_tokens():
with open(globals.CONVERSATION_MAP_FILE, "w", encoding="utf-8") as f:
f.write("{}")
logger.info(f"Seed token count: {len(globals.seed_map)}")
return {"status": "success", "seed_tokens_count": len(globals.seed_map)}
return {"status": "success", "seed_tokens_count": len(globals.seed_map)}


@app.get(f"/{api_prefix}/cishu" if api_prefix else "/cishu")
async def cishu(start_date: str = None, end_date: str = None):
"""
获取对话统计信息,支持日期范围过滤
:param start_date: 开始日期,格式:YYYY-MM-DD
:param end_date: 结束日期,格式:YYYY-MM-DD
:return: 过滤后的对话统计信息
"""
try:
# 从全局变量读取统计数据,如果文件不存在则初始化
if not hasattr(globals, 'conversation_stats'):
globals.conversation_stats = {}

# 如果统计文件存在,则读取文件
try:
with open(globals.CONVERSATION_STATS_FILE, "r", encoding="utf-8") as f:
globals.conversation_stats = json.load(f)
except FileNotFoundError:
# 如果文件不存在,创建空文件
with open(globals.CONVERSATION_STATS_FILE, "w", encoding="utf-8") as f:
json.dump({}, f, ensure_ascii=False, indent=2)

# 日期过滤
filtered_stats = {}
for date_str, count in globals.conversation_stats.items():
try:
current_date = datetime.strptime(date_str, "%Y-%m-%d")

# 检查是否在日期范围内
if start_date:
start = datetime.strptime(start_date, "%Y-%m-%d")
if current_date < start:
continue

if end_date:
end = datetime.strptime(end_date, "%Y-%m-%d")
if current_date > end:
continue

filtered_stats[date_str] = count

except ValueError as e:
logger.error(f"日期格式错误 {date_str}: {str(e)}")
continue

# 计算总对话次数
total_conversations = sum(filtered_stats.values())

return {
"status": "success",
"total": total_conversations,
"conversation_stats": filtered_stats
}

except Exception as e:
logger.error(f"获取对话统计信息时出错: {str(e)}")
raise HTTPException(
status_code=500,
detail={"status": "error", "message": "获取对话统计信息时出错"}
)
82 changes: 68 additions & 14 deletions templates/tokens.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<title>Tokens 管理</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('DOMContentLoaded', async () => {
const apiPrefix = "{{ api_prefix }}";
const uploadForm = document.getElementById('uploadForm');
const clearForm = document.getElementById('clearForm');
Expand Down Expand Up @@ -49,23 +49,77 @@
alert('复制失败,请手动复制');
});
});

async function fetchConversationStats() {
const today = new Date();
const lastMonth = new Date(today);
lastMonth.setMonth(today.getMonth() - 1);

const startDate = lastMonth.toISOString().split('T')[0];
const endDate = today.toISOString().split('T')[0];

const statsUrl = apiPrefix === "None"
? `/cishu?start_date=${startDate}&end_date=${endDate}`
: `/${apiPrefix}/cishu?start_date=${startDate}&end_date=${endDate}`;

try {
const response = await fetch(statsUrl);
const data = await response.json();

const statsContainer = document.getElementById('statsContent');
if (data.status === 'success') {
let statsHtml = `<div class="text-xl font-bold mb-2">总对话量:${data.total}</div>`;
statsHtml += '<div class="grid grid-cols-1 gap-2">';

const sortedDates = Object.entries(data.conversation_stats)
.sort(([dateA], [dateB]) => dateB.localeCompare(dateA));

for (const [date, count] of sortedDates) {
statsHtml += `
<div class="flex justify-between items-center p-2 bg-gray-50 rounded">
<span class="text-gray-700">${date}</span>
<span class="text-blue-600 font-semibold">${count}</span>
</div>
`;
}
statsHtml += '</div>';
statsContainer.innerHTML = statsHtml;
}
} catch (error) {
console.error('获取统计数据失败:', error);
}
}

fetchConversationStats();
});
</script>
</head>
<body class="bg-gradient-to-r from-blue-200 via-purple-200 to-pink-200 flex justify-center items-center min-h-screen">
<div class="bg-white p-10 rounded-lg shadow-2xl w-128 text-center">
<h1 class="text-4xl font-extrabold text-gray-900 mb-6">Tokens 管理</h1>
<p class="text-gray-600 mb-4">当前可用 Tokens 数量:<span class="text-blue-600">{{ tokens_count }}</span></p>
<form class="mb-2" id="uploadForm" method="post">
<textarea class="w-full p-4 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400 resize-none" name="text" placeholder="一行一个Token,可以是 AccessToken 或 RefreshToken" rows="10"></textarea>
<p class="text-gray-600 mb-2">注:使用docker时如果挂载了data文件夹则重启后不需要再次上传</p>
<button class="w-full bg-blue-600 text-white py-3 rounded-md hover:bg-blue-700 transition duration-300 mb-2" type="submit">上传</button>
</form>
<button id="errorButton" class="w-full bg-yellow-600 text-white py-3 rounded-md hover:bg-yellow-700 transition duration-200 mt-2">查看错误Tokens</button>
<p class="text-gray-600 mt-2">点击清空,将会清空上传和错误的 Tokens</p>
<form id="clearForm" method="post">
<button class="w-full bg-red-600 text-white py-3 rounded-md hover:bg-red-700 transition duration-300" type="submit">清空Tokens</button>
</form>
<div class="flex flex-col md:flex-row gap-6 p-6 w-full max-w-6xl">
<div class="bg-white p-10 rounded-lg shadow-2xl md:w-1/2">
<h1 class="text-4xl font-extrabold text-gray-900 mb-6">Tokens 管理</h1>
<p class="text-gray-600 mb-4">当前可用 Tokens 数量:<span class="text-blue-600">{{ tokens_count }}</span></p>
<form class="mb-2" id="uploadForm" method="post">
<textarea class="w-full p-4 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400 resize-none" name="text" placeholder="一行一个Token,可以是 AccessToken 或 RefreshToken" rows="10"></textarea>
<p class="text-gray-600 mb-2">注:使用docker时如果挂载了data文件夹则重启后不需要再次上传</p>
<button class="w-full bg-blue-600 text-white py-3 rounded-md hover:bg-blue-700 transition duration-300 mb-2" type="submit">上传</button>
</form>
<button id="errorButton" class="w-full bg-yellow-600 text-white py-3 rounded-md hover:bg-yellow-700 transition duration-200 mt-2">查看错误Tokens</button>
<p class="text-gray-600 mt-2">点击清空,将会清空上传和错误的 Tokens</p>
<form id="clearForm" method="post">
<button class="w-full bg-red-600 text-white py-3 rounded-md hover:bg-red-700 transition duration-300" type="submit">清空Tokens</button>
</form>
</div>

<div class="bg-white p-10 rounded-lg shadow-2xl md:w-1/2">
<h1 class="text-4xl font-extrabold text-gray-900 mb-6">对话统计</h1>
<div class="text-gray-600 mb-4">最近一个月的对话统计数据</div>
<div id="statsContent" class="overflow-y-auto max-h-[500px]">
<div class="flex justify-center items-center h-32">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
</div>
</div>
</div>

<div id="errorModal" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex justify-center items-center hidden">
Expand Down
12 changes: 12 additions & 0 deletions utils/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
FP_FILE = os.path.join(DATA_FOLDER, "fp_map.json")
SEED_MAP_FILE = os.path.join(DATA_FOLDER, "seed_map.json")
CONVERSATION_MAP_FILE = os.path.join(DATA_FOLDER, "conversation_map.json")
CONVERSATION_STATS_FILE = os.path.join(DATA_FOLDER, "conversation_stats.txt")

count = 0
token_list = []
Expand All @@ -21,6 +22,7 @@
fp_map = {}
seed_map = {}
conversation_map = {}
conversation_stats = {}
impersonate_list = [
"chrome99",
"chrome100",
Expand Down Expand Up @@ -84,6 +86,16 @@
else:
conversation_map = {}

if os.path.exists(CONVERSATION_STATS_FILE):
with open(CONVERSATION_STATS_FILE, "r", encoding="utf-8") as f:
try:
conversation_stats = json.load(f)
except:
conversation_stats = {}
else:
with open(CONVERSATION_STATS_FILE, "w", encoding="utf-8") as f:
f.write("{}")

if os.path.exists(TOKENS_FILE):
with open(TOKENS_FILE, "r", encoding="utf-8") as f:
for line in f:
Expand Down