528 lines
20 KiB
JavaScript
528 lines
20 KiB
JavaScript
class PathVisualizer {
|
||
constructor() {
|
||
this.currentData = null;
|
||
this.currentIndex = 0;
|
||
this.allData = {};
|
||
this.urls = {}; // 改为对象,每个长度存储对应的URLs
|
||
this.currentLength = 1; // 当前选择的路径长度
|
||
this.currentScale = 1;
|
||
this.translateX = 0;
|
||
this.translateY = 0;
|
||
this.isDragging = false;
|
||
this.currentModalBox = null;
|
||
this.analysisData = {}; // 存储分析数据
|
||
|
||
this.initializeEventListeners();
|
||
this.initializeModal();
|
||
this.loadData();
|
||
}
|
||
|
||
async loadData() {
|
||
try {
|
||
// 加载当前长度的数据
|
||
const response = await fetch(`path/processed_${this.currentLength}.json`);
|
||
const data = await response.json();
|
||
this.allData = data;
|
||
this.urls = Object.keys(this.allData);
|
||
|
||
// 加载分析数据
|
||
try {
|
||
const analysisResponse = await fetch(`path/processed_${this.currentLength}_with_analysis.json`);
|
||
this.analysisData = await analysisResponse.json();
|
||
} catch (error) {
|
||
console.error('加载分析数据失败:', error);
|
||
this.analysisData = {};
|
||
}
|
||
|
||
// 更新URL总数显示
|
||
this.updateUrlCount();
|
||
|
||
this.showPath(0);
|
||
} catch (error) {
|
||
console.error('加载数据失败:', error);
|
||
}
|
||
}
|
||
|
||
updateUrlCount() {
|
||
// 创建或更新URL计数显示
|
||
let urlCountElement = document.getElementById('urlCount');
|
||
if (!urlCountElement) {
|
||
urlCountElement = document.createElement('div');
|
||
urlCountElement.id = 'urlCount';
|
||
urlCountElement.className = 'url-count';
|
||
|
||
// 获取长度选择器的父元素,并在其后插入计数元素
|
||
const lengthSelector = document.getElementById('pathLength');
|
||
lengthSelector.parentNode.appendChild(urlCountElement);
|
||
}
|
||
|
||
// 更新计数显示
|
||
urlCountElement.textContent = `页面数: ${this.urls.length}`;
|
||
}
|
||
|
||
initializeEventListeners() {
|
||
document.getElementById('prevBtn').addEventListener('click', () => this.showPrevious());
|
||
document.getElementById('nextBtn').addEventListener('click', () => this.showNext());
|
||
document.getElementById('urlSearch').addEventListener('input', (e) => this.handleSearch(e));
|
||
|
||
// 添加随机URL按钮的事件监听
|
||
document.getElementById('randomUrl').addEventListener('click', () => this.showRandomUrl());
|
||
|
||
// 添加路径长度选择事件
|
||
document.getElementById('pathLength').addEventListener('change', (e) => {
|
||
this.currentLength = parseInt(e.target.value);
|
||
this.currentIndex = 0; // 重置索引
|
||
this.loadData(); // 重新加载数据
|
||
});
|
||
}
|
||
|
||
initializeModal() {
|
||
const modal = document.getElementById('imageModal');
|
||
const modalImg = document.getElementById('modalImage');
|
||
const zoomIn = document.getElementById('zoomIn');
|
||
const zoomOut = document.getElementById('zoomOut');
|
||
const zoomReset = document.getElementById('zoomReset');
|
||
const closeModalBtn = document.getElementById('closeModal');
|
||
|
||
// 关闭模态框
|
||
const closeModal = () => {
|
||
modal.style.display = 'none';
|
||
this.currentScale = 1;
|
||
this.translateX = 0;
|
||
this.translateY = 0;
|
||
this.updateModalImageScale();
|
||
};
|
||
|
||
closeModalBtn.onclick = closeModal;
|
||
|
||
// 点击空白处关闭
|
||
modal.onclick = (event) => {
|
||
if (event.target === modal) {
|
||
closeModal();
|
||
}
|
||
};
|
||
|
||
// 鼠标拖拽
|
||
modalImg.onmousedown = (e) => {
|
||
this.isDragging = true;
|
||
this.startX = e.clientX - this.translateX;
|
||
this.startY = e.clientY - this.translateY;
|
||
e.preventDefault();
|
||
};
|
||
|
||
document.onmousemove = (e) => {
|
||
if (!this.isDragging) return;
|
||
this.translateX = e.clientX - this.startX;
|
||
this.translateY = e.clientY - this.startY;
|
||
this.updateModalImageScale();
|
||
};
|
||
|
||
document.onmouseup = () => {
|
||
this.isDragging = false;
|
||
};
|
||
|
||
// 优化滚轮缩放,以鼠标位置为中心,降低灵敏度
|
||
modal.addEventListener('wheel', (e) => {
|
||
e.preventDefault();
|
||
|
||
// 获取鼠标相对于图片的位置
|
||
const rect = modalImg.getBoundingClientRect();
|
||
const mouseX = e.clientX - rect.left;
|
||
const mouseY = e.clientY - rect.top;
|
||
|
||
// 计算鼠标在图片上的相对位置(考虑当前的变换)
|
||
const x = (mouseX - this.translateX) / this.currentScale;
|
||
const y = (mouseY - this.translateY) / this.currentScale;
|
||
|
||
// 计算缩放比例,降低灵敏度
|
||
const delta = e.deltaY > 0 ? 0.95 : 1.05; // 从0.9/1.1改为0.95/1.05
|
||
const newScale = this.currentScale * delta;
|
||
|
||
// 限制最大和最小缩放比例
|
||
const limitedScale = Math.min(Math.max(newScale, 0.1), 10); // 限制在0.1到10倍之间
|
||
|
||
// 只有当缩放比例在限制范围内才应用变换
|
||
if (limitedScale !== this.currentScale) {
|
||
// 计算新的平移值,保持鼠标位置不变
|
||
this.translateX = mouseX - x * limitedScale;
|
||
this.translateY = mouseY - y * limitedScale;
|
||
this.currentScale = limitedScale;
|
||
|
||
this.updateModalImageScale();
|
||
}
|
||
}, { passive: false });
|
||
|
||
// 按钮缩放
|
||
zoomIn.onclick = () => {
|
||
this.currentScale *= 1.2;
|
||
this.updateModalImageScale();
|
||
};
|
||
|
||
zoomOut.onclick = () => {
|
||
this.currentScale /= 1.2;
|
||
this.updateModalImageScale();
|
||
};
|
||
|
||
zoomReset.onclick = () => {
|
||
this.currentScale = 1;
|
||
this.translateX = 0;
|
||
this.translateY = 0;
|
||
this.updateModalImageScale();
|
||
};
|
||
}
|
||
|
||
handleSearch(event) {
|
||
const searchTerm = event.target.value.toLowerCase();
|
||
const foundIndex = this.urls.findIndex(url => url.toLowerCase().includes(searchTerm));
|
||
if (foundIndex !== -1) {
|
||
this.showPath(foundIndex);
|
||
}
|
||
}
|
||
|
||
showRandomUrl() {
|
||
if (this.urls && this.urls.length > 0) {
|
||
// 生成随机索引
|
||
const randomIndex = Math.floor(Math.random() * this.urls.length);
|
||
// 显示随机选中的路径
|
||
this.showPath(randomIndex);
|
||
}
|
||
}
|
||
|
||
showPath(index) {
|
||
if (index < 0 || index >= this.urls.length) return;
|
||
|
||
this.currentIndex = index;
|
||
const url = this.urls[index];
|
||
this.currentData = this.allData[url];
|
||
|
||
document.getElementById('currentUrl').textContent = `[长度: ${this.currentLength}] ${url}`;
|
||
this.renderPaths();
|
||
}
|
||
|
||
showPrevious() {
|
||
this.showPath(this.currentIndex - 1);
|
||
}
|
||
|
||
showNext() {
|
||
this.showPath(this.currentIndex + 1);
|
||
}
|
||
|
||
openModal(img) {
|
||
const modal = document.getElementById('imageModal');
|
||
const modalImg = document.getElementById('modalImage');
|
||
|
||
modal.style.display = 'block';
|
||
modalImg.src = img.src;
|
||
|
||
// 重置缩放和位置
|
||
this.currentScale = 1;
|
||
this.translateX = 0;
|
||
this.translateY = 0;
|
||
this.updateModalImageScale();
|
||
}
|
||
|
||
updateModalImageScale() {
|
||
const modalImg = document.getElementById('modalImage');
|
||
const modalBox = document.getElementById('modalBoundingBox');
|
||
|
||
// 应用缩放和平移
|
||
modalImg.style.transform = `translate(${this.translateX}px, ${this.translateY}px) scale(${this.currentScale})`;
|
||
modalImg.style.transformOrigin = '0 0'; // 设置变换原点为左上角
|
||
|
||
if (this.currentModalBox) {
|
||
modalBox.style.transform = `translate(${this.translateX}px, ${this.translateY}px) scale(${this.currentScale})`;
|
||
modalBox.style.transformOrigin = '0 0';
|
||
}
|
||
}
|
||
|
||
async renderPaths() {
|
||
const container = document.getElementById('pathsContainer');
|
||
container.innerHTML = '';
|
||
|
||
// 获取当前URL的分析数据
|
||
const currentUrl = this.urls[this.currentIndex];
|
||
const analysisForUrl = this.analysisData[currentUrl] || {};
|
||
|
||
// 添加任务描述区域 - 即使没有数据也显示空白框架
|
||
const taskContainer = document.createElement('div');
|
||
taskContainer.className = 'task-extract-container';
|
||
|
||
for (const meta of analysisForUrl.shortestPathsMeta[0].task_summaries) {
|
||
|
||
// 添加任务标题
|
||
const titleLabel = document.createElement('div');
|
||
titleLabel.className = 'text-label';
|
||
titleLabel.textContent = '提炼任务:';
|
||
taskContainer.appendChild(titleLabel);
|
||
|
||
const titleText = document.createElement('div');
|
||
titleText.className = 'task-description';
|
||
// 如果有数据则显示,否则留空
|
||
titleText.textContent = meta.question || '';
|
||
taskContainer.appendChild(titleText);
|
||
|
||
// 添加过程描述
|
||
const descLabel = document.createElement('div');
|
||
descLabel.className = 'text-label';
|
||
descLabel.textContent = '参考答案:';
|
||
taskContainer.appendChild(descLabel);
|
||
|
||
const descText = document.createElement('div');
|
||
descText.className = 'process-description';
|
||
// 如果有数据则显示,否则留空
|
||
descText.textContent = meta.answer || '';
|
||
taskContainer.appendChild(descText);
|
||
}
|
||
|
||
container.appendChild(taskContainer);
|
||
|
||
for (const path of this.currentData.shortestPaths) {
|
||
const pathDiv = document.createElement('div');
|
||
pathDiv.className = 'path-container';
|
||
|
||
const meta = this.currentData.shortestPathsMeta.find(m =>
|
||
JSON.stringify(m.chainIDs) === JSON.stringify(path));
|
||
|
||
if (!meta) continue;
|
||
|
||
// 添加缩略图预览区域
|
||
const thumbnailsDiv = document.createElement('div');
|
||
thumbnailsDiv.className = 'path-thumbnails';
|
||
|
||
// 创建所有步骤的缩略图
|
||
for (let i = 0; i < path.length; i++) {
|
||
const thumbDiv = document.createElement('div');
|
||
thumbDiv.className = 'thumbnail-container';
|
||
|
||
// 添加步骤编号
|
||
const stepLabel = document.createElement('div');
|
||
stepLabel.className = 'thumbnail-step-label';
|
||
stepLabel.textContent = `${i}`;
|
||
thumbDiv.appendChild(stepLabel);
|
||
|
||
// 添加缩略图,使用 chainChildNum 而不是滚动位置
|
||
const imgPath = i === path.length - 1
|
||
? `screenshots/${path[i]}_full.png`
|
||
: `screenshots/${path[i]}_${meta.chainChildNum[i]}.png`;
|
||
|
||
const thumbImg = document.createElement('img');
|
||
thumbImg.src = imgPath;
|
||
thumbImg.className = 'thumbnail-image';
|
||
thumbImg.onclick = () => this.openModal(thumbImg);
|
||
|
||
thumbDiv.appendChild(thumbImg);
|
||
thumbnailsDiv.appendChild(thumbDiv);
|
||
}
|
||
|
||
pathDiv.appendChild(thumbnailsDiv);
|
||
|
||
for (let i = 0; i < path.length; i++) {
|
||
const stepDiv = document.createElement('div');
|
||
stepDiv.className = 'step-container';
|
||
|
||
const stepHeader = document.createElement('div');
|
||
stepHeader.className = 'step-header';
|
||
stepHeader.textContent = `步骤 ${i}`;
|
||
stepDiv.appendChild(stepHeader);
|
||
|
||
const urlDiv = document.createElement('div');
|
||
urlDiv.className = 'step-url';
|
||
urlDiv.textContent = `URL: ${meta.chainUrls[i]}`;
|
||
stepDiv.appendChild(urlDiv);
|
||
|
||
// 添加request ID显示和按钮区域
|
||
const requestId = path[i];
|
||
const requestIdDiv = document.createElement('div');
|
||
requestIdDiv.className = 'request-id-container';
|
||
|
||
// 显示request ID
|
||
const requestIdLabel = document.createElement('span');
|
||
requestIdLabel.textContent = `request id: ${requestId}`;
|
||
requestIdDiv.appendChild(requestIdLabel);
|
||
|
||
// 添加查看child按钮
|
||
const viewChildBtn = document.createElement('button');
|
||
viewChildBtn.className = 'view-btn';
|
||
viewChildBtn.textContent = '查看child';
|
||
viewChildBtn.onclick = () => this.viewChildJson(requestId);
|
||
requestIdDiv.appendChild(viewChildBtn);
|
||
|
||
// 添加查看request queue按钮
|
||
const viewQueueBtn = document.createElement('button');
|
||
viewQueueBtn.className = 'view-btn';
|
||
viewQueueBtn.textContent = '查看request queue';
|
||
viewQueueBtn.onclick = () => this.viewRequestQueueJson(requestId);
|
||
requestIdDiv.appendChild(viewQueueBtn);
|
||
|
||
stepDiv.appendChild(requestIdDiv);
|
||
|
||
if (i < meta.chainTexts.length) {
|
||
const textDiv = document.createElement('div');
|
||
textDiv.className = 'path-info';
|
||
textDiv.textContent = `点击文本: ${meta.chainTexts[i]}`;
|
||
stepDiv.appendChild(textDiv);
|
||
}
|
||
|
||
// 构建并显示截图路径,使用 chainChildNum 而不是滚动位置
|
||
const imgPath = i === path.length - 1
|
||
? `screenshots/${path[i]}_full.png`
|
||
: `screenshots/${path[i]}_${meta.chainChildNum[i]}.png`;
|
||
|
||
const screenshotPathDiv = document.createElement('div');
|
||
screenshotPathDiv.className = 'screenshot-path';
|
||
screenshotPathDiv.textContent = `截图路径: ${imgPath}`;
|
||
|
||
// 添加下载按钮
|
||
const downloadBtn = document.createElement('button');
|
||
downloadBtn.className = 'download-btn';
|
||
downloadBtn.textContent = '下载截图';
|
||
downloadBtn.onclick = () => this.downloadImage(imgPath);
|
||
screenshotPathDiv.appendChild(downloadBtn);
|
||
|
||
stepDiv.appendChild(screenshotPathDiv);
|
||
|
||
const screenshotDiv = document.createElement('div');
|
||
screenshotDiv.className = 'screenshot-container';
|
||
|
||
const img = document.createElement('img');
|
||
img.src = imgPath;
|
||
img.onclick = () => this.openModal(img);
|
||
|
||
img.onload = () => {
|
||
if (i < meta.chainViewportBoundingBoxes.length && i !== path.length - 1) {
|
||
const box = meta.chainViewportBoundingBoxes[i];
|
||
const boundingBox = document.createElement('div');
|
||
boundingBox.className = 'bounding-box';
|
||
|
||
const scale = img.width / img.naturalWidth;
|
||
|
||
boundingBox.style.left = `${box.x * scale}px`;
|
||
boundingBox.style.top = `${box.y * scale}px`;
|
||
boundingBox.style.width = `${box.width * scale}px`;
|
||
boundingBox.style.height = `${box.height * scale}px`;
|
||
|
||
screenshotDiv.appendChild(boundingBox);
|
||
}
|
||
};
|
||
|
||
screenshotDiv.appendChild(img);
|
||
stepDiv.appendChild(screenshotDiv);
|
||
|
||
pathDiv.appendChild(stepDiv);
|
||
}
|
||
|
||
container.appendChild(pathDiv);
|
||
}
|
||
}
|
||
|
||
// 添加查看child JSON的方法
|
||
async viewChildJson(requestId) {
|
||
try {
|
||
const response = await fetch(`path/childs/${requestId}.json`);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
const data = await response.json();
|
||
this.showJsonModal('Child JSON', data);
|
||
} catch (error) {
|
||
console.error('加载child JSON失败:', error);
|
||
alert(`加载child JSON失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// 添加查看request queue JSON的方法
|
||
async viewRequestQueueJson(requestId) {
|
||
try {
|
||
const response = await fetch(`storage/request_queues/default/${requestId}.json`);
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
const data = await response.json();
|
||
this.showJsonModal('Request Queue JSON', data);
|
||
} catch (error) {
|
||
console.error('加载request queue JSON失败:', error);
|
||
alert(`加载request queue JSON失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// 显示JSON模态框
|
||
showJsonModal(title, jsonData) {
|
||
// 检查是否已存在JSON模态框,如果不存在则创建
|
||
let jsonModal = document.getElementById('jsonModal');
|
||
if (!jsonModal) {
|
||
jsonModal = document.createElement('div');
|
||
jsonModal.id = 'jsonModal';
|
||
jsonModal.className = 'modal';
|
||
|
||
const modalContent = document.createElement('div');
|
||
modalContent.className = 'modal-content json-modal-content';
|
||
|
||
const closeBtn = document.createElement('span');
|
||
closeBtn.className = 'close-btn';
|
||
closeBtn.innerHTML = '×';
|
||
closeBtn.onclick = () => { jsonModal.style.display = 'none'; };
|
||
|
||
const modalTitle = document.createElement('h3');
|
||
modalTitle.id = 'jsonModalTitle';
|
||
|
||
const preElement = document.createElement('pre');
|
||
preElement.id = 'jsonContent';
|
||
preElement.className = 'json-content';
|
||
|
||
modalContent.appendChild(closeBtn);
|
||
modalContent.appendChild(modalTitle);
|
||
modalContent.appendChild(preElement);
|
||
jsonModal.appendChild(modalContent);
|
||
|
||
document.body.appendChild(jsonModal);
|
||
|
||
// 点击模态框外部关闭
|
||
jsonModal.onclick = (event) => {
|
||
if (event.target === jsonModal) {
|
||
jsonModal.style.display = 'none';
|
||
}
|
||
};
|
||
}
|
||
|
||
// 更新模态框内容
|
||
document.getElementById('jsonModalTitle').textContent = title;
|
||
|
||
// 格式化并高亮JSON
|
||
const formattedJson = JSON.stringify(jsonData, null, 2);
|
||
const preElement = document.getElementById('jsonContent');
|
||
preElement.textContent = formattedJson;
|
||
|
||
// 如果有语法高亮库(如highlight.js),可以在这里应用
|
||
if (window.hljs) {
|
||
preElement.innerHTML = window.hljs.highlight('json', formattedJson).value;
|
||
}
|
||
|
||
// 显示模态框
|
||
jsonModal.style.display = 'block';
|
||
}
|
||
|
||
// 添加下载图片的方法
|
||
async downloadImage(imgPath) {
|
||
try {
|
||
const response = await fetch(imgPath);
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = imgPath.split('/').pop(); // 使用原始文件名
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
document.body.removeChild(a);
|
||
} catch (error) {
|
||
console.error('下载图片失败:', error);
|
||
alert('下载图片失败,请重试');
|
||
}
|
||
}
|
||
}
|
||
|
||
// 初始化可视化器
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
new PathVisualizer();
|
||
});
|