ReasonGraph / templates /index_cn.html
ZongqianLi's picture
Upload 16 files
7eda955 verified
<!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;
margin-top: 30px; /* Added space to move links lower */
}
.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;
}
.link-separator {
color: white;
opacity: 0.9;
}
.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-button:disabled {
background-color: #9ca3af;
cursor: not-allowed;
color: white;
border-color: #9ca3af;
}
.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;
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 220px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
top: 125%;
left: 50%;
margin-left: -110px;
opacity: 0;
transition: opacity 0.1s;
}
.tooltip .tooltiptext::after {
content: "";
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #555 transparent;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
/* New tooltip-top class for tooltips that appear above elements */
.tooltip-top .tooltiptext {
bottom: 125%; /* Position above instead of below */
top: auto; /* Override the default top value */
}
.tooltip-top .tooltiptext::after {
top: 100%; /* Arrow at the bottom rather than top */
bottom: auto; /* Override the default bottom value */
border-color: #555 transparent transparent transparent; /* Arrow pointing down */
}
.tooltip-wrap .tooltiptext {
width: 440px;
margin-left: -110px;
white-space: normal;
font-size: 14px;
}
.lang-tooltip .tooltiptext {
width: 440px;
margin-left: 0;
transform: translateX(-50%);
font-size: 14px;
white-space: normal;
font-weight: normal;
}
.english-tooltip .tooltiptext {
left: 40%;
}
.chinese-tooltip .tooltiptext {
left: 50%;
}
.mermaid {
padding: 0;
border-radius: 4px;
}
.has-visualization .placeholder-visualization {
display: none;
}
</style>
</head>
<body>
<div class="banner">
<div class="banner-content">
<h1>ReasonGraph</h1>
<div class="search-area">
<div class="search-input-container">
<textarea id="question" class="search-input" placeholder="在此输入您的问题..." rows="1"></textarea>
<div class="error-message" id="question-error">请输入问题</div>
</div>
<div class="search-buttons">
<select class="param-input" id="reasoning-method">
<!-- Populated dynamically -->
</select>
<button onclick="metaReasoning()" id="meta-btn" class="tooltip">元推理
<span class="tooltiptext">使用模型自行选择的推理方法</span>
</button>
<button onclick="processQuestion()" id="process-btn" class="tooltip">开始推理
<span class="tooltiptext">使用选定方法和模型进行推理</span>
</button>
<button onclick="longReasoning()" id="long-btn" class="tooltip">长推理
<span class="tooltiptext">仅由deepseek-reasoner和qwq-plus模型支持;请输入Claude的API Key以进行长推理可视化</span>
</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>论文</u> |&nbsp</a><a href="mailto:[email protected]"><u>Email</u></a>
<span class="link-separator"> |&nbsp|&nbsp</span><a href="./index.html" class="tooltip tooltip-top lang-tooltip english-tooltip"><u>English</u> /&nbsp
<span class="tooltiptext">Switch to English version; To get responses in other languages, ask in that language and say "please answer in this language"</span>
</a><a href="./index_cn.html" class="tooltip tooltip-top lang-tooltip chinese-tooltip"><u>中文</u>
<span class="tooltiptext">切换到中文版;如果想使模型输出中文,只需用中文提问,同时输入"请使用中文回答"</span>
</a>
</div>
</div>
</div>
<div class="container">
<div class="column">
<h2>推理设置</h2>
<div class="param-group">
<div class="param-label">API提供商</div>
<select class="param-input" id="api-provider" onchange="handleProviderChange(this.value)">
<!-- Populated dynamically -->
</select>
</div>
<div class="param-group">
<div class="param-label">模型</div>
<select class="param-input" id="model">
<!-- Populated dynamically -->
</select>
</div>
<div class="param-group">
<div class="param-label">最大输出令牌数</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">请输入有效的API Key</div>
</div>
<div class="param-group">
<div class="param-label">自定义提示格式</div>
<textarea class="param-input" id="prompt-format" rows="6"></textarea>
</div>
<div class="output-section">
<h3>原始模型输出</h3>
<div class="output-wrapper">
<pre id="raw-output">输出将显示在这里...</pre>
</div>
</div>
</div>
<div class="column">
<h2>可视化设置</h2>
<div class="param-group">
<div class="param-label">每行字符数</div>
<input type="number" class="param-input" id="chars-per-line">
</div>
<div class="param-group">
<div class="param-label">最大行数</div>
<input type="number" class="param-input" id="max-lines">
</div>
<div class="output-section">
<h3>可视化结果</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()">重置</button>
<button class="zoom-button" onclick="downloadDiagram()">下载流程图</button>
<button class="zoom-button" onclick="downloadMermaidCode()">下载代码</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;
// Save raw output for long reasoning
let currentRawOutput = "";
// Store user-entered API keys
let userApiKeys = {};
function updateButtonTextPreserveTooltip(button, text) {
for (let i = 0; i < button.childNodes.length; i++) {
if (button.childNodes[i].nodeType === Node.TEXT_NODE) {
button.childNodes[i].nodeValue = text;
return;
}
}
button.prepend(document.createTextNode(text));
}
// Handle API Provider change
async function handleProviderChange(provider) {
try {
// Update model list
updateModelList();
// Check if we have a user-entered API key for this provider
if (userApiKeys[provider]) {
document.getElementById('api-key').value = userApiKeys[provider];
} else {
// Get the default API key only if user hasn't entered one
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);
// Add event listener for API key changes
document.getElementById('api-key').addEventListener('change', function() {
const provider = document.getElementById('api-provider').value;
const apiKey = this.value.trim();
if (apiKey) {
// Save in local memory
userApiKeys[provider] = apiKey;
}
});
} catch (error) {
console.error('Failed to load configuration:', error);
showError('加载配置失败。请刷新页面。');
}
}
// 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('更新方法配置失败。');
}
});
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('没有可下载的图表');
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('下载图表失败');
}
}
// Function to download the Mermaid code
async function downloadMermaidCode() {
// Do nothing if zooming is locked
if (window.isZoomLocked) return;
try {
// First, check if we have stored code from the last visualization
let mermaidCode = lastMermaidCode;
// If no stored code, try to extract it
if (!mermaidCode) {
const diagramContainer = document.getElementById('mermaid-diagram');
if (!diagramContainer) {
alert('没有可下载代码的图表');
return;
}
// Try to find the mermaid element with the code
const mermaidElement = diagramContainer.querySelector('.mermaid');
if (mermaidElement) {
// Get the text content which contains the Mermaid code
mermaidCode = mermaidElement.getAttribute('data-processed') === 'true'
? mermaidElement.dataset.graph
: mermaidElement.textContent;
}
// Fallback: If we can't get the code directly, try to extract from raw output
if (!mermaidCode && currentRawOutput) {
mermaidCode = extractMermaidCode(currentRawOutput);
}
if (!mermaidCode) {
// Another fallback: try to extract from svg data
const svg = diagramContainer.querySelector('svg');
if (svg) {
const svgData = svg.outerHTML;
// Look for mermaid data embedded in the SVG
const match = svgData.match(/data-mermaid="(.*?)"/);
if (match && match[1]) {
mermaidCode = decodeURIComponent(match[1]);
}
}
}
}
if (!mermaidCode) {
// If we still don't have the code, try to generate a basic flowchart from the SVG
const diagramContainer = document.getElementById('mermaid-diagram');
const svg = diagramContainer && diagramContainer.querySelector('svg');
if (svg) {
// Try to reconstruct Mermaid code from SVG elements
mermaidCode = "flowchart TD\n";
mermaidCode += " A[\"This is an auto-generated approximation of the flowchart.\"]\n";
mermaidCode += " B[\"The original Mermaid code could not be extracted.\"]\n";
mermaidCode += " A --> B\n";
mermaidCode += " classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;";
} else {
alert('Unable to extract or generate Mermaid code');
return;
}
}
// Create blob and download link
const blob = new Blob([mermaidCode], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
// Create temporary link and trigger download
const link = document.createElement('a');
link.href = url;
link.download = 'reasoning_diagram_code.txt';
document.body.appendChild(link);
link.click();
// Cleanup
document.body.removeChild(link);
URL.revokeObjectURL(url);
} catch (error) {
console.error('Error downloading Mermaid code:', error);
alert('下载Mermaid代码失败: ' + error.message);
}
}
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 = `错误: ${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 longButton = document.getElementById('long-btn');
const rawOutput = document.getElementById('raw-output');
processButton.disabled = true;
metaButton.disabled = true;
longButton.disabled = true;
updateButtonTextPreserveTooltip(processButton, '处理中...');
rawOutput.textContent = '加载中...';
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';
// Save the raw output for potential long reasoning
currentRawOutput = result.raw_output;
if (result.visualization) {
// Store the raw visualization code before rendering
if (result.visualization.includes('class="mermaid"')) {
const codeMatch = result.visualization.match(/<div class="mermaid">([\s\S]*?)<\/div>/);
if (codeMatch && codeMatch[1]) {
lastMermaidCode = codeMatch[1].trim();
}
}
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 || '发生未知错误');
}
} catch (error) {
showError('处理请求失败: ' + error.message);
} finally {
// Unlock visualization
unlockVisualization();
processButton.disabled = false;
metaButton.disabled = false;
longButton.disabled = false;
updateButtonTextPreserveTooltip(processButton, '开始推理');
if (isMetaReasoning) {
updateButtonTextPreserveTooltip(metaButton, '元推理');
}
}
}
// Long Reasoning function
async function longReasoning() {
if (!validateInputs()) {
return;
}
// Disable all buttons during processing
const processButton = document.getElementById('process-btn');
const metaButton = document.getElementById('meta-btn');
const longButton = document.getElementById('long-btn');
const rawOutput = document.getElementById('raw-output');
processButton.disabled = true;
metaButton.disabled = true;
longButton.disabled = true;
updateButtonTextPreserveTooltip(longButton, '处理中...');
rawOutput.textContent = '切换到纯文本模式并生成初始输出...';
rawOutput.style.color = '#1f2937';
// Lock visualization
lockVisualization();
try {
// 1. Switch to Plain Text reasoning method
const methodSelect = document.getElementById('reasoning-method');
const originalMethod = methodSelect.value; // Save original method
methodSelect.value = 'plain';
// Get and update the prompt format for Plain Text
const methodResponse = await fetch('/method-config/plain');
const methodConfig = await methodResponse.json();
if (methodConfig) {
updatePromptFormat(methodConfig.prompt_format);
}
// 2. Process with Plain Text to get initial output
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: 'plain',
chars_per_line: parseInt(document.getElementById('chars-per-line').value),
max_lines: parseInt(document.getElementById('max-lines').value)
};
rawOutput.textContent = '生成初始输出...';
// Call the process endpoint
const response = await fetch('/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
// Save the raw output for long reasoning
currentRawOutput = result.raw_output;
// Display the plain text output in the Raw Output area
rawOutput.textContent = result.raw_output;
rawOutput.style.color = '#1f2937';
// Reset Zoom and visualization at the beginning - force zoom to 100%
currentZoom = 1; // Force zoom level to 100%
applyZoom(); // Apply the zoom reset
const container = document.getElementById('mermaid-diagram');
container.innerHTML = '';
document.getElementById('zoom-level').textContent = '100%';
// 3. Now proceed with the long reasoning process using Anthropic
const question = document.getElementById('question').value;
// Prepare the long reasoning prompt
const longReasoningPrompt = `Please transform the following original reasoning process into a structured flowchart:
'''
${question}
${currentRawOutput}
'''
Requirements:
0. Create a visually balanced and aesthetically pleasing diagram
1. Structure each node as: [Step Name]: [Concise Summary of Reasoning]
- Keep node text simple and enclose in double quotes inside brackets like: A["Step Name: Reasoning"]
- Example: A["Problem Framing: Define the question"]
2. Support multiple reasoning structures:
- Linear reasoning: Sequential steps leading directly to a conclusion
- Tree reasoning: Branching paths exploring multiple possibilities
- Reflective reasoning: Loops that revisit and refine earlier conclusions
- Not limited to the structures above
3. Flowchart syntax guidance:
- Start with: flowchart TD
- Node definition: letter["text content"]
- Connection definition: A --> B
- Conditional branch: A -->|condition| B
4. When analyzing the reasoning:
- Preserve the logical structure of the original reasoning
- Identify implicit steps that connect explicit reasoning
- Highlight areas where reflection led to revised conclusions
- Maintain proper causal relationships between nodes
- When showing alternative methods or different solution attempts, use a tree-like structure with branches to clearly visualize parallel thinking paths
5. Include these specific style classes in your Mermaid flowchart:
- classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;
- classDef question fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;
- classDef answer fill:#d4edda,stroke:#28a745,stroke-width:2px;
- classDef refinement fill:#fff3cd,stroke:#ffc107,stroke-width:2px;
- classDef aha fill:#ffcccb,stroke:#d9534f,stroke-width:2px;
6. Apply styling to specific node types:
- The first node (starting point) should use class "question"
- The final conclusion/answer node and any significant intermediate results should use class "answer"
- Any sub-questions or question refinements should also use class "question"
- Nodes that involve refinement, reflection, revision, or error correction should use class "refinement". Ensure each refinement node has both incoming arrows from preceding steps and outgoing arrows to subsequent steps, maintaining the full flow of the reasoning process while also creating proper feedback loops.
- Nodes that represent key insights, breakthrough moments, or "aha moments" should use class "aha"
7. Important:
- When applying class styles to nodes, do NOT use the ":::" syntax. Instead, define classes separately with classDef statements and then apply them using "class NodeName ClassName" syntax. For example, use "class A question" rather than "A:::question".
- Ensure all nodes are connected in a single unified flowchart. There should be no disconnected components or floating nodes. Every node must have at least one connection to another node in the diagram.
- Use special node styles (refinement and aha) sparingly and only when truly justified by the content. A node should only be classified as "refinement" if it explicitly revises a previous conclusion, and as "aha" only for genuine breakthrough moments that fundamentally change the direction of reasoning.
- Try to capture all significant steps from the original text. Do not oversimplify or omit important reasoning steps even if the input is lengthy.
- Do not use Python-style comments in the Mermaid code.
Please visualize the thinking steps from the original reasoning process as a proper Mermaid flowchart.`;
// Use Anthropic for the flowchart generation
// Get the appropriate API key for Anthropic
let anthropicApiKey = userApiKeys["anthropic"];
if (!anthropicApiKey) {
// If no user-entered key exists for Anthropic, try to get the default
try {
const keyResponse = await fetch('/provider-api-key/anthropic');
const keyResult = await keyResponse.json();
if (keyResult.success) {
anthropicApiKey = keyResult.api_key;
} else {
throw new Error('无法获取Anthropic的API Key');
}
} catch (error) {
console.error('Error fetching Anthropic API key:', error);
alert('无法获取Anthropic的API Key用于流程图生成');
return;
}
}
const longData = {
provider: "anthropic",
api_key: anthropicApiKey,
model: "claude-3-7-sonnet-20250219", // Use Claude for visualization
max_tokens: parseInt(document.getElementById('max-tokens').value),
question: longReasoningPrompt,
prompt_format: "",
reasoning_method: "cot",
chars_per_line: parseInt(document.getElementById('chars-per-line').value),
max_lines: parseInt(document.getElementById('max-lines').value)
};
// Keep showing the plain text result while generating the flowchart
// Don't update rawOutput.textContent here
const longResponse = await fetch('/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(longData)
});
const longResult = await longResponse.json();
if (longResult.success) {
// Keep displaying the original plain text output
// We don't overwrite rawOutput.textContent here to preserve the plain text output
// Extract Mermaid diagram code from the response
const mermaidCode = extractMermaidCode(longResult.raw_output);
if (mermaidCode) {
// Store the code before rendering
lastMermaidCode = mermaidCode;
// Render the Mermaid diagram
const container = document.getElementById('mermaid-diagram');
container.innerHTML = '<div class="mermaid">' + mermaidCode + '</div>';
// Initialize Mermaid
mermaid.initialize({
startOnLoad: true,
theme: 'default',
securityLevel: 'loose',
flowchart: {
curve: 'basis',
padding: 15
}
});
// Force rendering
mermaid.init(undefined, '.mermaid');
// Add visualization class
document.getElementById('mermaid-container').classList.add('has-visualization');
// Reset zoom
resetZoom();
} else if (longResult.visualization) {
// Fall back to any visualization
const container = document.getElementById('mermaid-diagram');
container.innerHTML = longResult.visualization;
document.getElementById('mermaid-container').classList.add('has-visualization');
resetZoom();
mermaid.init();
}
} else {
// If visualization fails, show error without overwriting raw output
const errorMsg = longResult.error || '流程图生成过程中发生未知错误';
console.error(errorMsg);
alert('可视化错误: ' + errorMsg);
}
} else {
// Use standard error function since we don't have valid output to preserve
showError(result.error || '初始处理过程中发生未知错误');
}
// Don't restore the original method - keep it on Plain Text
// This way the user stays in Plain Text mode after the operation
// Optional: Update the prompt format for Plain Text if needed
const plainTextResponse = await fetch('/method-config/plain');
const plainTextConfig = await plainTextResponse.json();
if (plainTextConfig) {
updatePromptFormat(plainTextConfig.prompt_format);
}
} catch (error) {
console.error('Long reasoning error:', error);
// Only use showError if we don't already have plain text output to preserve
if (!currentRawOutput) {
showError('处理长推理请求失败: ' + error.message);
} else {
alert('可视化错误: ' + error.message);
}
} finally {
// Unlock visualization
unlockVisualization();
// Re-enable all buttons
processButton.disabled = false;
metaButton.disabled = false;
longButton.disabled = false;
updateButtonTextPreserveTooltip(longButton, '长推理');
}
}
// Helper function to extract Mermaid code from response
function extractMermaidCode(text) {
// Look for code blocks with Mermaid content
const mermaidRegex = /```(?:mermaid)?\s*(flowchart[\s\S]*?)```/i;
const match = text.match(mermaidRegex);
if (match && match[1]) {
return match[1].trim();
}
// If no code block, look for just the flowchart content
const flowchartRegex = /(flowchart[\s\S]*?)(?:\n\n|$)/i;
const flowchartMatch = text.match(flowchartRegex);
if (flowchartMatch && flowchartMatch[1]) {
return flowchartMatch[1].trim();
}
return null;
}
// Global variable to store the last mermaid visualization code
let lastMermaidCode = "";
// Meta Reasoning function
async function metaReasoning() {
const metaButton = document.getElementById('meta-btn');
const rawOutput = document.getElementById('raw-output');
try {
metaButton.disabled = true;
updateButtonTextPreserveTooltip(metaButton, '选择方法中...');
rawOutput.textContent = '分析问题以选择最佳方法...';
// 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(`选择的推理方法: ${methodConfig.name}`);
// Update button to show method was selected
updateButtonTextPreserveTooltip(metaButton, '已选择方法');
try {
// Process the question with the selected method
await processQuestion(true);
} catch (processError) {
console.error('Error processing with selected method:', processError);
showError('使用所选方法处理失败: ' + processError.message);
// Make sure to re-enable the button on error
metaButton.disabled = false;
updateButtonTextPreserveTooltip(metaButton, '元推理');
}
} else {
showError('加载方法配置失败');
metaButton.disabled = false;
updateButtonTextPreserveTooltip(metaButton, '元推理');
}
} else {
showError(result.error || '选择方法失败');
metaButton.disabled = false;
updateButtonTextPreserveTooltip(metaButton, '元推理');
}
} catch (error) {
console.error('Meta reasoning error:', error);
showError('执行元推理失败');
metaButton.disabled = false;
updateButtonTextPreserveTooltip(metaButton, '元推理');
}
}
// 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);
/**
* Process Mermaid node text with character and line limits
* @param {string} mermaidCode - Original Mermaid code
* @param {number} maxCharsPerLine - Maximum characters per line
* @param {number} maxLines - Maximum number of lines per node
* @param {string} truncationSuffix - Suffix for truncated text
* @returns {string} Processed Mermaid code
*/
function processMermaidNodeText(mermaidCode, maxCharsPerLine, maxLines, truncationSuffix = "...") {
// Node definition regex: matches patterns like A["text content"] with capture groups
const nodeRegex = /([A-Za-z0-9_]+)\s*\[\s*"([^"]+)"\s*\]/g;
// Process each node's text content
return mermaidCode.replace(nodeRegex, (match, nodeId, text) => {
// Clean text by replacing existing <br> tags and newlines with spaces
const cleanText = text.replace(/<br>/g, ' ').replace(/\n/g, ' ');
// Text wrapping function similar to Python's textwrap.wrap
function wrapText(text, width) {
const words = text.split(' ');
const lines = [];
let currentLine = '';
for (const word of words) {
if (currentLine.length + word.length + (currentLine ? 1 : 0) <= width) {
currentLine += (currentLine ? ' ' : '') + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
if (currentLine) {
lines.push(currentLine);
}
return lines;
}
// Wrap text according to max chars per line
let wrappedLines = wrapText(cleanText, maxCharsPerLine);
// Limit number of lines and add truncation indicator if needed
if (wrappedLines.length > maxLines) {
wrappedLines = wrappedLines.slice(0, maxLines);
const lastLine = wrappedLines[wrappedLines.length - 1];
if (lastLine.length > maxCharsPerLine - truncationSuffix.length) {
wrappedLines[wrappedLines.length - 1] =
lastLine.substring(0, maxCharsPerLine - truncationSuffix.length) + truncationSuffix;
} else {
wrappedLines[wrappedLines.length - 1] = lastLine + truncationSuffix;
}
}
// Join lines with <br> for Mermaid formatting
const processedText = wrappedLines.join('<br>');
// Return the node with processed text
return `${nodeId}["${processedText}"]`;
});
}
// Override extractMermaidCode to apply text processing for Long Reasoning
const originalExtractMermaidCode = extractMermaidCode;
extractMermaidCode = function(text) {
// Call the original function to extract the code
const mermaidCode = originalExtractMermaidCode(text);
if (mermaidCode) {
// Get character and line limits from UI
const maxCharsPerLine = parseInt(document.getElementById('chars-per-line').value) || 40;
const maxLines = parseInt(document.getElementById('max-lines').value) || 4;
// Apply text processing to enforce character and line limits
return processMermaidNodeText(mermaidCode, maxCharsPerLine, maxLines);
}
return mermaidCode;
};
</script>
</body>
</html>