ReasonGraph / templates /index.html
ZongqianLi's picture
Update templates/index.html
59f4f68 verified
raw
history blame
30.3 kB
<!DOCTYPE html>
<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 !important; /* 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> |&nbsp</a><a href="https://arxiv.org/abs/2503.03979"><u>Paper</u> |&nbsp</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>