fix: 修复前台4项问题(MCP列举/微信技能上限/Word资产栏/技能MCP反馈) + 同步容器live状态
- #549 平台MCP列举: 注入全量已启用MCP清单, AI不再凭空编造 - #550 微信Bot技能自动发现上限 15->300 - #551 知识库Word文档同步资产后Word栏可见 + 双向删除同步(assets/knowledge级联软删) - #552 对话反馈实际使用的技能/MCP(used_mcp字段), 含历史消息序列化 - 同步容器内其他未提交live改动(im_bot_platform/TokenDashboard/WechatBot/ChatPage)
This commit is contained in:
1 parent
7fb7c07f57
commit
1578d68f95
10 files changed
+1927
-1719
No files matched your search
@@ -54,6 +54,7 @@ function mapFileTypeToLabel(ft: string): string {
|
||||
const mapping: Record<string, string> = {
|
||||
pdf: 'pdf', docx: 'word', doc: 'word', xlsx: 'excel', xls: 'excel',
|
||||
pptx: 'ppt', ppt: 'ppt', csv: 'txt', md: 'md', txt: 'txt',
|
||||
word: 'word', excel: 'excel',
|
||||
};
|
||||
return mapping[ft] || 'other';
|
||||
}
|
||||
@@ -399,7 +400,7 @@ const AssetsPage: React.FC = () => {
|
||||
const VALID_CATEGORIES = ['file', 'novel', 'image', 'video', 'audio', 'custom', 'other'];
|
||||
const normalizeAsset = (item: any): Asset => {
|
||||
const rawCat = item.category;
|
||||
const ft = item.fileType || item.file_type || 'other';
|
||||
const ft = mapFileTypeToLabel(item.fileType || item.file_type || 'other');
|
||||
let category = rawCat;
|
||||
if (!VALID_CATEGORIES.includes(category)) {
|
||||
category = (ft && ft !== 'other') ? 'file' : 'other';
|
||||
|
||||
+1719
-1695
File diff suppressed because it is too large.
Load diff
@@ -160,6 +160,37 @@ function getMediaStats(logs: TokenUsageLog[]) {
|
||||
return { imageCount, videoCount, imageTokens, videoTokens, models };
|
||||
}
|
||||
|
||||
// 分页拉取全部 token 日志(后端 /logs 默认只返回 20 条,会导致前端上下翻页无法查看更早记录)。
|
||||
// 通过 page_size=100 循环拉取,直到不足一页为止。
|
||||
async function fetchAllTokenLogs(): Promise<TokenUsageLog[]> {
|
||||
const all: any[] = [];
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
while (hasMore) {
|
||||
const res = await tokenAPI.getLogs({ page_size: 100, page });
|
||||
const rawLogs = (res.data as any)?.items || res.data || [];
|
||||
if (!Array.isArray(rawLogs) || rawLogs.length === 0) {
|
||||
hasMore = false;
|
||||
break;
|
||||
}
|
||||
all.push(
|
||||
...rawLogs.map((item: any) => ({
|
||||
...item,
|
||||
promptTokens: item.promptTokens ?? item.prompt_tokens ?? 0,
|
||||
completionTokens: item.completionTokens ?? item.completion_tokens ?? 0,
|
||||
totalTokens: item.totalTokens ?? item.total_tokens ?? 0,
|
||||
modelName: item.modelName ?? item.model_name ?? '',
|
||||
cost: item.cost ?? 0,
|
||||
createdAt: item.createdAt ?? item.created_at ?? item.createdAt,
|
||||
appName: item.appName ?? item.app_name ?? '',
|
||||
}))
|
||||
);
|
||||
if (rawLogs.length < 100) hasMore = false;
|
||||
else page += 1;
|
||||
}
|
||||
return all as TokenUsageLog[];
|
||||
}
|
||||
|
||||
const TokenDashboardPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [totalConsumed, setTotalConsumed] = useState(0);
|
||||
@@ -178,28 +209,18 @@ const TokenDashboardPage: React.FC = () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const [overviewRes, balanceRes, logsRes] = await Promise.all([
|
||||
const [overviewRes, balanceRes, allLogs] = await Promise.all([
|
||||
tokenAPI.getOverview(),
|
||||
tokenAPI.getBalance(),
|
||||
tokenAPI.getLogs(),
|
||||
fetchAllTokenLogs(),
|
||||
]);
|
||||
setTotalConsumed(overviewRes.data?.total_used || overviewRes.data?.total_consumed || overviewRes.data?.totalConsumed || 0);
|
||||
setBalance(balanceRes.data?.balance || 0);
|
||||
// 从overview响应中获取服务器时间
|
||||
const serverTimeStr = overviewRes.data?.server_time || overviewRes.data?.serverTime || overviewRes.data?.current_time || overviewRes.data?.time || '';
|
||||
setServerTime(serverTimeStr);
|
||||
// 兼容分页格式和直接数组两种返回格式
|
||||
const rawLogs = (logsRes.data as any)?.items || logsRes.data || [];
|
||||
// 兼容后端返回的 snake_case 字段名
|
||||
setLogs(rawLogs.map((item: any) => ({
|
||||
...item,
|
||||
promptTokens: item.promptTokens ?? item.prompt_tokens ?? 0,
|
||||
completionTokens: item.completionTokens ?? item.completion_tokens ?? 0,
|
||||
totalTokens: item.totalTokens ?? item.total_tokens ?? 0,
|
||||
modelName: item.modelName ?? item.model_name ?? '',
|
||||
cost: item.cost ?? 0,
|
||||
createdAt: item.createdAt ?? item.created_at ?? item.createdAt,
|
||||
})));
|
||||
// 已拉取全部日志(fetchAllTokenLogs 内部循环分页),客户端可自由上下翻页
|
||||
setLogs(allLogs);
|
||||
} catch (err: any) {
|
||||
setError(err.message || '数据加载失败');
|
||||
} finally {
|
||||
|
||||
@@ -93,7 +93,16 @@ const WechatBot: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const formatTime = (t: string) => (t ? new Date(t).toLocaleString('zh-CN') : '-');
|
||||
// 后端按北京时间(Asia/Shanghai)存储连接时间(无时区标记),前端统一按北京时间展示,
|
||||
// 不受浏览器所在时区影响。
|
||||
const formatTime = (t: string) => {
|
||||
if (!t) return '-';
|
||||
const iso = t.includes('T') ? t : t.replace(' ', 'T');
|
||||
const withTz = iso.includes('+') || iso.includes('Z') ? iso : iso + '+08:00';
|
||||
const d = new Date(withTz);
|
||||
if (isNaN(d.getTime())) return t;
|
||||
return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false });
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ minHeight: '100%', background: '#FFFFFF', color: '#1A1F36', padding: '32px' }}>
|
||||
|
||||
Reference in new issue
Block a user