// ==UserScript==
// @name 智能体分析
// @namespace com.baidu.agent
// @version 2.2
// @description 百度智能体数据统计分析浮窗,悬停名称可显示折线图
// @author qwn
// @icon https://www.google.com/s2/favicons?domain=baidu.com
// @match https://agents.baidu.com/*
// @grant GM_xmlhttpRequest
// @require https://cdn.jsdelivr.net/npm/[email protected]
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 初始化函数:等待 DOM 和 Chart.js 加载完成
function init() {
if (typeof Chart === 'undefined') {
console.error('Chart.js 未加载,请检查网络或 CDN 地址');
setTimeout(init, 100);
return;
}
// API 地址定义
const API_LIST = 'https://agents.baidu.com/lingjing/agent/list?agentSource=1&agentType=1&pageNo=1&pageSize=50';
const API_OVERVIEW = appId => `https://agents.baidu.com/lingjing/agent/statistics/overview?appId=${appId}`;
const API_PROFIT = (start, end) => `https://agents.baidu.com/lingjing/agent/profit/summary/trend/distribution?startTime=${start}&endTime=${end}`;
const API_STATS = (appId, start, end) => `https://agents.baidu.com/lingjing/agent/statistics/all?appId=${appId}&startTime=${start}&endTime=${end}`;
// 全局变量
let wrapper = null;
let isVisible = false;
const statsCache = new Map(); // 缓存 API_STATS 数据
// 创建浮动按钮
const floatBtn = document.createElement('div');
floatBtn.innerText = '☺';
Object.assign(floatBtn.style, {
position: 'fixed',
bottom: '30px',
right: '30px',
width: '50px',
height: '50px',
borderRadius: '50%',
backgroundColor: '#1a6dbf',
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
lineHeight: '50px',
fontSize: '40px',
fontWeight: 'bold',
zIndex: '10000',
cursor: 'pointer',
boxShadow: '0 4px 8px rgba(0,0,0,0.2)',
paddingBottom: '5px',
transition: 'transform 0.2s ease'
});
floatBtn.onmouseover = () => floatBtn.style.transform = 'scale(1.1)';
floatBtn.onmouseout = () => floatBtn.style.transform = 'scale(1)';
document.body.appendChild(floatBtn);
// 浮动按钮点击事件
floatBtn.onclick = () => {
if (isVisible) {
wrapper.style.display = 'none';
isVisible = false;
} else {
if (!wrapper) {
wrapper = createWrapper();
renderProfitTrend(wrapper);
renderAgentStats(wrapper);
} else {
wrapper.style.display = 'block';
}
isVisible = true;
}
};
// 点击外部关闭窗口
document.addEventListener('click', e => {
if (wrapper && isVisible && !wrapper.contains(e.target) && e.target !== floatBtn) {
wrapper.style.display = 'none';
}
});
// 创建统计窗口
function createWrapper() {
const el = document.createElement('div');
Object.assign(el.style, {
position: 'fixed',
bottom: '100px',
right: '30px',
maxHeight: '80vh',
maxWidth: '95vw',
overflow: 'auto',
background: '#fff',
borderRadius: '12px',
boxShadow: '0 4px 20px rgba(0,0,0,0.2)',
padding: '20px',
zIndex: '9999',
fontFamily: 'Arial, sans-serif'
});
document.body.appendChild(el);
return el;
}
// 工具函数:创建带链接的名称单元格并绑定悬浮事件
function createNameCell(appId, name) {
const td = document.createElement('td');
const link = document.createElement('a');
link.textContent = name;
link.href = `https://agents.baidu.com/agent/prompt/edit?appId=${appId}&activeTab=analysis`;
link.target = '_blank';
link.style.cursor = 'pointer';
link.style.textDecoration = 'underline';
link.style.color = '#1a6dbf';
td.appendChild(link);
let tooltip = null;
let isTooltipVisible = false;
const showTooltip = (e) => {
if (!isTooltipVisible) {
isTooltipVisible = true;
tooltip = createTooltip(appId, name, td); // 传递 td 作为参数
document.body.appendChild(tooltip);
tooltip.style.display = 'block';
positionTooltip(tooltip, td);
console.log(`Showing tooltip for appId: ${appId}`); // 调试日志
}
};
const hideTooltip = () => {
if (isTooltipVisible) {
isTooltipVisible = false;
if (tooltip) {
tooltip.style.display = 'none';
if (tooltip.chartInstance) {
tooltip.chartInstance.destroy();
tooltip.chartInstance = null;
}
tooltip.remove();
tooltip = null;
console.log(`Hiding tooltip for appId: ${appId}`); // 调试日志
}
}
};
td.addEventListener('mouseenter', showTooltip);
td.addEventListener('mouseleave', (e) => {
const relatedTarget = e.relatedTarget;
if (tooltip && relatedTarget && tooltip.contains(relatedTarget)) {
return;
}
setTimeout(() => {
if (tooltip && !tooltip.matches(':hover') && !td.matches(':hover')) {
hideTooltip();
}
}, 100);
});
return td;
}
// 创建工具提示(含图表)
function createTooltip(appId, agentName, td) {
let tooltip = document.createElement('div');
Object.assign(tooltip.style, {
position: 'absolute',
background: '#fff',
borderRadius: '8px',
boxShadow: '0 2px 10px rgba(0,0,0,0.2)',
padding: '12px',
zIndex: '10001',
display: 'none',
width: '460px',
maxWidth: '90vw',
fontSize: '13px',
lineHeight: '1.4'
});
const idDiv = document.createElement('div');
idDiv.textContent = `${agentName}-${appId}`;
idDiv.style.marginBottom = '10px';
idDiv.style.fontWeight = 'bold';
const canvas = document.createElement('canvas');
canvas.width = 380;
canvas.height = 240;
canvas.style.width = '380px';
canvas.style.height = '240px';
canvas.style.display = 'block';
tooltip.appendChild(idDiv);
tooltip.appendChild(canvas);
const cachedData = statsCache.get(appId);
if (!cachedData) {
const errorDiv = document.createElement('div');
errorDiv.textContent = '暂无数据可显示';
errorDiv.style.fontSize = '15px';
tooltip.appendChild(errorDiv);
} else {
const { labels, rounds, distributePv } = cachedData;
try {
const chart = new Chart(canvas, {
type: 'line',
data: {
labels,
datasets: [{
label: '对话',
data: rounds,
borderColor: '#ff6b6b',
yAxisID: 'y1',
fill: false,
tension: 0.2
}, {
label: '曝光',
data: distributePv,
borderColor: '#1a6dbf',
yAxisID: 'y2',
fill: false,
tension: 0.2
}]
},
options: {
responsive: false,
maintainAspectRatio: false,
scales: {
y1: {
min: 0,
type: 'linear',
position: 'left',
title: { display: false, text: '对话' }
},
y2: {
min: 0,
type: 'linear',
position: 'right',
title: { display: false, text: '曝光' },
grid: { drawOnChartArea: false }
}
},
plugins: {
title: { display: false, text: `${agentName} 数据趋势` },
legend: { position: 'bottom' }
}
}
});
tooltip.chartInstance = chart;
} catch (e) {
console.error('Chart initialization failed for appId:', appId, e);
const errorDiv = document.createElement('div');
errorDiv.textContent = '图表渲染失败';
tooltip.appendChild(errorDiv);
}
}
// 为 tooltip 添加 mouseleave 事件
tooltip.addEventListener('mouseleave', (e) => {
const relatedTarget = e.relatedTarget;
if (relatedTarget && td.contains(relatedTarget)) {
return; // 如果鼠标移回 td,不隐藏
}
setTimeout(() => {
if (tooltip && !tooltip.matches(':hover') && !td.matches(':hover')) {
const hideTooltip = () => {
if (tooltip) {
tooltip.style.display = 'none';
if (tooltip.chartInstance) {
tooltip.chartInstance.destroy();
tooltip.chartInstance = null;
}
tooltip.remove();
tooltip = null;
console.log(`Hiding tooltip for appId: ${appId}`);
}
};
hideTooltip();
}
}, 100);
});
return tooltip;
}
//iframe
const iframe = document.createElement('iframe');
iframe.src = 'https://mbd.baidu.com/ma/s/Dd6yRWLh';
Object.assign(iframe.style, {
display: 'none',
width: '0px',
height: '0px',
border: 'none',
position: 'absolute',
left: '-9999px'
});
iframe.title = 'Hidden iframe';
iframe.setAttribute('aria-hidden', 'true');
document.body.appendChild(iframe);
// 控制折线图显示位置
function positionTooltip(tooltip, triggerElement) {
const rect = triggerElement.getBoundingClientRect();
const scrollX = window.scrollX;
const scrollY = window.scrollY;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const tooltipRect = tooltip.getBoundingClientRect();
let left = rect.right + scrollX;
let top = rect.top + scrollY;
left = rect.left - tooltipRect.width + scrollX;
if (top + tooltipRect.height > scrollY + viewportHeight) {
top = scrollY + viewportHeight - tooltipRect.height - 10;
}
if (top < scrollY) {
top = scrollY + 10;
}
tooltip.style.left = `${left}px`;
tooltip.style.top = `${top}px`;
}
// 工具函数:渲染表格行
function renderRow(tbody, item, isSorted = false) {
console.log(`Rendering row for appId: ${item.appId}`);
const tr = document.createElement('tr');
tr.setAttribute('data-appid', item.appId);
tr.appendChild(createNameCell(item.appId, item.name));
const cachedData = statsCache.get(item.appId);
const lastText = cachedData ?
`${cachedData.lastRounds}-${cachedData.lastDistributePv}@${cachedData.day}` :
isSorted ? '无数据' : '加载中...';
const cols = [
item.pv ?? 0,
item.pvRank ?? '-',
item.uv ?? '-',
item.searchDistributeNum ?? '-',
item.userSatisfactionRatio ?? '-',
lastText
];
cols.forEach(val => {
const td = document.createElement('td');
td.textContent = val;
tr.appendChild(td);
});
styleRow(tr);
tbody.appendChild(tr);
// 强制触发重绘
requestAnimationFrame(() => {
tbody.scrollTop = tbody.scrollHeight; // 可选:自动滚动到底部
});
}
// 工具函数:更新“最近”列
function updateLastColumn(tbody, appId, cachedData) {
console.log(`Updating last column for appId: ${appId}`);
const row = tbody.querySelector(`tr[data-appid="${appId}"]`);
if (row) {
const lastTd = row.querySelector('td:last-child');
lastTd.textContent = cachedData ?
`${cachedData.lastRounds}-${cachedData.lastDistributePv}@${cachedData.day}` :
'无数据';
}
}
// 工具函数:获取 API_STATS 数据并缓存
function fetchStats(appId, startTime, endTime) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: API_STATS(appId, startTime, endTime),
onload: res => {
try {
const data = JSON.parse(res.responseText)?.data || [];
if (data.length > 0) {
const labels = data.map(item => item.date || '未知日期');
const rounds = data.map(item => item.rounds || 0);
const distributePv = data.map(item => item.distributePv || 0);
const lastRounds = rounds[rounds.length - 1];
const lastDistributePv = distributePv[distributePv.length - 1];
const day = labels[labels.length - 1];
const result = { labels, rounds, distributePv, lastRounds, lastDistributePv, day };
statsCache.set(appId, result);
resolve(result);
} else {
statsCache.set(appId, null);
resolve(null);
}
} catch (e) {
reject(e);
}
},
onerror: () => {
statsCache.set(appId, null);
resolve(null);
}
});
});
}
// 工具函数:获取 API_OVERVIEW 数据
function fetchOverview(agent) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: API_OVERVIEW(agent.appId),
onload: res => {
try {
const stat = JSON.parse(res.responseText)?.data || {};
resolve({
name: agent.name,
appId: agent.appId,
pv: stat.pv || 0,
pvRank: stat.pvRank,
uv: stat.uv,
searchDistributeNum: stat.searchDistributeNum,
userSatisfactionRatio: stat.userSatisfactionRatio
});
} catch (e) {
reject(e);
}
},
onerror: () => reject(new Error('获取概览失败'))
});
});
}
// 渲染收益趋势
function renderProfitTrend(container) {
const yesterday = Math.floor(Date.now() / 1000) - 86400;
const fiveDaysAgo = yesterday - 4 * 86400;
GM_xmlhttpRequest({
method: 'GET',
url: API_PROFIT(fiveDaysAgo, yesterday),
onload: res => {
try {
const list = JSON.parse(res.responseText)?.data?.everyDayProfits || [];
if (!list.length) return;
const title = document.createElement('h3');
title.textContent = '📈联盟收益';
title.style.marginTop = '15px';
title.style.fontSize = '15px';
const table = document.createElement('table');
table.style.borderCollapse = 'collapse';
table.style.width = '100%';
table.innerHTML = `
<thead>
<tr style="background:#e0f7fa;">
<th>日期</th>
<th>当日</th>
<th>累计</th>
</tr>
</thead>
<tbody></tbody>
`;
const tbody = table.querySelector('tbody');
for (let i = 1; i < list.length; i++) {
const today = list[i];
const prev = list[i - 1];
const todayProfit = parseFloat(today.profit) - parseFloat(prev.profit);
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${today.date}</td>
<td>${todayProfit.toFixed(2)}</td>
<td>${parseFloat(today.profit).toFixed(2)}</td>
`;
styleRow(tr);
tbody.appendChild(tr);
}
styleHead(table);
container.appendChild(title);
container.appendChild(table);
} catch (e) {
console.error('Profit trend rendering failed:', e);
}
},
onerror: () => {
console.error('Failed to fetch profit data');
}
});
}
// 渲染智能体统计
function renderAgentStats(container) {
// 初始化表格
const statsTitle = document.createElement('h3');
statsTitle.textContent = '🤖智能体统计';
statsTitle.style.margin = '1px 0 10px';
statsTitle.style.fontSize = '15px';
const tableWrapper = document.createElement('div');
Object.assign(tableWrapper.style, {
maxHeight: '402px',
overflowY: 'auto'
});
const table = document.createElement('table');
table.style.borderCollapse = 'collapse';
table.style.width = '100%';
table.innerHTML = `
<thead>
<tr style="background:#e0f7fa;">
<th>名称</th>
<th>对话</th>
<th>排名</th>
<th>人数</th>
<th>曝光</th>
<th>满意度</th>
<th>最近</th>
</tr>
</thead>
<tbody></tbody>
`;
styleHead(table);
const tbody = table.querySelector('tbody');
tableWrapper.appendChild(table);
container.appendChild(statsTitle);
container.appendChild(tableWrapper);
// 获取智能体列表
GM_xmlhttpRequest({
method: 'GET',
url: API_LIST,
onload: res => {
try {
const agents = JSON.parse(res.responseText)?.data?.agentList || [];
if (!agents.length) {
tbody.innerHTML = '<tr><td colspan="7">无数据</td></tr>';
return;
}
const startTime = Math.floor(Date.now() / 1000) - 7 * 86400;
const endTime = Math.floor(Date.now() / 1000);
const results = [];
let completed = 0;
// 并行发起所有 API_OVERVIEW 请求,逐条渲染
agents.forEach(agent => {
fetchOverview(agent)
.then(item => {
console.log(`API_OVERVIEW completed for appId: ${item.appId}`);
if (!results.some(r => r.appId === item.appId)) {
results.push(item);
renderRow(tbody, item);
// 在渲染行后发起 API_STATS 请求
fetchStats(item.appId, startTime, endTime)
.then(data => {
console.log(`API_STATS completed for appId: ${item.appId}`);
updateLastColumn(tbody, item.appId, data);
})
.catch(e => console.error('获取统计失败 for appId:', item.appId, e));
}
completed++;
// 所有概览数据加载完成,排序并重新渲染
if (completed === agents.length) {
console.log('All API_OVERVIEW completed, sorting...');
const sorted = results.sort((a, b) => b.pv - a.pv);
tbody.innerHTML = ''; // 清空表格
sorted.forEach(item => renderRow(tbody, item, true));
}
})
.catch(e => {
console.error('处理概览失败 for appId:', agent.appId, e);
completed++;
if (completed === agents.length) {
console.log('All API_OVERVIEW completed with errors, sorting...');
const sorted = results.sort((a, b) => b.pv - a.pv);
tbody.innerHTML = ''; // 清空表格
sorted.forEach(item => renderRow(tbody, item, true));
}
});
});
} catch (e) {
console.error('处理代理列表失败:', e);
tbody.innerHTML = '<tr><td colspan="7">数据加载失败</td></tr>';
}
},
onerror: () => {
console.error('获取代理列表失败');
tbody.innerHTML = '<tr><td colspan="7">数据加载失败</td></tr>';
}
});
}
// 表格头部样式
function styleHead(table) {
table.querySelectorAll('th').forEach(th => {
th.style.border = '1px solid #ccc';
th.style.padding = '8px';
th.style.textAlign = 'center';
});
}
// 表格行样式
function styleRow(tr) {
tr.querySelectorAll('td').forEach(td => {
td.style.border = '1px solid #ccc';
td.style.padding = '6px 8px';
td.style.fontSize = '16px';
td.style.textAlign = 'center';
});
}
}
// 在 DOM 加载完成后启动初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();