Spaces:
Running
Running
<html> | |
<head> | |
<title>ReasonGraph</title> | |
<link rel="icon" href="static/assets/idea.png" type="image/x-icon"> | |
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js"></script> | |
<style> | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; | |
margin: 0; | |
padding: 20px; | |
background-color: #f5f5f5; | |
} | |
.banner { | |
margin: 3px 20px; | |
border-radius: 8px; | |
background-image: url("{{ url_for('static', filename='assets/banner-bg.jpg') }}"); | |
background-size: cover; | |
background-position: center; | |
box-shadow: 0 1px 3px rgba(0,0,0,0.1); | |
height: 280px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
position: relative; | |
} | |
.banner::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: rgba(0, 0, 0, 0.4); | |
border-radius: 8px; | |
} | |
.banner-content { | |
position: relative; | |
z-index: 1; | |
text-align: center; | |
display: flex; | |
flex-direction: column; | |
gap: 24px; | |
width: 100%; | |
max-width: 800px; | |
padding: 0 20px; | |
} | |
.banner h1 { | |
color: white; | |
font-size: 32px; | |
font-weight: 600; | |
margin: 0; | |
text-shadow: 0 2px 4px rgba(0,0,0,0.2); | |
} | |
.search-area { | |
display: flex; | |
flex-direction: column; | |
gap: 16px; | |
width: 100%; | |
} | |
.search-input-container { | |
position: relative; | |
width: 100%; | |
} | |
.search-input { | |
width: 100%; | |
padding: 12px; | |
border: 2px solid rgba(255, 255, 255, 0.2); | |
border-radius: 8px; | |
background: rgba(255, 255, 255, 0.9); | |
font-size: 16px; | |
resize: none; | |
outline: none; | |
transition: border-color 0.2s; | |
} | |
.search-input:focus { | |
border-color: rgba(255, 255, 255, 0.5); | |
} | |
.search-buttons { | |
display: flex; | |
gap: 10px; | |
justify-content: center; | |
align-items: center; | |
margin: 0 auto; | |
max-width: 800px; /* Match search input max-width */ | |
width: 100%; | |
padding: 0 20px; | |
} | |
.search-buttons .param-input { | |
width: 200px ; /* Override the default param-input width */ | |
padding: 8px 16px; | |
background-color: rgba(255, 255, 255, 0.9); | |
border: none; | |
border-radius: 6px; | |
font-size: 14px; | |
font-weight: 500; | |
height: 35px; /* Match button height */ | |
flex: none; /* Override flex property */ | |
} | |
.search-buttons button { | |
width: auto; | |
min-width: 120px; | |
padding: 8px 16px; | |
background-color: rgba(37, 99, 235, 0.8); /* Semi-transparent gray */ | |
color: white; /* White text */ | |
border: none; | |
border-radius: 6px; | |
font-size: 14px; | |
font-weight: 500; | |
cursor: pointer; | |
transition: all 0.2s; | |
height: 35px; /* Fixed height */ | |
line-height: 1; /* Ensure text vertical centering */ | |
} | |
.search-buttons button:hover { | |
background-color: rgba(37, 99, 235, 0.9); /* 鼠标悬停时变为深一点的蓝色 */ | |
transform: translateY(-1px); | |
} | |
.links { | |
display: flex; | |
justify-content: center; | |
gap: 0; | |
white-space: nowrap; | |
} | |
.links a { | |
color: white; | |
text-decoration: none; | |
font-size: 16px; | |
opacity: 0.9; | |
transition: opacity 0.2s; | |
} | |
.links a:hover { | |
opacity: 1; | |
text-decoration: underline; | |
} | |
.container { | |
display: flex; | |
min-height: 100vh; | |
gap: 20px; | |
padding: 20px; | |
} | |
.column { | |
flex: 1; | |
background: white; | |
border-radius: 8px; | |
box-shadow: 0 1px 3px rgba(0,0,0,0.1); | |
padding: 20px; | |
} | |
h2 { | |
margin-top: 0; | |
margin-bottom: 20px; | |
color: #1f2937; | |
font-size: 18px; | |
font-weight: 600; | |
} | |
.param-group { | |
display: flex; | |
margin-bottom: 15px; | |
border: 1px solid #e5e7eb; | |
border-radius: 4px; | |
overflow: hidden; | |
} | |
.param-label { | |
width: 180px; | |
padding: 10px 15px; | |
background-color: #f8f9fa; | |
border-right: 1px solid #e5e7eb; | |
font-size: 14px; | |
line-height: 1.5; | |
color: #374151; | |
} | |
.param-input { | |
flex: 1; | |
padding: 10px 15px; | |
border: none; | |
font-size: 14px; | |
line-height: 1.5; | |
outline: none; | |
background: white; | |
} | |
select.param-input { | |
cursor: pointer; | |
padding-right: 30px; | |
appearance: none; | |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%236b7280' viewBox='0 0 16 16'%3E%3Cpath d='M8 10l4-4H4l4 4z'/%3E%3C/svg%3E"); | |
background-repeat: no-repeat; | |
background-position: right 10px center; | |
} | |
textarea.param-input { | |
resize: vertical; | |
min-height: 80px; | |
} | |
.error-message { | |
color: #dc2626; | |
font-size: 14px; | |
display: none; | |
position: absolute; | |
right: 0; | |
top: 50%; | |
transform: translateY(-50%); | |
background: white; | |
padding: 2px 8px; | |
border-radius: 4px; | |
box-shadow: 0 1px 2px rgba(0,0,0,0.1); | |
margin-right: 10px; | |
} | |
.search-input-container, .param-group { | |
position: relative; | |
} | |
.output-section { | |
margin-top: 20px; | |
padding: 0; | |
background: white; | |
} | |
.output-section h3 { | |
margin: 0 0 15px 0; | |
color: #1f2937; | |
font-size: 18px; | |
font-weight: 600; | |
} | |
.output-wrapper { | |
overflow: auto; | |
height: 100px; | |
min-height: 100px; | |
max-height: 1000px; | |
border: 1px solid #e5e7eb; | |
border-radius: 4px; | |
padding: 15px; | |
resize: vertical; | |
background-color: #f8f9fa; | |
} | |
#raw-output { | |
white-space: pre-wrap; | |
word-break: break-word; | |
margin: 0; | |
font-family: 'Menlo', 'Monaco', 'Courier New', monospace; | |
font-size: 13px; | |
line-height: 1.5; | |
color: #1f2937; | |
} | |
button:disabled { | |
background-color: #9ca3af; | |
cursor: not-allowed; | |
} | |
.zoom-controls { | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
margin-bottom: 10px; | |
} | |
.zoom-button { | |
padding: 5px 10px; | |
background-color: #f3f4f6; | |
border: 1px solid #e5e7eb; | |
border-radius: 4px; | |
cursor: pointer; | |
font-size: 14px; | |
color: #374151; | |
width: auto; | |
} | |
.zoom-button:hover { | |
background-color: #e5e7eb; | |
} | |
.zoom-level { | |
font-size: 14px; | |
color: #374151; | |
min-width: 50px; | |
text-align: center; | |
} | |
#mermaid-container { | |
transform-origin: top left; /* Changed from 'top center' */ | |
transition: transform 0.2s ease; | |
width: 100%; | |
display: block; /* Changed from 'flex' */ | |
justify-content: flex-start; /* Changed from 'center' */ | |
} | |
.visualization-wrapper { | |
overflow: auto; | |
height: 370px; | |
min-height: 100px; | |
max-height: 1000px; | |
border: 1px solid #e5e7eb; | |
border-radius: 4px; | |
padding: 0; | |
resize: vertical; | |
background-color: #f8f9fa; | |
} | |
.mermaid { | |
padding: 0; | |
border-radius: 4px; | |
} | |
.has-visualization .placeholder-visualization { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="banner"> | |
<div class="banner-content"> | |
<h1>ReasonGraph: Visualisation of Reasoning Paths</h1> | |
<div class="search-area"> | |
<div class="search-input-container"> | |
<textarea id="question" class="search-input" placeholder="Enter your question here..." rows="1"></textarea> | |
<div class="error-message" id="question-error">Please enter a question</div> | |
</div> | |
<div class="search-buttons"> | |
<select class="param-input" id="reasoning-method"> | |
<!-- Populated dynamically --> | |
</select> | |
<button onclick="metaReasoning()" id="meta-btn">Meta Reasoning</button> | |
<button onclick="processQuestion()" id="process-btn">Start Reasoning</button> | |
</div> | |
</div> | |
<div class="links"> | |
<a href="https://github.com/ZongqianLi/ReasonGraph"><u>Github</u> | </a><a href="https://arxiv.org/abs/2503.03979"><u>Paper</u> | </a><a href="mailto:[email protected]"><u>Email</u></a> | |
</div> | |
</div> | |
</div> | |
<div class="container"> | |
<div class="column"> | |
<h2>Reasoning Settings</h2> | |
<div class="param-group"> | |
<div class="param-label">API Provider</div> | |
<select class="param-input" id="api-provider" onchange="handleProviderChange(this.value)"> | |
<!-- Populated dynamically --> | |
</select> | |
</div> | |
<div class="param-group"> | |
<div class="param-label">Model</div> | |
<select class="param-input" id="model"> | |
<!-- Populated dynamically --> | |
</select> | |
</div> | |
<div class="param-group"> | |
<div class="param-label">Max Tokens</div> | |
<input type="number" class="param-input" id="max-tokens"> | |
</div> | |
<div class="param-group"> | |
<div class="param-label">API Key</div> | |
<input type="password" class="param-input" id="api-key"> | |
<div class="error-message" id="api-key-error">Please enter a valid API key</div> | |
</div> | |
<div class="param-group"> | |
<div class="param-label">Custom Prompt Format</div> | |
<textarea class="param-input" id="prompt-format" rows="6"></textarea> | |
</div> | |
<div class="output-section"> | |
<h3>Raw Model Output</h3> | |
<div class="output-wrapper"> | |
<pre id="raw-output">Output will appear here...</pre> | |
</div> | |
</div> | |
</div> | |
<div class="column"> | |
<h2>Visualization Settings</h2> | |
<div class="param-group"> | |
<div class="param-label">Characters Per Line</div> | |
<input type="number" class="param-input" id="chars-per-line"> | |
</div> | |
<div class="param-group"> | |
<div class="param-label">Maximum Lines</div> | |
<input type="number" class="param-input" id="max-lines"> | |
</div> | |
<div class="output-section"> | |
<h3>Visualization Results</h3> | |
<div class="zoom-controls"> | |
<button class="zoom-button" onclick="adjustZoom(-0.1)">-</button> | |
<div class="zoom-level" id="zoom-level">100%</div> | |
<button class="zoom-button" onclick="adjustZoom(0.1)">+</button> | |
<button class="zoom-button" onclick="resetZoom()">Reset</button> | |
<button class="zoom-button" onclick="downloadDiagram()">Download</button> | |
</div> | |
<div class="visualization-wrapper"> | |
<div id="mermaid-container"> | |
<div id="mermaid-diagram"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Initialize Mermaid | |
mermaid.initialize({ | |
startOnLoad: true, | |
theme: 'default', | |
securityLevel: 'loose', | |
flowchart: { | |
curve: 'basis', | |
padding: 15 | |
} | |
}); | |
// Store current configuration | |
let currentConfig = null; | |
// Zoom control variables | |
let currentZoom = 1; | |
const MIN_ZOOM = 0.1; | |
const MAX_ZOOM = 5; | |
// Initialize zoom lock flag | |
window.isZoomLocked = false; | |
// Handle API Provider change | |
async function handleProviderChange(provider) { | |
try { | |
// Update model list | |
updateModelList(); | |
// Get and update API key | |
const response = await fetch(`/provider-api-key/${provider}`); | |
const result = await response.json(); | |
if (result.success) { | |
document.getElementById('api-key').value = result.api_key; | |
} else { | |
console.error('Failed to get API key:', result.error); | |
} | |
} catch (error) { | |
console.error('Error updating provider settings:', error); | |
} | |
} | |
// Load initial configuration | |
async function loadConfig() { | |
try { | |
const response = await fetch('/config'); | |
currentConfig = await response.json(); | |
// Populate API providers | |
const providerSelect = document.getElementById('api-provider'); | |
currentConfig.general.providers.forEach(provider => { | |
const option = document.createElement('option'); | |
option.value = provider; | |
option.textContent = provider.charAt(0).toUpperCase() + provider.slice(1); | |
providerSelect.appendChild(option); | |
}); | |
// Populate reasoning methods | |
const methodSelect = document.getElementById('reasoning-method'); | |
Object.entries(currentConfig.methods).forEach(([id, methodConfig]) => { | |
const option = document.createElement('option'); | |
option.value = id; | |
option.textContent = methodConfig.name; | |
methodSelect.appendChild(option); | |
}); | |
// Initial provider setup | |
await handleProviderChange(currentConfig.general.providers[0]); | |
// Set other initial values | |
document.getElementById('max-tokens').value = currentConfig.general.max_tokens; | |
document.getElementById('chars-per-line').value = currentConfig.general.visualization.chars_per_line; | |
document.getElementById('max-lines').value = currentConfig.general.visualization.max_lines; | |
// Set initial prompt format and example question | |
const defaultMethod = methodSelect.value; | |
const methodConfig = currentConfig.methods[defaultMethod]; | |
updatePromptFormat(methodConfig.prompt_format); | |
updateExampleQuestion(methodConfig.example_question); | |
} catch (error) { | |
console.error('Failed to load configuration:', error); | |
showError('Failed to load configuration. Please refresh the page.'); | |
} | |
} | |
// Update model list based on selected provider | |
function updateModelList() { | |
const provider = document.getElementById('api-provider').value; | |
const modelSelect = document.getElementById('model'); | |
modelSelect.innerHTML = ''; // Clear current options | |
const models = currentConfig.general.available_models; | |
const providers = currentConfig.general.model_providers; | |
models.forEach(model => { | |
if (providers[model] === provider) { | |
const option = document.createElement('option'); | |
option.value = model; | |
option.textContent = model; | |
modelSelect.appendChild(option); | |
} | |
}); | |
} | |
// Update prompt format when method changes | |
document.getElementById('reasoning-method').addEventListener('change', async (event) => { | |
try { | |
const response = await fetch(`/method-config/${event.target.value}`); | |
const methodConfig = await response.json(); | |
updatePromptFormat(methodConfig.prompt_format); | |
updateExampleQuestion(methodConfig.example_question); | |
} catch (error) { | |
console.error('Failed to load method configuration:', error); | |
showError('Failed to update method configuration.'); | |
} | |
}); | |
function updatePromptFormat(format) { | |
document.getElementById('prompt-format').value = format; | |
} | |
function updateExampleQuestion(question) { | |
document.getElementById('question').value = question; | |
} | |
function adjustZoom(delta) { | |
// Do nothing if zooming is locked | |
if (window.isZoomLocked) return; | |
const newZoom = Math.min(Math.max(currentZoom + delta, MIN_ZOOM), MAX_ZOOM); | |
if (newZoom !== currentZoom) { | |
currentZoom = newZoom; | |
applyZoom(); | |
} | |
} | |
function resetZoom() { | |
// Do nothing if zooming is locked | |
if (window.isZoomLocked) return; | |
currentZoom = 1; | |
applyZoom(); | |
} | |
function applyZoom() { | |
const container = document.getElementById('mermaid-container'); | |
container.style.transform = `scale(${currentZoom})`; | |
// Update zoom level display | |
const percentage = Math.round(currentZoom * 100); | |
document.getElementById('zoom-level').textContent = `${percentage}%`; | |
} | |
function lockVisualization() { | |
window.isZoomLocked = true; | |
const zoomButtons = document.querySelectorAll('.zoom-button'); | |
zoomButtons.forEach(button => button.disabled = true); | |
document.querySelector('.visualization-wrapper').style.pointerEvents = 'none'; | |
} | |
function unlockVisualization() { | |
window.isZoomLocked = false; | |
const zoomButtons = document.querySelectorAll('.zoom-button'); | |
zoomButtons.forEach(button => button.disabled = false); | |
document.querySelector('.visualization-wrapper').style.pointerEvents = 'auto'; | |
} | |
async function downloadDiagram() { | |
// Do nothing if zooming is locked | |
if (window.isZoomLocked) return; | |
const diagramContainer = document.getElementById('mermaid-diagram'); | |
if (!diagramContainer || !diagramContainer.querySelector('svg')) { | |
alert('No diagram available to download'); | |
return; | |
} | |
try { | |
// Get the SVG element | |
const svg = diagramContainer.querySelector('svg'); | |
// Create a copy of the SVG to modify | |
const svgCopy = svg.cloneNode(true); | |
// Ensure the SVG has proper dimensions | |
const bbox = svg.getBBox(); | |
svgCopy.setAttribute('width', bbox.width); | |
svgCopy.setAttribute('height', bbox.height); | |
svgCopy.setAttribute('viewBox', `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`); | |
// Convert SVG to string | |
const serializer = new XMLSerializer(); | |
const svgString = serializer.serializeToString(svgCopy); | |
// Create blob and download link | |
const blob = new Blob([svgString], {type: 'image/svg+xml'}); | |
const url = URL.createObjectURL(blob); | |
// Create temporary link and trigger download | |
const link = document.createElement('a'); | |
link.href = url; | |
link.download = 'reasoning_diagram.svg'; | |
document.body.appendChild(link); | |
link.click(); | |
// Cleanup | |
document.body.removeChild(link); | |
URL.revokeObjectURL(url); | |
} catch (error) { | |
console.error('Error downloading diagram:', error); | |
alert('Failed to download diagram'); | |
} | |
} | |
function validateInputs() { | |
const apiKey = document.getElementById('api-key').value.trim(); | |
const question = document.getElementById('question').value.trim(); | |
let isValid = true; | |
// Validate API Key | |
if (!apiKey) { | |
document.getElementById('api-key-error').style.display = 'block'; | |
isValid = false; | |
} else { | |
document.getElementById('api-key-error').style.display = 'none'; | |
} | |
// Validate Question | |
if (!question) { | |
document.getElementById('question-error').style.display = 'block'; | |
isValid = false; | |
} else { | |
document.getElementById('question-error').style.display = 'none'; | |
} | |
return isValid; | |
} | |
function showError(message) { | |
const rawOutput = document.getElementById('raw-output'); | |
rawOutput.textContent = `Error: ${message}`; | |
rawOutput.style.color = '#dc2626'; | |
} | |
// Process question | |
async function processQuestion(isMetaReasoning = false) { | |
if (!validateInputs()) { | |
return; | |
} | |
// Reset Zoom before processing question | |
resetZoom(); | |
const processButton = document.getElementById('process-btn'); | |
const metaButton = document.getElementById('meta-btn'); | |
const rawOutput = document.getElementById('raw-output'); | |
processButton.disabled = true; | |
metaButton.disabled = true; | |
processButton.textContent = 'Processing...'; | |
rawOutput.textContent = 'Loading...'; | |
rawOutput.style.color = '#1f2937'; | |
// Lock visualization | |
lockVisualization(); | |
const data = { | |
provider: document.getElementById('api-provider').value, | |
api_key: document.getElementById('api-key').value, | |
model: document.getElementById('model').value, | |
max_tokens: parseInt(document.getElementById('max-tokens').value), | |
question: document.getElementById('question').value, | |
prompt_format: document.getElementById('prompt-format').value, | |
reasoning_method: document.getElementById('reasoning-method').value, | |
chars_per_line: parseInt(document.getElementById('chars-per-line').value), | |
max_lines: parseInt(document.getElementById('max-lines').value) | |
}; | |
try { | |
const response = await fetch('/process', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(data) | |
}); | |
const result = await response.json(); | |
if (result.success) { | |
rawOutput.textContent = result.raw_output; | |
rawOutput.style.color = '#1f2937'; | |
if (result.visualization) { | |
const container = document.getElementById('mermaid-diagram'); | |
container.innerHTML = result.visualization; | |
document.getElementById('mermaid-container').classList.add('has-visualization'); | |
resetZoom(); | |
mermaid.init(); | |
} | |
} else { | |
showError(result.error || 'Unknown error occurred'); | |
} | |
} catch (error) { | |
showError('Failed to process request: ' + error.message); | |
} finally { | |
// Unlock visualization | |
unlockVisualization(); | |
processButton.disabled = false; | |
metaButton.disabled = false; | |
processButton.textContent = 'Start Reasoning'; | |
if (isMetaReasoning) { | |
metaButton.textContent = 'Meta Reasoning'; | |
} | |
} | |
} | |
// Meta Reasoning function | |
async function metaReasoning() { | |
const metaButton = document.getElementById('meta-btn'); | |
const rawOutput = document.getElementById('raw-output'); | |
try { | |
metaButton.disabled = true; | |
metaButton.textContent = 'Selecting Method...'; | |
rawOutput.textContent = 'Analyzing question to select best method...'; | |
// Get current parameters | |
const data = { | |
provider: document.getElementById('api-provider').value, | |
api_key: document.getElementById('api-key').value, | |
model: document.getElementById('model').value, | |
question: document.getElementById('question').value | |
}; | |
// Call the method selection endpoint | |
const response = await fetch('/select-method', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify(data) | |
}); | |
const result = await response.json(); | |
if (result.success) { | |
// Set the selected method | |
const methodSelect = document.getElementById('reasoning-method'); | |
methodSelect.value = result.selected_method; | |
// Fetch and update the corresponding method configuration | |
const methodResponse = await fetch(`/method-config/${result.selected_method}`); | |
const methodConfig = await methodResponse.json(); | |
if (methodConfig) { | |
// Update the prompt format | |
updatePromptFormat(methodConfig.prompt_format); | |
// Update example question if needed | |
if (document.getElementById('question').value === '') { | |
updateExampleQuestion(methodConfig.example_question); | |
} | |
console.log(`Selected reasoning method: ${methodConfig.name}`); | |
// Update button to show method was selected | |
metaButton.textContent = 'Method Selected'; | |
// Process the question with the selected method | |
await processQuestion(true); | |
} else { | |
showError('Failed to load method configuration'); | |
metaButton.textContent = 'Meta Reasoning'; | |
} | |
} else { | |
showError(result.error || 'Failed to select method'); | |
metaButton.textContent = 'Meta Reasoning'; | |
} | |
} catch (error) { | |
console.error('Meta reasoning error:', error); | |
showError('Failed to execute meta reasoning'); | |
metaButton.disabled = false; | |
metaButton.textContent = 'Meta Reasoning'; | |
} | |
} | |
// Add event listener for mouse wheel zoom | |
document.querySelector('.visualization-wrapper').addEventListener('wheel', function(e) { | |
// Do nothing if zooming is locked | |
if (window.isZoomLocked) { | |
e.preventDefault(); | |
return; | |
} | |
if (e.ctrlKey) { | |
e.preventDefault(); // Prevent default zoom | |
const delta = e.deltaY > 0 ? -0.1 : 0.1; | |
adjustZoom(delta); | |
} | |
}); | |
// Load configuration when page loads | |
document.addEventListener('DOMContentLoaded', loadConfig); | |
</script> | |
</body> | |
</html> |