ZongqianLi commited on
Commit
7eda955
·
verified ·
1 Parent(s): c2a2581

Upload 16 files

Browse files
api_base.py CHANGED
@@ -1,3 +1,9 @@
 
 
 
 
 
 
1
  from abc import ABC, abstractmethod
2
  import logging
3
  import requests
@@ -246,7 +252,15 @@ class DeepSeekAPI(BaseAPI):
246
  max_tokens=max_tokens
247
  )
248
 
249
- return response.choices[0].message.content
 
 
 
 
 
 
 
 
250
 
251
  except Exception as e:
252
  self._handle_error(e, "request or response processing")
@@ -272,15 +286,49 @@ class QwenAPI(BaseAPI):
272
  formatted_prompt = self._format_prompt(prompt, prompt_format)
273
 
274
  logger.info(f"Sending request to Qwen API with model {self.model}")
275
- response = self.client.chat.completions.create(
276
- model=self.model,
277
- messages=[
278
- {"role": "user", "content": formatted_prompt}
279
- ],
280
- max_tokens=max_tokens
281
- )
282
 
283
- return response.choices[0].message.content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
 
285
  except Exception as e:
286
  self._handle_error(e, "request or response processing")
 
1
+ """
2
+ Base API module for handling different API providers.
3
+ This module provides a unified interface for interacting with various API providers
4
+ like Anthropic, OpenAI, Google Gemini and Together AI.
5
+ """
6
+
7
  from abc import ABC, abstractmethod
8
  import logging
9
  import requests
 
252
  max_tokens=max_tokens
253
  )
254
 
255
+ # Check if this is the reasoning model response
256
+ if self.model == "deepseek-reasoner" and hasattr(response.choices[0].message, "reasoning_content"):
257
+ # Include both reasoning and answer
258
+ reasoning = response.choices[0].message.reasoning_content
259
+ answer = response.choices[0].message.content
260
+ return f"Reasoning:\n{reasoning}\n\nAnswer:\n{answer}"
261
+ else:
262
+ # Regular model response
263
+ return response.choices[0].message.content
264
 
265
  except Exception as e:
266
  self._handle_error(e, "request or response processing")
 
286
  formatted_prompt = self._format_prompt(prompt, prompt_format)
287
 
288
  logger.info(f"Sending request to Qwen API with model {self.model}")
 
 
 
 
 
 
 
289
 
290
+ # Check if this is the reasoning model (qwq-plus)
291
+ if self.model == "qwq-plus":
292
+ # For qwq-plus model, we need to use streaming
293
+ reasoning_content = ""
294
+ answer_content = ""
295
+ is_answering = False
296
+
297
+ response = self.client.chat.completions.create(
298
+ model=self.model,
299
+ messages=[
300
+ {"role": "user", "content": formatted_prompt}
301
+ ],
302
+ max_tokens=max_tokens,
303
+ stream=True # qwq-plus only supports streaming output
304
+ )
305
+
306
+ for chunk in response:
307
+ if not chunk.choices:
308
+ continue
309
+
310
+ delta = chunk.choices[0].delta
311
+ # Collect reasoning process
312
+ if hasattr(delta, 'reasoning_content') and delta.reasoning_content is not None:
313
+ reasoning_content += delta.reasoning_content
314
+ # Collect answer content
315
+ elif hasattr(delta, 'content') and delta.content is not None:
316
+ answer_content += delta.content
317
+ is_answering = True
318
+
319
+ # Return combined reasoning and answer
320
+ return f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{answer_content}"
321
+ else:
322
+ # Regular model response (non-streaming)
323
+ response = self.client.chat.completions.create(
324
+ model=self.model,
325
+ messages=[
326
+ {"role": "user", "content": formatted_prompt}
327
+ ],
328
+ max_tokens=max_tokens
329
+ )
330
+
331
+ return response.choices[0].message.content
332
 
333
  except Exception as e:
334
  self._handle_error(e, "request or response processing")
app.py CHANGED
@@ -1,5 +1,9 @@
1
  from flask import Flask, render_template, request, jsonify
2
  from api_base import create_api # New import for API factory
 
 
 
 
3
  from cot_reasoning import (
4
  VisualizationConfig,
5
  create_mermaid_diagram as create_cot_diagram,
@@ -82,6 +86,41 @@ def get_provider_api_key(provider):
82
  'error': str(e)
83
  }), 500
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  @app.route('/select-method', methods=['POST'])
86
  def select_method():
87
  """Let the model select the most appropriate reasoning method"""
@@ -233,6 +272,9 @@ def process():
233
  elif reasoning_method == 'bs':
234
  result = parse_bs_response(raw_response, question)
235
  visualization = create_bs_diagram(result, viz_config)
 
 
 
236
 
237
  logger.info("Successfully generated visualization")
238
  except Exception as viz_error:
 
1
  from flask import Flask, render_template, request, jsonify
2
  from api_base import create_api # New import for API factory
3
+ from plain_text_reasoning import (
4
+ create_mermaid_diagram as create_plain_diagram,
5
+ parse_plain_text_response
6
+ )
7
  from cot_reasoning import (
8
  VisualizationConfig,
9
  create_mermaid_diagram as create_cot_diagram,
 
86
  'error': str(e)
87
  }), 500
88
 
89
+ @app.route('/save-api-key', methods=['POST'])
90
+ def save_api_key():
91
+ """Save API key for a provider in memory only (no file storage)"""
92
+ try:
93
+ data = request.json
94
+ if not data:
95
+ return jsonify({
96
+ 'success': False,
97
+ 'error': 'No data provided'
98
+ }), 400
99
+
100
+ provider = data.get('provider')
101
+ api_key = data.get('api_key')
102
+
103
+ if not provider or not api_key:
104
+ return jsonify({
105
+ 'success': False,
106
+ 'error': 'Provider and API key are required'
107
+ }), 400
108
+
109
+ # Update API key in config (this updates the in-memory API keys only)
110
+ config.general.provider_api_keys[provider] = api_key
111
+ logger.info(f"Saved API key for provider: {provider} (in memory only)")
112
+
113
+ return jsonify({
114
+ 'success': True
115
+ })
116
+
117
+ except Exception as e:
118
+ logger.error(f"Error saving API key: {str(e)}")
119
+ return jsonify({
120
+ 'success': False,
121
+ 'error': str(e)
122
+ }), 500
123
+
124
  @app.route('/select-method', methods=['POST'])
125
  def select_method():
126
  """Let the model select the most appropriate reasoning method"""
 
272
  elif reasoning_method == 'bs':
273
  result = parse_bs_response(raw_response, question)
274
  visualization = create_bs_diagram(result, viz_config)
275
+ elif reasoning_method == 'plain':
276
+ parse_plain_text_response(raw_response, question)
277
+ visualization = None
278
 
279
  logger.info("Successfully generated visualization")
280
  except Exception as viz_error:
configs.py CHANGED
@@ -49,6 +49,7 @@ class GeneralConfig:
49
  "gemini-1.5-flash",
50
  "gemini-1.5-flash-8b",
51
  "gemini-1.5-pro",
 
52
  # Together AI Models
53
  "meta-llama/Llama-3.3-70B-Instruct-Turbo",
54
  #"meta-llama/Llama-3.2-3B-Instruct-Turbo",
@@ -75,6 +76,7 @@ class GeneralConfig:
75
  #"nvidia/Llama-3.1-Nemotron-70B-Instruct-HF",
76
  # DeepSeek Models
77
  "deepseek-chat",
 
78
  # Qwen Models
79
  "qwen-max",
80
  "qwen-max-latest",
@@ -85,6 +87,7 @@ class GeneralConfig:
85
  "qwen-turbo",
86
  "qwen-turbo-latest",
87
  "qwen-turbo-2024-11-01",
 
88
  #"qwen2.5-14b-instruct-1m",
89
  #"qwen2.5-7b-instruct-1m",
90
  "qwen2.5-72b-instruct",
@@ -114,6 +117,7 @@ class GeneralConfig:
114
  "gemini-1.5-flash": "google",
115
  "gemini-1.5-flash-8b": "google",
116
  "gemini-1.5-pro": "google",
 
117
  "meta-llama/Llama-3.3-70B-Instruct-Turbo": "together",
118
  #"meta-llama/Llama-3.2-3B-Instruct-Turbo": "together",
119
  "meta-llama/Meta-Llama-3.1-405B-Instruct-Lite-Pro": "together",
@@ -138,6 +142,7 @@ class GeneralConfig:
138
  #"databricks/dbrx-instruct": "together",
139
  #"nvidia/Llama-3.1-Nemotron-70B-Instruct-HF": "together",
140
  "deepseek-chat": "deepseek",
 
141
  "qwen-max": "qwen",
142
  "qwen-max-latest": "qwen",
143
  "qwen-max-2025-01-25": "qwen",
@@ -147,6 +152,7 @@ class GeneralConfig:
147
  "qwen-turbo": "qwen",
148
  "qwen-turbo-latest": "qwen",
149
  "qwen-turbo-2024-11-01": "qwen",
 
150
  #"qwen2.5-14b-instruct-1m": "qwen",
151
  #"qwen2.5-7b-instruct-1m": "qwen",
152
  "qwen2.5-72b-instruct": "qwen",
@@ -169,6 +175,13 @@ class GeneralConfig:
169
  """Get default API key for specific provider"""
170
  return self.provider_api_keys.get(provider, "")
171
 
 
 
 
 
 
 
 
172
  @dataclass
173
  class ChainOfThoughtsConfig:
174
  """Configuration specific to Chain of Thoughts method"""
@@ -373,6 +386,7 @@ class ReasoningConfig:
373
  "srf": SelfRefineConfig(),
374
  "l2m": LeastToMostConfig(),
375
  "bs": BeamSearchConfig(),
 
376
  }
377
 
378
  def get_method_config(self, method_id: str) -> Optional[dict]:
 
49
  "gemini-1.5-flash",
50
  "gemini-1.5-flash-8b",
51
  "gemini-1.5-pro",
52
+ "gemini-2.0-flash-thinking-exp",
53
  # Together AI Models
54
  "meta-llama/Llama-3.3-70B-Instruct-Turbo",
55
  #"meta-llama/Llama-3.2-3B-Instruct-Turbo",
 
76
  #"nvidia/Llama-3.1-Nemotron-70B-Instruct-HF",
77
  # DeepSeek Models
78
  "deepseek-chat",
79
+ "deepseek-reasoner",
80
  # Qwen Models
81
  "qwen-max",
82
  "qwen-max-latest",
 
87
  "qwen-turbo",
88
  "qwen-turbo-latest",
89
  "qwen-turbo-2024-11-01",
90
+ "qwq-plus",
91
  #"qwen2.5-14b-instruct-1m",
92
  #"qwen2.5-7b-instruct-1m",
93
  "qwen2.5-72b-instruct",
 
117
  "gemini-1.5-flash": "google",
118
  "gemini-1.5-flash-8b": "google",
119
  "gemini-1.5-pro": "google",
120
+ "gemini-2.0-flash-thinking-exp": "google",
121
  "meta-llama/Llama-3.3-70B-Instruct-Turbo": "together",
122
  #"meta-llama/Llama-3.2-3B-Instruct-Turbo": "together",
123
  "meta-llama/Meta-Llama-3.1-405B-Instruct-Lite-Pro": "together",
 
142
  #"databricks/dbrx-instruct": "together",
143
  #"nvidia/Llama-3.1-Nemotron-70B-Instruct-HF": "together",
144
  "deepseek-chat": "deepseek",
145
+ "deepseek-reasoner": "deepseek",
146
  "qwen-max": "qwen",
147
  "qwen-max-latest": "qwen",
148
  "qwen-max-2025-01-25": "qwen",
 
152
  "qwen-turbo": "qwen",
153
  "qwen-turbo-latest": "qwen",
154
  "qwen-turbo-2024-11-01": "qwen",
155
+ "qwq-plus": "qwen",
156
  #"qwen2.5-14b-instruct-1m": "qwen",
157
  #"qwen2.5-7b-instruct-1m": "qwen",
158
  "qwen2.5-72b-instruct": "qwen",
 
175
  """Get default API key for specific provider"""
176
  return self.provider_api_keys.get(provider, "")
177
 
178
+ @dataclass
179
+ class PlainTextConfig:
180
+ """Configuration specific to Plain Text method (no visualization)"""
181
+ name: str = "Plain Text"
182
+ prompt_format: str = '''{question}'''
183
+ example_question: str = "Who are you?"
184
+
185
  @dataclass
186
  class ChainOfThoughtsConfig:
187
  """Configuration specific to Chain of Thoughts method"""
 
386
  "srf": SelfRefineConfig(),
387
  "l2m": LeastToMostConfig(),
388
  "bs": BeamSearchConfig(),
389
+ "plain": PlainTextConfig(),
390
  }
391
 
392
  def get_method_config(self, method_id: str) -> Optional[dict]:
cot_reasoning.py CHANGED
@@ -84,28 +84,20 @@ Note:
84
  """
85
 
86
  def wrap_text(text: str, config: VisualizationConfig) -> str:
87
- """
88
- Wrap text to fit within box constraints with proper line breaks.
89
-
90
- Args:
91
- text: The text to wrap
92
- config: VisualizationConfig containing formatting parameters
93
-
94
- Returns:
95
- Wrapped text with line breaks
96
- """
97
- # Clean the text first
98
  text = text.replace('\n', ' ').replace('"', "'")
99
-
100
- # Wrap the text into lines
101
  wrapped_lines = textwrap.wrap(text, width=config.max_chars_per_line)
102
 
103
- # Limit number of lines and add truncation if necessary
104
  if len(wrapped_lines) > config.max_lines:
105
- wrapped_lines = wrapped_lines[:config.max_lines-1]
106
- wrapped_lines.append(wrapped_lines[-1][:config.max_chars_per_line-3] + config.truncation_suffix)
 
 
 
 
 
 
107
 
108
- # Join with <br> for HTML line breaks in Mermaid
109
  return "<br>".join(wrapped_lines)
110
 
111
  def parse_cot_response(response_text: str, question: str) -> CoTResponse:
 
84
  """
85
 
86
  def wrap_text(text: str, config: VisualizationConfig) -> str:
87
+ """Wrap text to fit within box constraints"""
 
 
 
 
 
 
 
 
 
 
88
  text = text.replace('\n', ' ').replace('"', "'")
 
 
89
  wrapped_lines = textwrap.wrap(text, width=config.max_chars_per_line)
90
 
 
91
  if len(wrapped_lines) > config.max_lines:
92
+ # Option 1: Simply truncate and add ellipsis to the last line
93
+ wrapped_lines = wrapped_lines[:config.max_lines]
94
+ wrapped_lines[-1] = wrapped_lines[-1][:config.max_chars_per_line-3] + "..."
95
+
96
+ # Option 2 (alternative): Include part of the next line to show continuity
97
+ # original_next_line = wrapped_lines[config.max_lines] if len(wrapped_lines) > config.max_lines else ""
98
+ # wrapped_lines = wrapped_lines[:config.max_lines-1]
99
+ # wrapped_lines.append(original_next_line[:config.max_chars_per_line-3] + "...")
100
 
 
101
  return "<br>".join(wrapped_lines)
102
 
103
  def parse_cot_response(response_text: str, question: str) -> CoTResponse:
l2m_reasoning.py CHANGED
@@ -61,8 +61,8 @@ def wrap_text(text: str, max_chars: int = 40, max_lines: int = 4) -> str:
61
  wrapped_lines = textwrap.wrap(text, width=max_chars)
62
 
63
  if len(wrapped_lines) > max_lines:
64
- wrapped_lines = wrapped_lines[:max_lines-1]
65
- wrapped_lines.append(wrapped_lines[-1][:max_chars-3] + "...")
66
 
67
  return "<br>".join(wrapped_lines)
68
 
 
61
  wrapped_lines = textwrap.wrap(text, width=max_chars)
62
 
63
  if len(wrapped_lines) > max_lines:
64
+ wrapped_lines = wrapped_lines[:max_lines]
65
+ wrapped_lines[-1] = wrapped_lines[-1][:max_chars-3] + "..."
66
 
67
  return "<br>".join(wrapped_lines)
68
 
plain_text_reasoning.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Module for handling plain text reasoning without visualization.
3
+ This allows users to see the raw model output.
4
+ """
5
+
6
+ def parse_plain_text_response(response_text: str, question: str) -> str:
7
+ """
8
+ Simply pass through the raw model response without any parsing.
9
+
10
+ Args:
11
+ response_text: The raw response from the API
12
+ question: The original question
13
+
14
+ Returns:
15
+ The unmodified response text
16
+ """
17
+ return response_text
18
+
19
+ def create_mermaid_diagram(raw_text: str, config=None) -> None:
20
+ """
21
+ No diagram creation for plain text mode.
22
+
23
+ Args:
24
+ raw_text: The raw response text
25
+ config: Visualization configuration (not used)
26
+
27
+ Returns:
28
+ None as no visualization is needed
29
+ """
30
+ return None
templates/index.html CHANGED
@@ -135,6 +135,7 @@
135
  justify-content: center;
136
  gap: 0;
137
  white-space: nowrap;
 
138
  }
139
 
140
  .links a {
@@ -149,6 +150,11 @@
149
  opacity: 1;
150
  text-decoration: underline;
151
  }
 
 
 
 
 
152
 
153
  .container {
154
  display: flex;
@@ -296,6 +302,13 @@
296
  background-color: #e5e7eb;
297
  }
298
 
 
 
 
 
 
 
 
299
  .zoom-level {
300
  font-size: 14px;
301
  color: #374151;
@@ -324,6 +337,80 @@
324
  background-color: #f8f9fa;
325
  }
326
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  .mermaid {
328
  padding: 0;
329
  border-radius: 4px;
@@ -337,7 +424,7 @@
337
  <body>
338
  <div class="banner">
339
  <div class="banner-content">
340
- <h1>ReasonGraph: Visualisation of Reasoning Paths</h1>
341
  <div class="search-area">
342
  <div class="search-input-container">
343
  <textarea id="question" class="search-input" placeholder="Enter your question here..." rows="1"></textarea>
@@ -347,15 +434,24 @@
347
  <select class="param-input" id="reasoning-method">
348
  <!-- Populated dynamically -->
349
  </select>
350
- <button onclick="metaReasoning()" id="meta-btn">Meta Reasoning</button>
351
- <button onclick="processQuestion()" id="process-btn">Start Reasoning</button>
 
 
 
 
 
 
 
352
  </div>
353
  </div>
354
  <div class="links">
355
  <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>
356
- </div>
357
- <div class="links">
358
- <a href="./index.html"><u>English</u> |&nbsp</a><a href="./index_cn.html"><u>��文</u></a>
 
 
359
  </div>
360
  </div>
361
  </div>
@@ -422,7 +518,8 @@
422
  <div class="zoom-level" id="zoom-level">100%</div>
423
  <button class="zoom-button" onclick="adjustZoom(0.1)">+</button>
424
  <button class="zoom-button" onclick="resetZoom()">Reset</button>
425
- <button class="zoom-button" onclick="downloadDiagram()">Download</button>
 
426
  </div>
427
  <div class="visualization-wrapper">
428
  <div id="mermaid-container">
@@ -456,20 +553,42 @@
456
  // Initialize zoom lock flag
457
  window.isZoomLocked = false;
458
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  // Handle API Provider change
460
  async function handleProviderChange(provider) {
461
  try {
462
  // Update model list
463
  updateModelList();
464
 
465
- // Get and update API key
466
- const response = await fetch(`/provider-api-key/${provider}`);
467
- const result = await response.json();
468
-
469
- if (result.success) {
470
- document.getElementById('api-key').value = result.api_key;
471
  } else {
472
- console.error('Failed to get API key:', result.error);
 
 
 
 
 
 
 
 
473
  }
474
  } catch (error) {
475
  console.error('Error updating provider settings:', error);
@@ -513,6 +632,17 @@
513
  const methodConfig = currentConfig.methods[defaultMethod];
514
  updatePromptFormat(methodConfig.prompt_format);
515
  updateExampleQuestion(methodConfig.example_question);
 
 
 
 
 
 
 
 
 
 
 
516
  } catch (error) {
517
  console.error('Failed to load configuration:', error);
518
  showError('Failed to load configuration. Please refresh the page.');
@@ -648,6 +778,90 @@
648
  }
649
  }
650
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  function validateInputs() {
652
  const apiKey = document.getElementById('api-key').value.trim();
653
  const question = document.getElementById('question').value.trim();
@@ -689,11 +903,13 @@
689
  resetZoom();
690
  const processButton = document.getElementById('process-btn');
691
  const metaButton = document.getElementById('meta-btn');
 
692
  const rawOutput = document.getElementById('raw-output');
693
 
694
  processButton.disabled = true;
695
  metaButton.disabled = true;
696
- processButton.textContent = 'Processing...';
 
697
  rawOutput.textContent = 'Loading...';
698
  rawOutput.style.color = '#1f2937';
699
 
@@ -727,7 +943,18 @@
727
  rawOutput.textContent = result.raw_output;
728
  rawOutput.style.color = '#1f2937';
729
 
 
 
 
730
  if (result.visualization) {
 
 
 
 
 
 
 
 
731
  const container = document.getElementById('mermaid-diagram');
732
  container.innerHTML = result.visualization;
733
  document.getElementById('mermaid-container').classList.add('has-visualization');
@@ -745,13 +972,302 @@
745
 
746
  processButton.disabled = false;
747
  metaButton.disabled = false;
748
- processButton.textContent = 'Start Reasoning';
 
749
  if (isMetaReasoning) {
750
- metaButton.textContent = 'Meta Reasoning';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752
  }
753
  }
754
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
755
  // Meta Reasoning function
756
  async function metaReasoning() {
757
  const metaButton = document.getElementById('meta-btn');
@@ -759,7 +1275,7 @@
759
 
760
  try {
761
  metaButton.disabled = true;
762
- metaButton.textContent = 'Selecting Method...';
763
  rawOutput.textContent = 'Analyzing question to select best method...';
764
 
765
  // Get current parameters
@@ -802,22 +1318,34 @@
802
  console.log(`Selected reasoning method: ${methodConfig.name}`);
803
 
804
  // Update button to show method was selected
805
- metaButton.textContent = 'Method Selected';
806
- // Process the question with the selected method
807
- await processQuestion(true);
 
 
 
 
 
 
 
 
 
 
808
  } else {
809
  showError('Failed to load method configuration');
810
- metaButton.textContent = 'Meta Reasoning';
 
811
  }
812
  } else {
813
  showError(result.error || 'Failed to select method');
814
- metaButton.textContent = 'Meta Reasoning';
 
815
  }
816
  } catch (error) {
817
  console.error('Meta reasoning error:', error);
818
  showError('Failed to execute meta reasoning');
819
  metaButton.disabled = false;
820
- metaButton.textContent = 'Meta Reasoning';
821
  }
822
  }
823
 
@@ -838,6 +1366,87 @@
838
 
839
  // Load configuration when page loads
840
  document.addEventListener('DOMContentLoaded', loadConfig);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
841
  </script>
842
  </body>
843
  </html>
 
135
  justify-content: center;
136
  gap: 0;
137
  white-space: nowrap;
138
+ margin-top: 30px; /* Added space to move links lower */
139
  }
140
 
141
  .links a {
 
150
  opacity: 1;
151
  text-decoration: underline;
152
  }
153
+
154
+ .link-separator {
155
+ color: white;
156
+ opacity: 0.9;
157
+ }
158
 
159
  .container {
160
  display: flex;
 
302
  background-color: #e5e7eb;
303
  }
304
 
305
+ .zoom-button:disabled {
306
+ background-color: #9ca3af;
307
+ cursor: not-allowed;
308
+ color: white;
309
+ border-color: #9ca3af;
310
+ }
311
+
312
  .zoom-level {
313
  font-size: 14px;
314
  color: #374151;
 
337
  background-color: #f8f9fa;
338
  }
339
 
340
+ .tooltip {
341
+ position: relative;
342
+ display: inline-block;
343
+ }
344
+
345
+ .tooltip .tooltiptext {
346
+ visibility: hidden;
347
+ width: 220px;
348
+ background-color: #555;
349
+ color: #fff;
350
+ text-align: center;
351
+ border-radius: 6px;
352
+ padding: 5px;
353
+ position: absolute;
354
+ z-index: 1;
355
+ top: 125%;
356
+ left: 50%;
357
+ margin-left: -110px;
358
+ opacity: 0;
359
+ transition: opacity 0.1s;
360
+ }
361
+
362
+ .tooltip .tooltiptext::after {
363
+ content: "";
364
+ position: absolute;
365
+ bottom: 100%;
366
+ left: 50%;
367
+ margin-left: -5px;
368
+ border-width: 5px;
369
+ border-style: solid;
370
+ border-color: transparent transparent #555 transparent;
371
+ }
372
+
373
+ .tooltip:hover .tooltiptext {
374
+ visibility: visible;
375
+ opacity: 1;
376
+ }
377
+
378
+ /* New tooltip-top class for tooltips that appear above elements */
379
+ .tooltip-top .tooltiptext {
380
+ bottom: 125%; /* Position above instead of below */
381
+ top: auto; /* Override the default top value */
382
+ }
383
+
384
+ .tooltip-top .tooltiptext::after {
385
+ top: 100%; /* Arrow at the bottom rather than top */
386
+ bottom: auto; /* Override the default bottom value */
387
+ border-color: #555 transparent transparent transparent; /* Arrow pointing down */
388
+ }
389
+
390
+ .tooltip-wrap .tooltiptext {
391
+ width: 440px;
392
+ margin-left: -110px;
393
+ white-space: normal;
394
+ font-size: 14px;
395
+ }
396
+
397
+ .lang-tooltip .tooltiptext {
398
+ width: 440px;
399
+ margin-left: 0;
400
+ transform: translateX(-50%);
401
+ font-size: 14px;
402
+ white-space: normal;
403
+ font-weight: normal;
404
+ }
405
+
406
+ .english-tooltip .tooltiptext {
407
+ left: 40%;
408
+ }
409
+
410
+ .chinese-tooltip .tooltiptext {
411
+ left: 50%;
412
+ }
413
+
414
  .mermaid {
415
  padding: 0;
416
  border-radius: 4px;
 
424
  <body>
425
  <div class="banner">
426
  <div class="banner-content">
427
+ <h1>ReasonGraph</h1>
428
  <div class="search-area">
429
  <div class="search-input-container">
430
  <textarea id="question" class="search-input" placeholder="Enter your question here..." rows="1"></textarea>
 
434
  <select class="param-input" id="reasoning-method">
435
  <!-- Populated dynamically -->
436
  </select>
437
+ <button onclick="metaReasoning()" id="meta-btn" class="tooltip">Meta Reasoning
438
+ <span class="tooltiptext">Use reasoning method self-selected by the model</span>
439
+ </button>
440
+ <button onclick="processQuestion()" id="process-btn" class="tooltip">Start Reasoning
441
+ <span class="tooltiptext">Normal reasoning by selected method and model</span>
442
+ </button>
443
+ <button onclick="longReasoning()" id="long-btn" class="tooltip">Long Reasoning
444
+ <span class="tooltiptext">Only supported by deepseek-reasoner and qwq-plus models; Please insert Claude's API key for long reasoning visualization</span>
445
+ </button>
446
  </div>
447
  </div>
448
  <div class="links">
449
  <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>
450
+ <span class="link-separator"> |&nbsp|&nbsp</span><a href="./index.html" class="tooltip tooltip-top lang-tooltip english-tooltip"><u>English</u> /&nbsp
451
+ <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>
452
+ </a><a href="./index_cn.html" class="tooltip tooltip-top lang-tooltip chinese-tooltip"><u>中文</u>
453
+ <span class="tooltiptext">切换到中文版;如果想使模型输出中文,只需用中文提问,同时输入"请使用中文回答"</span>
454
+ </a>
455
  </div>
456
  </div>
457
  </div>
 
518
  <div class="zoom-level" id="zoom-level">100%</div>
519
  <button class="zoom-button" onclick="adjustZoom(0.1)">+</button>
520
  <button class="zoom-button" onclick="resetZoom()">Reset</button>
521
+ <button class="zoom-button" onclick="downloadDiagram()">Download Flow Chart</button>
522
+ <button class="zoom-button" onclick="downloadMermaidCode()">Download Code</button>
523
  </div>
524
  <div class="visualization-wrapper">
525
  <div id="mermaid-container">
 
553
  // Initialize zoom lock flag
554
  window.isZoomLocked = false;
555
 
556
+ // Save raw output for long reasoning
557
+ let currentRawOutput = "";
558
+
559
+ // Store user-entered API keys
560
+ let userApiKeys = {};
561
+
562
+ function updateButtonTextPreserveTooltip(button, text) {
563
+ for (let i = 0; i < button.childNodes.length; i++) {
564
+ if (button.childNodes[i].nodeType === Node.TEXT_NODE) {
565
+ button.childNodes[i].nodeValue = text;
566
+ return;
567
+ }
568
+ }
569
+
570
+ button.prepend(document.createTextNode(text));
571
+ }
572
+
573
  // Handle API Provider change
574
  async function handleProviderChange(provider) {
575
  try {
576
  // Update model list
577
  updateModelList();
578
 
579
+ // Check if we have a user-entered API key for this provider
580
+ if (userApiKeys[provider]) {
581
+ document.getElementById('api-key').value = userApiKeys[provider];
 
 
 
582
  } else {
583
+ // Get the default API key only if user hasn't entered one
584
+ const response = await fetch(`/provider-api-key/${provider}`);
585
+ const result = await response.json();
586
+
587
+ if (result.success) {
588
+ document.getElementById('api-key').value = result.api_key;
589
+ } else {
590
+ console.error('Failed to get API key:', result.error);
591
+ }
592
  }
593
  } catch (error) {
594
  console.error('Error updating provider settings:', error);
 
632
  const methodConfig = currentConfig.methods[defaultMethod];
633
  updatePromptFormat(methodConfig.prompt_format);
634
  updateExampleQuestion(methodConfig.example_question);
635
+
636
+ // Add event listener for API key changes
637
+ document.getElementById('api-key').addEventListener('change', function() {
638
+ const provider = document.getElementById('api-provider').value;
639
+ const apiKey = this.value.trim();
640
+
641
+ if (apiKey) {
642
+ // Save in local memory
643
+ userApiKeys[provider] = apiKey;
644
+ }
645
+ });
646
  } catch (error) {
647
  console.error('Failed to load configuration:', error);
648
  showError('Failed to load configuration. Please refresh the page.');
 
778
  }
779
  }
780
 
781
+ // Function to download the Mermaid code
782
+ async function downloadMermaidCode() {
783
+ // Do nothing if zooming is locked
784
+ if (window.isZoomLocked) return;
785
+
786
+ try {
787
+ // First, check if we have stored code from the last visualization
788
+ let mermaidCode = lastMermaidCode;
789
+
790
+ // If no stored code, try to extract it
791
+ if (!mermaidCode) {
792
+ const diagramContainer = document.getElementById('mermaid-diagram');
793
+ if (!diagramContainer) {
794
+ alert('No diagram available to download code');
795
+ return;
796
+ }
797
+
798
+ // Try to find the mermaid element with the code
799
+ const mermaidElement = diagramContainer.querySelector('.mermaid');
800
+
801
+ if (mermaidElement) {
802
+ // Get the text content which contains the Mermaid code
803
+ mermaidCode = mermaidElement.getAttribute('data-processed') === 'true'
804
+ ? mermaidElement.dataset.graph
805
+ : mermaidElement.textContent;
806
+ }
807
+
808
+ // Fallback: If we can't get the code directly, try to extract from raw output
809
+ if (!mermaidCode && currentRawOutput) {
810
+ mermaidCode = extractMermaidCode(currentRawOutput);
811
+ }
812
+
813
+ if (!mermaidCode) {
814
+ // Another fallback: try to extract from svg data
815
+ const svg = diagramContainer.querySelector('svg');
816
+ if (svg) {
817
+ const svgData = svg.outerHTML;
818
+ // Look for mermaid data embedded in the SVG
819
+ const match = svgData.match(/data-mermaid="(.*?)"/);
820
+ if (match && match[1]) {
821
+ mermaidCode = decodeURIComponent(match[1]);
822
+ }
823
+ }
824
+ }
825
+ }
826
+
827
+ if (!mermaidCode) {
828
+ // If we still don't have the code, try to generate a basic flowchart from the SVG
829
+ const diagramContainer = document.getElementById('mermaid-diagram');
830
+ const svg = diagramContainer && diagramContainer.querySelector('svg');
831
+
832
+ if (svg) {
833
+ // Try to reconstruct Mermaid code from SVG elements
834
+ mermaidCode = "flowchart TD\n";
835
+ mermaidCode += " A[\"This is an auto-generated approximation of the flowchart.\"]\n";
836
+ mermaidCode += " B[\"The original Mermaid code could not be extracted.\"]\n";
837
+ mermaidCode += " A --> B\n";
838
+ mermaidCode += " classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;";
839
+ } else {
840
+ alert('Unable to extract or generate Mermaid code');
841
+ return;
842
+ }
843
+ }
844
+
845
+ // Create blob and download link
846
+ const blob = new Blob([mermaidCode], {type: 'text/plain'});
847
+ const url = URL.createObjectURL(blob);
848
+
849
+ // Create temporary link and trigger download
850
+ const link = document.createElement('a');
851
+ link.href = url;
852
+ link.download = 'reasoning_diagram_code.txt';
853
+ document.body.appendChild(link);
854
+ link.click();
855
+
856
+ // Cleanup
857
+ document.body.removeChild(link);
858
+ URL.revokeObjectURL(url);
859
+ } catch (error) {
860
+ console.error('Error downloading Mermaid code:', error);
861
+ alert('Failed to download Mermaid code: ' + error.message);
862
+ }
863
+ }
864
+
865
  function validateInputs() {
866
  const apiKey = document.getElementById('api-key').value.trim();
867
  const question = document.getElementById('question').value.trim();
 
903
  resetZoom();
904
  const processButton = document.getElementById('process-btn');
905
  const metaButton = document.getElementById('meta-btn');
906
+ const longButton = document.getElementById('long-btn');
907
  const rawOutput = document.getElementById('raw-output');
908
 
909
  processButton.disabled = true;
910
  metaButton.disabled = true;
911
+ longButton.disabled = true;
912
+ updateButtonTextPreserveTooltip(processButton, 'Processing...');
913
  rawOutput.textContent = 'Loading...';
914
  rawOutput.style.color = '#1f2937';
915
 
 
943
  rawOutput.textContent = result.raw_output;
944
  rawOutput.style.color = '#1f2937';
945
 
946
+ // Save the raw output for potential long reasoning
947
+ currentRawOutput = result.raw_output;
948
+
949
  if (result.visualization) {
950
+ // Store the raw visualization code before rendering
951
+ if (result.visualization.includes('class="mermaid"')) {
952
+ const codeMatch = result.visualization.match(/<div class="mermaid">([\s\S]*?)<\/div>/);
953
+ if (codeMatch && codeMatch[1]) {
954
+ lastMermaidCode = codeMatch[1].trim();
955
+ }
956
+ }
957
+
958
  const container = document.getElementById('mermaid-diagram');
959
  container.innerHTML = result.visualization;
960
  document.getElementById('mermaid-container').classList.add('has-visualization');
 
972
 
973
  processButton.disabled = false;
974
  metaButton.disabled = false;
975
+ longButton.disabled = false;
976
+ updateButtonTextPreserveTooltip(processButton, 'Start Reasoning');
977
  if (isMetaReasoning) {
978
+ updateButtonTextPreserveTooltip(metaButton, 'Meta Reasoning');
979
+ }
980
+ }
981
+ }
982
+
983
+ // Long Reasoning function
984
+ async function longReasoning() {
985
+ if (!validateInputs()) {
986
+ return;
987
+ }
988
+
989
+ // Disable all buttons during processing
990
+ const processButton = document.getElementById('process-btn');
991
+ const metaButton = document.getElementById('meta-btn');
992
+ const longButton = document.getElementById('long-btn');
993
+ const rawOutput = document.getElementById('raw-output');
994
+
995
+ processButton.disabled = true;
996
+ metaButton.disabled = true;
997
+ longButton.disabled = true;
998
+ updateButtonTextPreserveTooltip(longButton, 'Processing...');
999
+ rawOutput.textContent = 'Switching to Plain Text mode and generating initial response...';
1000
+ rawOutput.style.color = '#1f2937';
1001
+
1002
+ // Lock visualization
1003
+ lockVisualization();
1004
+
1005
+ try {
1006
+ // 1. Switch to Plain Text reasoning method
1007
+ const methodSelect = document.getElementById('reasoning-method');
1008
+ const originalMethod = methodSelect.value; // Save original method
1009
+ methodSelect.value = 'plain';
1010
+
1011
+ // Get and update the prompt format for Plain Text
1012
+ const methodResponse = await fetch('/method-config/plain');
1013
+ const methodConfig = await methodResponse.json();
1014
+ if (methodConfig) {
1015
+ updatePromptFormat(methodConfig.prompt_format);
1016
  }
1017
+
1018
+ // 2. Process with Plain Text to get initial output
1019
+ const data = {
1020
+ provider: document.getElementById('api-provider').value,
1021
+ api_key: document.getElementById('api-key').value,
1022
+ model: document.getElementById('model').value,
1023
+ max_tokens: parseInt(document.getElementById('max-tokens').value),
1024
+ question: document.getElementById('question').value,
1025
+ prompt_format: document.getElementById('prompt-format').value,
1026
+ reasoning_method: 'plain',
1027
+ chars_per_line: parseInt(document.getElementById('chars-per-line').value),
1028
+ max_lines: parseInt(document.getElementById('max-lines').value)
1029
+ };
1030
+
1031
+ rawOutput.textContent = 'Generating initial output...';
1032
+
1033
+ // Call the process endpoint
1034
+ const response = await fetch('/process', {
1035
+ method: 'POST',
1036
+ headers: {
1037
+ 'Content-Type': 'application/json'
1038
+ },
1039
+ body: JSON.stringify(data)
1040
+ });
1041
+
1042
+ const result = await response.json();
1043
+
1044
+ if (result.success) {
1045
+ // Save the raw output for long reasoning
1046
+ currentRawOutput = result.raw_output;
1047
+
1048
+ // Display the plain text output in the Raw Output area
1049
+ rawOutput.textContent = result.raw_output;
1050
+ rawOutput.style.color = '#1f2937';
1051
+
1052
+ // Reset Zoom and visualization at the beginning - force zoom to 100%
1053
+ currentZoom = 1; // Force zoom level to 100%
1054
+ applyZoom(); // Apply the zoom reset
1055
+ const container = document.getElementById('mermaid-diagram');
1056
+ container.innerHTML = '';
1057
+ document.getElementById('zoom-level').textContent = '100%';
1058
+
1059
+ // 3. Now proceed with the long reasoning process using Anthropic
1060
+ const question = document.getElementById('question').value;
1061
+
1062
+ // Prepare the long reasoning prompt
1063
+ const longReasoningPrompt = `Please transform the following original reasoning process into a structured flowchart:
1064
+
1065
+ '''
1066
+ ${question}
1067
+ ${currentRawOutput}
1068
+ '''
1069
+
1070
+ Requirements:
1071
+ 0. Create a visually balanced and aesthetically pleasing diagram
1072
+
1073
+ 1. Structure each node as: [Step Name]: [Concise Summary of Reasoning]
1074
+ - Keep node text simple and enclose in double quotes inside brackets like: A["Step Name: Reasoning"]
1075
+ - Example: A["Problem Framing: Define the question"]
1076
+
1077
+ 2. Support multiple reasoning structures:
1078
+ - Linear reasoning: Sequential steps leading directly to a conclusion
1079
+ - Tree reasoning: Branching paths exploring multiple possibilities
1080
+ - Reflective reasoning: Loops that revisit and refine earlier conclusions
1081
+ - Not limited to the structures above
1082
+
1083
+ 3. Flowchart syntax guidance:
1084
+ - Start with: flowchart TD
1085
+ - Node definition: letter["text content"]
1086
+ - Connection definition: A --> B
1087
+ - Conditional branch: A -->|condition| B
1088
+
1089
+ 4. When analyzing the reasoning:
1090
+ - Preserve the logical structure of the original reasoning
1091
+ - Identify implicit steps that connect explicit reasoning
1092
+ - Highlight areas where reflection led to revised conclusions
1093
+ - Maintain proper causal relationships between nodes
1094
+ - When showing alternative methods or different solution attempts, use a tree-like structure with branches to clearly visualize parallel thinking paths
1095
+
1096
+ 5. Include these specific style classes in your Mermaid flowchart:
1097
+ - classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;
1098
+ - classDef question fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;
1099
+ - classDef answer fill:#d4edda,stroke:#28a745,stroke-width:2px;
1100
+ - classDef refinement fill:#fff3cd,stroke:#ffc107,stroke-width:2px;
1101
+ - classDef aha fill:#ffcccb,stroke:#d9534f,stroke-width:2px;
1102
+
1103
+ 6. Apply styling to specific node types:
1104
+ - The first node (starting point) should use class "question"
1105
+ - The final conclusion/answer node and any significant intermediate results should use class "answer"
1106
+ - Any sub-questions or question refinements should also use class "question"
1107
+ - 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.
1108
+ - Nodes that represent key insights, breakthrough moments, or "aha moments" should use class "aha"
1109
+
1110
+ 7. Important:
1111
+ - 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".
1112
+ - 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.
1113
+ - 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.
1114
+ - Try to capture all significant steps from the original text. Do not oversimplify or omit important reasoning steps even if the input is lengthy.
1115
+ - Do not use Python-style comments in the Mermaid code.
1116
+
1117
+ Please visualize the thinking steps from the original reasoning process as a proper Mermaid flowchart.`;
1118
+
1119
+ // Use Anthropic for the flowchart generation
1120
+ // Get the appropriate API key for Anthropic
1121
+ let anthropicApiKey = userApiKeys["anthropic"];
1122
+ if (!anthropicApiKey) {
1123
+ // If no user-entered key exists for Anthropic, try to get the default
1124
+ try {
1125
+ const keyResponse = await fetch('/provider-api-key/anthropic');
1126
+ const keyResult = await keyResponse.json();
1127
+ if (keyResult.success) {
1128
+ anthropicApiKey = keyResult.api_key;
1129
+ } else {
1130
+ throw new Error('No API key available for Anthropic');
1131
+ }
1132
+ } catch (error) {
1133
+ console.error('Error fetching Anthropic API key:', error);
1134
+ alert('Unable to get Anthropic API key for flowchart generation');
1135
+ return;
1136
+ }
1137
+ }
1138
+
1139
+ const longData = {
1140
+ provider: "anthropic",
1141
+ api_key: anthropicApiKey,
1142
+ model: "claude-3-7-sonnet-20250219", // Use Claude for visualization
1143
+ max_tokens: parseInt(document.getElementById('max-tokens').value),
1144
+ question: longReasoningPrompt,
1145
+ prompt_format: "",
1146
+ reasoning_method: "cot",
1147
+ chars_per_line: parseInt(document.getElementById('chars-per-line').value),
1148
+ max_lines: parseInt(document.getElementById('max-lines').value)
1149
+ };
1150
+
1151
+ // Keep showing the plain text result while generating the flowchart
1152
+ // Don't update rawOutput.textContent here
1153
+
1154
+ const longResponse = await fetch('/process', {
1155
+ method: 'POST',
1156
+ headers: {
1157
+ 'Content-Type': 'application/json'
1158
+ },
1159
+ body: JSON.stringify(longData)
1160
+ });
1161
+
1162
+ const longResult = await longResponse.json();
1163
+
1164
+ if (longResult.success) {
1165
+ // Keep displaying the original plain text output
1166
+ // We don't overwrite rawOutput.textContent here to preserve the plain text output
1167
+
1168
+ // Extract Mermaid diagram code from the response
1169
+ const mermaidCode = extractMermaidCode(longResult.raw_output);
1170
+
1171
+ if (mermaidCode) {
1172
+ // Store the code before rendering
1173
+ lastMermaidCode = mermaidCode;
1174
+
1175
+ // Render the Mermaid diagram
1176
+ const container = document.getElementById('mermaid-diagram');
1177
+ container.innerHTML = '<div class="mermaid">' + mermaidCode + '</div>';
1178
+
1179
+ // Initialize Mermaid
1180
+ mermaid.initialize({
1181
+ startOnLoad: true,
1182
+ theme: 'default',
1183
+ securityLevel: 'loose',
1184
+ flowchart: {
1185
+ curve: 'basis',
1186
+ padding: 15
1187
+ }
1188
+ });
1189
+
1190
+ // Force rendering
1191
+ mermaid.init(undefined, '.mermaid');
1192
+
1193
+ // Add visualization class
1194
+ document.getElementById('mermaid-container').classList.add('has-visualization');
1195
+
1196
+ // Reset zoom
1197
+ resetZoom();
1198
+ } else if (longResult.visualization) {
1199
+ // Fall back to any visualization
1200
+ const container = document.getElementById('mermaid-diagram');
1201
+ container.innerHTML = longResult.visualization;
1202
+ document.getElementById('mermaid-container').classList.add('has-visualization');
1203
+ resetZoom();
1204
+ mermaid.init();
1205
+ }
1206
+ } else {
1207
+ // If visualization fails, show error without overwriting raw output
1208
+ const errorMsg = longResult.error || 'Unknown error during flowchart generation';
1209
+ console.error(errorMsg);
1210
+ alert('Visualization error: ' + errorMsg);
1211
+ }
1212
+ } else {
1213
+ // Use standard error function since we don't have valid output to preserve
1214
+ showError(result.error || 'Unknown error during initial processing');
1215
+ }
1216
+
1217
+ // Don't restore the original method - keep it on Plain Text
1218
+ // This way the user stays in Plain Text mode after the operation
1219
+
1220
+ // Optional: Update the prompt format for Plain Text if needed
1221
+ const plainTextResponse = await fetch('/method-config/plain');
1222
+ const plainTextConfig = await plainTextResponse.json();
1223
+ if (plainTextConfig) {
1224
+ updatePromptFormat(plainTextConfig.prompt_format);
1225
+ }
1226
+
1227
+ } catch (error) {
1228
+ console.error('Long reasoning error:', error);
1229
+ // Only use showError if we don't already have plain text output to preserve
1230
+ if (!currentRawOutput) {
1231
+ showError('Failed to process long reasoning request: ' + error.message);
1232
+ } else {
1233
+ alert('Error in visualization: ' + error.message);
1234
+ }
1235
+ } finally {
1236
+ // Unlock visualization
1237
+ unlockVisualization();
1238
+
1239
+ // Re-enable all buttons
1240
+ processButton.disabled = false;
1241
+ metaButton.disabled = false;
1242
+ longButton.disabled = false;
1243
+ updateButtonTextPreserveTooltip(longButton, 'Long Reasoning');
1244
  }
1245
  }
1246
 
1247
+ // Helper function to extract Mermaid code from response
1248
+ function extractMermaidCode(text) {
1249
+ // Look for code blocks with Mermaid content
1250
+ const mermaidRegex = /```(?:mermaid)?\s*(flowchart[\s\S]*?)```/i;
1251
+ const match = text.match(mermaidRegex);
1252
+
1253
+ if (match && match[1]) {
1254
+ return match[1].trim();
1255
+ }
1256
+
1257
+ // If no code block, look for just the flowchart content
1258
+ const flowchartRegex = /(flowchart[\s\S]*?)(?:\n\n|$)/i;
1259
+ const flowchartMatch = text.match(flowchartRegex);
1260
+
1261
+ if (flowchartMatch && flowchartMatch[1]) {
1262
+ return flowchartMatch[1].trim();
1263
+ }
1264
+
1265
+ return null;
1266
+ }
1267
+
1268
+ // Global variable to store the last mermaid visualization code
1269
+ let lastMermaidCode = "";
1270
+
1271
  // Meta Reasoning function
1272
  async function metaReasoning() {
1273
  const metaButton = document.getElementById('meta-btn');
 
1275
 
1276
  try {
1277
  metaButton.disabled = true;
1278
+ updateButtonTextPreserveTooltip(metaButton, 'Selecting Method...');
1279
  rawOutput.textContent = 'Analyzing question to select best method...';
1280
 
1281
  // Get current parameters
 
1318
  console.log(`Selected reasoning method: ${methodConfig.name}`);
1319
 
1320
  // Update button to show method was selected
1321
+ updateButtonTextPreserveTooltip(metaButton, 'Method Selected');
1322
+
1323
+ try {
1324
+ // Process the question with the selected method
1325
+ await processQuestion(true);
1326
+ } catch (processError) {
1327
+ console.error('Error processing with selected method:', processError);
1328
+ showError('Failed to process with selected method: ' + processError.message);
1329
+
1330
+ // Make sure to re-enable the button on error
1331
+ metaButton.disabled = false;
1332
+ updateButtonTextPreserveTooltip(metaButton, 'Meta Reasoning');
1333
+ }
1334
  } else {
1335
  showError('Failed to load method configuration');
1336
+ metaButton.disabled = false;
1337
+ updateButtonTextPreserveTooltip(metaButton, 'Meta Reasoning');
1338
  }
1339
  } else {
1340
  showError(result.error || 'Failed to select method');
1341
+ metaButton.disabled = false;
1342
+ updateButtonTextPreserveTooltip(metaButton, 'Meta Reasoning');
1343
  }
1344
  } catch (error) {
1345
  console.error('Meta reasoning error:', error);
1346
  showError('Failed to execute meta reasoning');
1347
  metaButton.disabled = false;
1348
+ updateButtonTextPreserveTooltip(metaButton, 'Meta Reasoning');
1349
  }
1350
  }
1351
 
 
1366
 
1367
  // Load configuration when page loads
1368
  document.addEventListener('DOMContentLoaded', loadConfig);
1369
+
1370
+ /**
1371
+ * Process Mermaid node text with character and line limits
1372
+ * @param {string} mermaidCode - Original Mermaid code
1373
+ * @param {number} maxCharsPerLine - Maximum characters per line
1374
+ * @param {number} maxLines - Maximum number of lines per node
1375
+ * @param {string} truncationSuffix - Suffix for truncated text
1376
+ * @returns {string} Processed Mermaid code
1377
+ */
1378
+ function processMermaidNodeText(mermaidCode, maxCharsPerLine, maxLines, truncationSuffix = "...") {
1379
+ // Node definition regex: matches patterns like A["text content"] with capture groups
1380
+ const nodeRegex = /([A-Za-z0-9_]+)\s*\[\s*"([^"]+)"\s*\]/g;
1381
+
1382
+ // Process each node's text content
1383
+ return mermaidCode.replace(nodeRegex, (match, nodeId, text) => {
1384
+ // Clean text by replacing existing <br> tags and newlines with spaces
1385
+ const cleanText = text.replace(/<br>/g, ' ').replace(/\n/g, ' ');
1386
+
1387
+ // Text wrapping function similar to Python's textwrap.wrap
1388
+ function wrapText(text, width) {
1389
+ const words = text.split(' ');
1390
+ const lines = [];
1391
+ let currentLine = '';
1392
+
1393
+ for (const word of words) {
1394
+ if (currentLine.length + word.length + (currentLine ? 1 : 0) <= width) {
1395
+ currentLine += (currentLine ? ' ' : '') + word;
1396
+ } else {
1397
+ lines.push(currentLine);
1398
+ currentLine = word;
1399
+ }
1400
+ }
1401
+
1402
+ if (currentLine) {
1403
+ lines.push(currentLine);
1404
+ }
1405
+
1406
+ return lines;
1407
+ }
1408
+
1409
+ // Wrap text according to max chars per line
1410
+ let wrappedLines = wrapText(cleanText, maxCharsPerLine);
1411
+
1412
+ // Limit number of lines and add truncation indicator if needed
1413
+ if (wrappedLines.length > maxLines) {
1414
+ wrappedLines = wrappedLines.slice(0, maxLines);
1415
+ const lastLine = wrappedLines[wrappedLines.length - 1];
1416
+ if (lastLine.length > maxCharsPerLine - truncationSuffix.length) {
1417
+ wrappedLines[wrappedLines.length - 1] =
1418
+ lastLine.substring(0, maxCharsPerLine - truncationSuffix.length) + truncationSuffix;
1419
+ } else {
1420
+ wrappedLines[wrappedLines.length - 1] = lastLine + truncationSuffix;
1421
+ }
1422
+ }
1423
+
1424
+ // Join lines with <br> for Mermaid formatting
1425
+ const processedText = wrappedLines.join('<br>');
1426
+
1427
+ // Return the node with processed text
1428
+ return `${nodeId}["${processedText}"]`;
1429
+ });
1430
+ }
1431
+
1432
+ // Override extractMermaidCode to apply text processing for Long Reasoning
1433
+ const originalExtractMermaidCode = extractMermaidCode;
1434
+
1435
+ extractMermaidCode = function(text) {
1436
+ // Call the original function to extract the code
1437
+ const mermaidCode = originalExtractMermaidCode(text);
1438
+
1439
+ if (mermaidCode) {
1440
+ // Get character and line limits from UI
1441
+ const maxCharsPerLine = parseInt(document.getElementById('chars-per-line').value) || 40;
1442
+ const maxLines = parseInt(document.getElementById('max-lines').value) || 4;
1443
+
1444
+ // Apply text processing to enforce character and line limits
1445
+ return processMermaidNodeText(mermaidCode, maxCharsPerLine, maxLines);
1446
+ }
1447
+
1448
+ return mermaidCode;
1449
+ };
1450
  </script>
1451
  </body>
1452
  </html>
templates/index_cn.html CHANGED
@@ -135,6 +135,7 @@
135
  justify-content: center;
136
  gap: 0;
137
  white-space: nowrap;
 
138
  }
139
 
140
  .links a {
@@ -149,6 +150,11 @@
149
  opacity: 1;
150
  text-decoration: underline;
151
  }
 
 
 
 
 
152
 
153
  .container {
154
  display: flex;
@@ -296,6 +302,13 @@
296
  background-color: #e5e7eb;
297
  }
298
 
 
 
 
 
 
 
 
299
  .zoom-level {
300
  font-size: 14px;
301
  color: #374151;
@@ -324,6 +337,80 @@
324
  background-color: #f8f9fa;
325
  }
326
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  .mermaid {
328
  padding: 0;
329
  border-radius: 4px;
@@ -337,7 +424,7 @@
337
  <body>
338
  <div class="banner">
339
  <div class="banner-content">
340
- <h1>ReasonGraph: Visualisation of Reasoning Paths</h1>
341
  <div class="search-area">
342
  <div class="search-input-container">
343
  <textarea id="question" class="search-input" placeholder="在此输入您的问题..." rows="1"></textarea>
@@ -347,15 +434,24 @@
347
  <select class="param-input" id="reasoning-method">
348
  <!-- Populated dynamically -->
349
  </select>
350
- <button onclick="metaReasoning()" id="meta-btn">元推理</button>
351
- <button onclick="processQuestion()" id="process-btn">开始推理</button>
 
 
 
 
 
 
 
352
  </div>
353
  </div>
354
  <div class="links">
355
- <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>邮箱</u></a>
356
- </div>
357
- <div class="links">
358
- <a href="./index.html"><u>English</u> |&nbsp</a><a href="./index_cn.html"><u>中文</u></a>
 
 
359
  </div>
360
  </div>
361
  </div>
@@ -386,7 +482,7 @@
386
  <div class="param-group">
387
  <div class="param-label">API Key</div>
388
  <input type="password" class="param-input" id="api-key">
389
- <div class="error-message" id="api-key-error">请输入有效的API密钥</div>
390
  </div>
391
 
392
  <div class="param-group">
@@ -395,7 +491,7 @@
395
  </div>
396
 
397
  <div class="output-section">
398
- <h3>模型原始输出</h3>
399
  <div class="output-wrapper">
400
  <pre id="raw-output">输出将显示在这里...</pre>
401
  </div>
@@ -422,7 +518,8 @@
422
  <div class="zoom-level" id="zoom-level">100%</div>
423
  <button class="zoom-button" onclick="adjustZoom(0.1)">+</button>
424
  <button class="zoom-button" onclick="resetZoom()">重置</button>
425
- <button class="zoom-button" onclick="downloadDiagram()">下载</button>
 
426
  </div>
427
  <div class="visualization-wrapper">
428
  <div id="mermaid-container">
@@ -456,20 +553,42 @@
456
  // Initialize zoom lock flag
457
  window.isZoomLocked = false;
458
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  // Handle API Provider change
460
  async function handleProviderChange(provider) {
461
  try {
462
  // Update model list
463
  updateModelList();
464
 
465
- // Get and update API key
466
- const response = await fetch(`/provider-api-key/${provider}`);
467
- const result = await response.json();
468
-
469
- if (result.success) {
470
- document.getElementById('api-key').value = result.api_key;
471
  } else {
472
- console.error('Failed to get API key:', result.error);
 
 
 
 
 
 
 
 
473
  }
474
  } catch (error) {
475
  console.error('Error updating provider settings:', error);
@@ -513,9 +632,20 @@
513
  const methodConfig = currentConfig.methods[defaultMethod];
514
  updatePromptFormat(methodConfig.prompt_format);
515
  updateExampleQuestion(methodConfig.example_question);
 
 
 
 
 
 
 
 
 
 
 
516
  } catch (error) {
517
  console.error('Failed to load configuration:', error);
518
- showError('Failed to load configuration. Please refresh the page.');
519
  }
520
  }
521
 
@@ -547,7 +677,7 @@
547
  updateExampleQuestion(methodConfig.example_question);
548
  } catch (error) {
549
  console.error('Failed to load method configuration:', error);
550
- showError('Failed to update method configuration.');
551
  }
552
  });
553
 
@@ -607,7 +737,7 @@
607
 
608
  const diagramContainer = document.getElementById('mermaid-diagram');
609
  if (!diagramContainer || !diagramContainer.querySelector('svg')) {
610
- alert('没有可供下载的图表');
611
  return;
612
  }
613
 
@@ -648,6 +778,90 @@
648
  }
649
  }
650
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  function validateInputs() {
652
  const apiKey = document.getElementById('api-key').value.trim();
653
  const question = document.getElementById('question').value.trim();
@@ -689,11 +903,13 @@
689
  resetZoom();
690
  const processButton = document.getElementById('process-btn');
691
  const metaButton = document.getElementById('meta-btn');
 
692
  const rawOutput = document.getElementById('raw-output');
693
 
694
  processButton.disabled = true;
695
  metaButton.disabled = true;
696
- processButton.textContent = '处理中...';
 
697
  rawOutput.textContent = '加载中...';
698
  rawOutput.style.color = '#1f2937';
699
 
@@ -727,7 +943,18 @@
727
  rawOutput.textContent = result.raw_output;
728
  rawOutput.style.color = '#1f2937';
729
 
 
 
 
730
  if (result.visualization) {
 
 
 
 
 
 
 
 
731
  const container = document.getElementById('mermaid-diagram');
732
  container.innerHTML = result.visualization;
733
  document.getElementById('mermaid-container').classList.add('has-visualization');
@@ -745,13 +972,302 @@
745
 
746
  processButton.disabled = false;
747
  metaButton.disabled = false;
748
- processButton.textContent = '开始推理';
 
749
  if (isMetaReasoning) {
750
- metaButton.textContent = '元推理';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
752
  }
753
  }
754
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
755
  // Meta Reasoning function
756
  async function metaReasoning() {
757
  const metaButton = document.getElementById('meta-btn');
@@ -759,8 +1275,8 @@
759
 
760
  try {
761
  metaButton.disabled = true;
762
- metaButton.textContent = '选择方法中...';
763
- rawOutput.textContent = '正在分析问题以选择最佳方法...';
764
 
765
  // Get current parameters
766
  const data = {
@@ -799,25 +1315,37 @@
799
  updateExampleQuestion(methodConfig.example_question);
800
  }
801
 
802
- console.log(`Selected reasoning method: ${methodConfig.name}`);
803
 
804
  // Update button to show method was selected
805
- metaButton.textContent = '方法已选择';
806
- // Process the question with the selected method
807
- await processQuestion(true);
 
 
 
 
 
 
 
 
 
 
808
  } else {
809
  showError('加载方法配置失败');
810
- metaButton.textContent = '元推理';
 
811
  }
812
  } else {
813
  showError(result.error || '选择方法失败');
814
- metaButton.textContent = '元推理';
 
815
  }
816
  } catch (error) {
817
  console.error('Meta reasoning error:', error);
818
  showError('执行元推理失败');
819
  metaButton.disabled = false;
820
- metaButton.textContent = '元推理';
821
  }
822
  }
823
 
@@ -838,6 +1366,87 @@
838
 
839
  // Load configuration when page loads
840
  document.addEventListener('DOMContentLoaded', loadConfig);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
841
  </script>
842
  </body>
843
  </html>
 
135
  justify-content: center;
136
  gap: 0;
137
  white-space: nowrap;
138
+ margin-top: 30px; /* Added space to move links lower */
139
  }
140
 
141
  .links a {
 
150
  opacity: 1;
151
  text-decoration: underline;
152
  }
153
+
154
+ .link-separator {
155
+ color: white;
156
+ opacity: 0.9;
157
+ }
158
 
159
  .container {
160
  display: flex;
 
302
  background-color: #e5e7eb;
303
  }
304
 
305
+ .zoom-button:disabled {
306
+ background-color: #9ca3af;
307
+ cursor: not-allowed;
308
+ color: white;
309
+ border-color: #9ca3af;
310
+ }
311
+
312
  .zoom-level {
313
  font-size: 14px;
314
  color: #374151;
 
337
  background-color: #f8f9fa;
338
  }
339
 
340
+ .tooltip {
341
+ position: relative;
342
+ display: inline-block;
343
+ }
344
+
345
+ .tooltip .tooltiptext {
346
+ visibility: hidden;
347
+ width: 220px;
348
+ background-color: #555;
349
+ color: #fff;
350
+ text-align: center;
351
+ border-radius: 6px;
352
+ padding: 5px;
353
+ position: absolute;
354
+ z-index: 1;
355
+ top: 125%;
356
+ left: 50%;
357
+ margin-left: -110px;
358
+ opacity: 0;
359
+ transition: opacity 0.1s;
360
+ }
361
+
362
+ .tooltip .tooltiptext::after {
363
+ content: "";
364
+ position: absolute;
365
+ bottom: 100%;
366
+ left: 50%;
367
+ margin-left: -5px;
368
+ border-width: 5px;
369
+ border-style: solid;
370
+ border-color: transparent transparent #555 transparent;
371
+ }
372
+
373
+ .tooltip:hover .tooltiptext {
374
+ visibility: visible;
375
+ opacity: 1;
376
+ }
377
+
378
+ /* New tooltip-top class for tooltips that appear above elements */
379
+ .tooltip-top .tooltiptext {
380
+ bottom: 125%; /* Position above instead of below */
381
+ top: auto; /* Override the default top value */
382
+ }
383
+
384
+ .tooltip-top .tooltiptext::after {
385
+ top: 100%; /* Arrow at the bottom rather than top */
386
+ bottom: auto; /* Override the default bottom value */
387
+ border-color: #555 transparent transparent transparent; /* Arrow pointing down */
388
+ }
389
+
390
+ .tooltip-wrap .tooltiptext {
391
+ width: 440px;
392
+ margin-left: -110px;
393
+ white-space: normal;
394
+ font-size: 14px;
395
+ }
396
+
397
+ .lang-tooltip .tooltiptext {
398
+ width: 440px;
399
+ margin-left: 0;
400
+ transform: translateX(-50%);
401
+ font-size: 14px;
402
+ white-space: normal;
403
+ font-weight: normal;
404
+ }
405
+
406
+ .english-tooltip .tooltiptext {
407
+ left: 40%;
408
+ }
409
+
410
+ .chinese-tooltip .tooltiptext {
411
+ left: 50%;
412
+ }
413
+
414
  .mermaid {
415
  padding: 0;
416
  border-radius: 4px;
 
424
  <body>
425
  <div class="banner">
426
  <div class="banner-content">
427
+ <h1>ReasonGraph</h1>
428
  <div class="search-area">
429
  <div class="search-input-container">
430
  <textarea id="question" class="search-input" placeholder="在此输入您的问题..." rows="1"></textarea>
 
434
  <select class="param-input" id="reasoning-method">
435
  <!-- Populated dynamically -->
436
  </select>
437
+ <button onclick="metaReasoning()" id="meta-btn" class="tooltip">元推理
438
+ <span class="tooltiptext">使用模型自行选择的推理方法</span>
439
+ </button>
440
+ <button onclick="processQuestion()" id="process-btn" class="tooltip">开始推理
441
+ <span class="tooltiptext">使用选定方法和模型进行推理</span>
442
+ </button>
443
+ <button onclick="longReasoning()" id="long-btn" class="tooltip">长推理
444
+ <span class="tooltiptext">仅由deepseek-reasoner和qwq-plus模型支持;请输入Claude的API Key以进行长推理可视化</span>
445
+ </button>
446
  </div>
447
  </div>
448
  <div class="links">
449
+ <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>
450
+ <span class="link-separator"> |&nbsp|&nbsp</span><a href="./index.html" class="tooltip tooltip-top lang-tooltip english-tooltip"><u>English</u> /&nbsp
451
+ <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>
452
+ </a><a href="./index_cn.html" class="tooltip tooltip-top lang-tooltip chinese-tooltip"><u>中文</u>
453
+ <span class="tooltiptext">切换到中文版;如果想使模型输出中文,只需用中文提问,同时输入"请使用中文回答"</span>
454
+ </a>
455
  </div>
456
  </div>
457
  </div>
 
482
  <div class="param-group">
483
  <div class="param-label">API Key</div>
484
  <input type="password" class="param-input" id="api-key">
485
+ <div class="error-message" id="api-key-error">请输入有效的API Key</div>
486
  </div>
487
 
488
  <div class="param-group">
 
491
  </div>
492
 
493
  <div class="output-section">
494
+ <h3>原始模型输出</h3>
495
  <div class="output-wrapper">
496
  <pre id="raw-output">输出将显示在这里...</pre>
497
  </div>
 
518
  <div class="zoom-level" id="zoom-level">100%</div>
519
  <button class="zoom-button" onclick="adjustZoom(0.1)">+</button>
520
  <button class="zoom-button" onclick="resetZoom()">重置</button>
521
+ <button class="zoom-button" onclick="downloadDiagram()">下载流程图</button>
522
+ <button class="zoom-button" onclick="downloadMermaidCode()">下载代码</button>
523
  </div>
524
  <div class="visualization-wrapper">
525
  <div id="mermaid-container">
 
553
  // Initialize zoom lock flag
554
  window.isZoomLocked = false;
555
 
556
+ // Save raw output for long reasoning
557
+ let currentRawOutput = "";
558
+
559
+ // Store user-entered API keys
560
+ let userApiKeys = {};
561
+
562
+ function updateButtonTextPreserveTooltip(button, text) {
563
+ for (let i = 0; i < button.childNodes.length; i++) {
564
+ if (button.childNodes[i].nodeType === Node.TEXT_NODE) {
565
+ button.childNodes[i].nodeValue = text;
566
+ return;
567
+ }
568
+ }
569
+
570
+ button.prepend(document.createTextNode(text));
571
+ }
572
+
573
  // Handle API Provider change
574
  async function handleProviderChange(provider) {
575
  try {
576
  // Update model list
577
  updateModelList();
578
 
579
+ // Check if we have a user-entered API key for this provider
580
+ if (userApiKeys[provider]) {
581
+ document.getElementById('api-key').value = userApiKeys[provider];
 
 
 
582
  } else {
583
+ // Get the default API key only if user hasn't entered one
584
+ const response = await fetch(`/provider-api-key/${provider}`);
585
+ const result = await response.json();
586
+
587
+ if (result.success) {
588
+ document.getElementById('api-key').value = result.api_key;
589
+ } else {
590
+ console.error('Failed to get API key:', result.error);
591
+ }
592
  }
593
  } catch (error) {
594
  console.error('Error updating provider settings:', error);
 
632
  const methodConfig = currentConfig.methods[defaultMethod];
633
  updatePromptFormat(methodConfig.prompt_format);
634
  updateExampleQuestion(methodConfig.example_question);
635
+
636
+ // Add event listener for API key changes
637
+ document.getElementById('api-key').addEventListener('change', function() {
638
+ const provider = document.getElementById('api-provider').value;
639
+ const apiKey = this.value.trim();
640
+
641
+ if (apiKey) {
642
+ // Save in local memory
643
+ userApiKeys[provider] = apiKey;
644
+ }
645
+ });
646
  } catch (error) {
647
  console.error('Failed to load configuration:', error);
648
+ showError('加载配置失败。请刷新页面。');
649
  }
650
  }
651
 
 
677
  updateExampleQuestion(methodConfig.example_question);
678
  } catch (error) {
679
  console.error('Failed to load method configuration:', error);
680
+ showError('更新方法配置失败。');
681
  }
682
  });
683
 
 
737
 
738
  const diagramContainer = document.getElementById('mermaid-diagram');
739
  if (!diagramContainer || !diagramContainer.querySelector('svg')) {
740
+ alert('没有可下载的图表');
741
  return;
742
  }
743
 
 
778
  }
779
  }
780
 
781
+ // Function to download the Mermaid code
782
+ async function downloadMermaidCode() {
783
+ // Do nothing if zooming is locked
784
+ if (window.isZoomLocked) return;
785
+
786
+ try {
787
+ // First, check if we have stored code from the last visualization
788
+ let mermaidCode = lastMermaidCode;
789
+
790
+ // If no stored code, try to extract it
791
+ if (!mermaidCode) {
792
+ const diagramContainer = document.getElementById('mermaid-diagram');
793
+ if (!diagramContainer) {
794
+ alert('没有可下载代码的图表');
795
+ return;
796
+ }
797
+
798
+ // Try to find the mermaid element with the code
799
+ const mermaidElement = diagramContainer.querySelector('.mermaid');
800
+
801
+ if (mermaidElement) {
802
+ // Get the text content which contains the Mermaid code
803
+ mermaidCode = mermaidElement.getAttribute('data-processed') === 'true'
804
+ ? mermaidElement.dataset.graph
805
+ : mermaidElement.textContent;
806
+ }
807
+
808
+ // Fallback: If we can't get the code directly, try to extract from raw output
809
+ if (!mermaidCode && currentRawOutput) {
810
+ mermaidCode = extractMermaidCode(currentRawOutput);
811
+ }
812
+
813
+ if (!mermaidCode) {
814
+ // Another fallback: try to extract from svg data
815
+ const svg = diagramContainer.querySelector('svg');
816
+ if (svg) {
817
+ const svgData = svg.outerHTML;
818
+ // Look for mermaid data embedded in the SVG
819
+ const match = svgData.match(/data-mermaid="(.*?)"/);
820
+ if (match && match[1]) {
821
+ mermaidCode = decodeURIComponent(match[1]);
822
+ }
823
+ }
824
+ }
825
+ }
826
+
827
+ if (!mermaidCode) {
828
+ // If we still don't have the code, try to generate a basic flowchart from the SVG
829
+ const diagramContainer = document.getElementById('mermaid-diagram');
830
+ const svg = diagramContainer && diagramContainer.querySelector('svg');
831
+
832
+ if (svg) {
833
+ // Try to reconstruct Mermaid code from SVG elements
834
+ mermaidCode = "flowchart TD\n";
835
+ mermaidCode += " A[\"This is an auto-generated approximation of the flowchart.\"]\n";
836
+ mermaidCode += " B[\"The original Mermaid code could not be extracted.\"]\n";
837
+ mermaidCode += " A --> B\n";
838
+ mermaidCode += " classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;";
839
+ } else {
840
+ alert('Unable to extract or generate Mermaid code');
841
+ return;
842
+ }
843
+ }
844
+
845
+ // Create blob and download link
846
+ const blob = new Blob([mermaidCode], {type: 'text/plain'});
847
+ const url = URL.createObjectURL(blob);
848
+
849
+ // Create temporary link and trigger download
850
+ const link = document.createElement('a');
851
+ link.href = url;
852
+ link.download = 'reasoning_diagram_code.txt';
853
+ document.body.appendChild(link);
854
+ link.click();
855
+
856
+ // Cleanup
857
+ document.body.removeChild(link);
858
+ URL.revokeObjectURL(url);
859
+ } catch (error) {
860
+ console.error('Error downloading Mermaid code:', error);
861
+ alert('下载Mermaid代码失败: ' + error.message);
862
+ }
863
+ }
864
+
865
  function validateInputs() {
866
  const apiKey = document.getElementById('api-key').value.trim();
867
  const question = document.getElementById('question').value.trim();
 
903
  resetZoom();
904
  const processButton = document.getElementById('process-btn');
905
  const metaButton = document.getElementById('meta-btn');
906
+ const longButton = document.getElementById('long-btn');
907
  const rawOutput = document.getElementById('raw-output');
908
 
909
  processButton.disabled = true;
910
  metaButton.disabled = true;
911
+ longButton.disabled = true;
912
+ updateButtonTextPreserveTooltip(processButton, '处理中...');
913
  rawOutput.textContent = '加载中...';
914
  rawOutput.style.color = '#1f2937';
915
 
 
943
  rawOutput.textContent = result.raw_output;
944
  rawOutput.style.color = '#1f2937';
945
 
946
+ // Save the raw output for potential long reasoning
947
+ currentRawOutput = result.raw_output;
948
+
949
  if (result.visualization) {
950
+ // Store the raw visualization code before rendering
951
+ if (result.visualization.includes('class="mermaid"')) {
952
+ const codeMatch = result.visualization.match(/<div class="mermaid">([\s\S]*?)<\/div>/);
953
+ if (codeMatch && codeMatch[1]) {
954
+ lastMermaidCode = codeMatch[1].trim();
955
+ }
956
+ }
957
+
958
  const container = document.getElementById('mermaid-diagram');
959
  container.innerHTML = result.visualization;
960
  document.getElementById('mermaid-container').classList.add('has-visualization');
 
972
 
973
  processButton.disabled = false;
974
  metaButton.disabled = false;
975
+ longButton.disabled = false;
976
+ updateButtonTextPreserveTooltip(processButton, '开始推理');
977
  if (isMetaReasoning) {
978
+ updateButtonTextPreserveTooltip(metaButton, '元推理');
979
+ }
980
+ }
981
+ }
982
+
983
+ // Long Reasoning function
984
+ async function longReasoning() {
985
+ if (!validateInputs()) {
986
+ return;
987
+ }
988
+
989
+ // Disable all buttons during processing
990
+ const processButton = document.getElementById('process-btn');
991
+ const metaButton = document.getElementById('meta-btn');
992
+ const longButton = document.getElementById('long-btn');
993
+ const rawOutput = document.getElementById('raw-output');
994
+
995
+ processButton.disabled = true;
996
+ metaButton.disabled = true;
997
+ longButton.disabled = true;
998
+ updateButtonTextPreserveTooltip(longButton, '处理中...');
999
+ rawOutput.textContent = '切换到纯文本模式并生成初始输出...';
1000
+ rawOutput.style.color = '#1f2937';
1001
+
1002
+ // Lock visualization
1003
+ lockVisualization();
1004
+
1005
+ try {
1006
+ // 1. Switch to Plain Text reasoning method
1007
+ const methodSelect = document.getElementById('reasoning-method');
1008
+ const originalMethod = methodSelect.value; // Save original method
1009
+ methodSelect.value = 'plain';
1010
+
1011
+ // Get and update the prompt format for Plain Text
1012
+ const methodResponse = await fetch('/method-config/plain');
1013
+ const methodConfig = await methodResponse.json();
1014
+ if (methodConfig) {
1015
+ updatePromptFormat(methodConfig.prompt_format);
1016
  }
1017
+
1018
+ // 2. Process with Plain Text to get initial output
1019
+ const data = {
1020
+ provider: document.getElementById('api-provider').value,
1021
+ api_key: document.getElementById('api-key').value,
1022
+ model: document.getElementById('model').value,
1023
+ max_tokens: parseInt(document.getElementById('max-tokens').value),
1024
+ question: document.getElementById('question').value,
1025
+ prompt_format: document.getElementById('prompt-format').value,
1026
+ reasoning_method: 'plain',
1027
+ chars_per_line: parseInt(document.getElementById('chars-per-line').value),
1028
+ max_lines: parseInt(document.getElementById('max-lines').value)
1029
+ };
1030
+
1031
+ rawOutput.textContent = '生成初始输出...';
1032
+
1033
+ // Call the process endpoint
1034
+ const response = await fetch('/process', {
1035
+ method: 'POST',
1036
+ headers: {
1037
+ 'Content-Type': 'application/json'
1038
+ },
1039
+ body: JSON.stringify(data)
1040
+ });
1041
+
1042
+ const result = await response.json();
1043
+
1044
+ if (result.success) {
1045
+ // Save the raw output for long reasoning
1046
+ currentRawOutput = result.raw_output;
1047
+
1048
+ // Display the plain text output in the Raw Output area
1049
+ rawOutput.textContent = result.raw_output;
1050
+ rawOutput.style.color = '#1f2937';
1051
+
1052
+ // Reset Zoom and visualization at the beginning - force zoom to 100%
1053
+ currentZoom = 1; // Force zoom level to 100%
1054
+ applyZoom(); // Apply the zoom reset
1055
+ const container = document.getElementById('mermaid-diagram');
1056
+ container.innerHTML = '';
1057
+ document.getElementById('zoom-level').textContent = '100%';
1058
+
1059
+ // 3. Now proceed with the long reasoning process using Anthropic
1060
+ const question = document.getElementById('question').value;
1061
+
1062
+ // Prepare the long reasoning prompt
1063
+ const longReasoningPrompt = `Please transform the following original reasoning process into a structured flowchart:
1064
+
1065
+ '''
1066
+ ${question}
1067
+ ${currentRawOutput}
1068
+ '''
1069
+
1070
+ Requirements:
1071
+ 0. Create a visually balanced and aesthetically pleasing diagram
1072
+
1073
+ 1. Structure each node as: [Step Name]: [Concise Summary of Reasoning]
1074
+ - Keep node text simple and enclose in double quotes inside brackets like: A["Step Name: Reasoning"]
1075
+ - Example: A["Problem Framing: Define the question"]
1076
+
1077
+ 2. Support multiple reasoning structures:
1078
+ - Linear reasoning: Sequential steps leading directly to a conclusion
1079
+ - Tree reasoning: Branching paths exploring multiple possibilities
1080
+ - Reflective reasoning: Loops that revisit and refine earlier conclusions
1081
+ - Not limited to the structures above
1082
+
1083
+ 3. Flowchart syntax guidance:
1084
+ - Start with: flowchart TD
1085
+ - Node definition: letter["text content"]
1086
+ - Connection definition: A --> B
1087
+ - Conditional branch: A -->|condition| B
1088
+
1089
+ 4. When analyzing the reasoning:
1090
+ - Preserve the logical structure of the original reasoning
1091
+ - Identify implicit steps that connect explicit reasoning
1092
+ - Highlight areas where reflection led to revised conclusions
1093
+ - Maintain proper causal relationships between nodes
1094
+ - When showing alternative methods or different solution attempts, use a tree-like structure with branches to clearly visualize parallel thinking paths
1095
+
1096
+ 5. Include these specific style classes in your Mermaid flowchart:
1097
+ - classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;
1098
+ - classDef question fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;
1099
+ - classDef answer fill:#d4edda,stroke:#28a745,stroke-width:2px;
1100
+ - classDef refinement fill:#fff3cd,stroke:#ffc107,stroke-width:2px;
1101
+ - classDef aha fill:#ffcccb,stroke:#d9534f,stroke-width:2px;
1102
+
1103
+ 6. Apply styling to specific node types:
1104
+ - The first node (starting point) should use class "question"
1105
+ - The final conclusion/answer node and any significant intermediate results should use class "answer"
1106
+ - Any sub-questions or question refinements should also use class "question"
1107
+ - 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.
1108
+ - Nodes that represent key insights, breakthrough moments, or "aha moments" should use class "aha"
1109
+
1110
+ 7. Important:
1111
+ - 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".
1112
+ - 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.
1113
+ - 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.
1114
+ - Try to capture all significant steps from the original text. Do not oversimplify or omit important reasoning steps even if the input is lengthy.
1115
+ - Do not use Python-style comments in the Mermaid code.
1116
+
1117
+ Please visualize the thinking steps from the original reasoning process as a proper Mermaid flowchart.`;
1118
+
1119
+ // Use Anthropic for the flowchart generation
1120
+ // Get the appropriate API key for Anthropic
1121
+ let anthropicApiKey = userApiKeys["anthropic"];
1122
+ if (!anthropicApiKey) {
1123
+ // If no user-entered key exists for Anthropic, try to get the default
1124
+ try {
1125
+ const keyResponse = await fetch('/provider-api-key/anthropic');
1126
+ const keyResult = await keyResponse.json();
1127
+ if (keyResult.success) {
1128
+ anthropicApiKey = keyResult.api_key;
1129
+ } else {
1130
+ throw new Error('无法获取Anthropic的API Key');
1131
+ }
1132
+ } catch (error) {
1133
+ console.error('Error fetching Anthropic API key:', error);
1134
+ alert('无法获取Anthropic的API Key用于流程图生成');
1135
+ return;
1136
+ }
1137
+ }
1138
+
1139
+ const longData = {
1140
+ provider: "anthropic",
1141
+ api_key: anthropicApiKey,
1142
+ model: "claude-3-7-sonnet-20250219", // Use Claude for visualization
1143
+ max_tokens: parseInt(document.getElementById('max-tokens').value),
1144
+ question: longReasoningPrompt,
1145
+ prompt_format: "",
1146
+ reasoning_method: "cot",
1147
+ chars_per_line: parseInt(document.getElementById('chars-per-line').value),
1148
+ max_lines: parseInt(document.getElementById('max-lines').value)
1149
+ };
1150
+
1151
+ // Keep showing the plain text result while generating the flowchart
1152
+ // Don't update rawOutput.textContent here
1153
+
1154
+ const longResponse = await fetch('/process', {
1155
+ method: 'POST',
1156
+ headers: {
1157
+ 'Content-Type': 'application/json'
1158
+ },
1159
+ body: JSON.stringify(longData)
1160
+ });
1161
+
1162
+ const longResult = await longResponse.json();
1163
+
1164
+ if (longResult.success) {
1165
+ // Keep displaying the original plain text output
1166
+ // We don't overwrite rawOutput.textContent here to preserve the plain text output
1167
+
1168
+ // Extract Mermaid diagram code from the response
1169
+ const mermaidCode = extractMermaidCode(longResult.raw_output);
1170
+
1171
+ if (mermaidCode) {
1172
+ // Store the code before rendering
1173
+ lastMermaidCode = mermaidCode;
1174
+
1175
+ // Render the Mermaid diagram
1176
+ const container = document.getElementById('mermaid-diagram');
1177
+ container.innerHTML = '<div class="mermaid">' + mermaidCode + '</div>';
1178
+
1179
+ // Initialize Mermaid
1180
+ mermaid.initialize({
1181
+ startOnLoad: true,
1182
+ theme: 'default',
1183
+ securityLevel: 'loose',
1184
+ flowchart: {
1185
+ curve: 'basis',
1186
+ padding: 15
1187
+ }
1188
+ });
1189
+
1190
+ // Force rendering
1191
+ mermaid.init(undefined, '.mermaid');
1192
+
1193
+ // Add visualization class
1194
+ document.getElementById('mermaid-container').classList.add('has-visualization');
1195
+
1196
+ // Reset zoom
1197
+ resetZoom();
1198
+ } else if (longResult.visualization) {
1199
+ // Fall back to any visualization
1200
+ const container = document.getElementById('mermaid-diagram');
1201
+ container.innerHTML = longResult.visualization;
1202
+ document.getElementById('mermaid-container').classList.add('has-visualization');
1203
+ resetZoom();
1204
+ mermaid.init();
1205
+ }
1206
+ } else {
1207
+ // If visualization fails, show error without overwriting raw output
1208
+ const errorMsg = longResult.error || '流程图生成过程中发生未知错误';
1209
+ console.error(errorMsg);
1210
+ alert('可视化错误: ' + errorMsg);
1211
+ }
1212
+ } else {
1213
+ // Use standard error function since we don't have valid output to preserve
1214
+ showError(result.error || '初始处理过程中发生未知错误');
1215
+ }
1216
+
1217
+ // Don't restore the original method - keep it on Plain Text
1218
+ // This way the user stays in Plain Text mode after the operation
1219
+
1220
+ // Optional: Update the prompt format for Plain Text if needed
1221
+ const plainTextResponse = await fetch('/method-config/plain');
1222
+ const plainTextConfig = await plainTextResponse.json();
1223
+ if (plainTextConfig) {
1224
+ updatePromptFormat(plainTextConfig.prompt_format);
1225
+ }
1226
+
1227
+ } catch (error) {
1228
+ console.error('Long reasoning error:', error);
1229
+ // Only use showError if we don't already have plain text output to preserve
1230
+ if (!currentRawOutput) {
1231
+ showError('处理长推理请求失败: ' + error.message);
1232
+ } else {
1233
+ alert('可视化错误: ' + error.message);
1234
+ }
1235
+ } finally {
1236
+ // Unlock visualization
1237
+ unlockVisualization();
1238
+
1239
+ // Re-enable all buttons
1240
+ processButton.disabled = false;
1241
+ metaButton.disabled = false;
1242
+ longButton.disabled = false;
1243
+ updateButtonTextPreserveTooltip(longButton, '长推理');
1244
  }
1245
  }
1246
 
1247
+ // Helper function to extract Mermaid code from response
1248
+ function extractMermaidCode(text) {
1249
+ // Look for code blocks with Mermaid content
1250
+ const mermaidRegex = /```(?:mermaid)?\s*(flowchart[\s\S]*?)```/i;
1251
+ const match = text.match(mermaidRegex);
1252
+
1253
+ if (match && match[1]) {
1254
+ return match[1].trim();
1255
+ }
1256
+
1257
+ // If no code block, look for just the flowchart content
1258
+ const flowchartRegex = /(flowchart[\s\S]*?)(?:\n\n|$)/i;
1259
+ const flowchartMatch = text.match(flowchartRegex);
1260
+
1261
+ if (flowchartMatch && flowchartMatch[1]) {
1262
+ return flowchartMatch[1].trim();
1263
+ }
1264
+
1265
+ return null;
1266
+ }
1267
+
1268
+ // Global variable to store the last mermaid visualization code
1269
+ let lastMermaidCode = "";
1270
+
1271
  // Meta Reasoning function
1272
  async function metaReasoning() {
1273
  const metaButton = document.getElementById('meta-btn');
 
1275
 
1276
  try {
1277
  metaButton.disabled = true;
1278
+ updateButtonTextPreserveTooltip(metaButton, '选择方法中...');
1279
+ rawOutput.textContent = '分析问题以选择最佳方法...';
1280
 
1281
  // Get current parameters
1282
  const data = {
 
1315
  updateExampleQuestion(methodConfig.example_question);
1316
  }
1317
 
1318
+ console.log(`选择的推理方法: ${methodConfig.name}`);
1319
 
1320
  // Update button to show method was selected
1321
+ updateButtonTextPreserveTooltip(metaButton, '已选择方法');
1322
+
1323
+ try {
1324
+ // Process the question with the selected method
1325
+ await processQuestion(true);
1326
+ } catch (processError) {
1327
+ console.error('Error processing with selected method:', processError);
1328
+ showError('使用所选方法处理失败: ' + processError.message);
1329
+
1330
+ // Make sure to re-enable the button on error
1331
+ metaButton.disabled = false;
1332
+ updateButtonTextPreserveTooltip(metaButton, '元推理');
1333
+ }
1334
  } else {
1335
  showError('加载方法配置失败');
1336
+ metaButton.disabled = false;
1337
+ updateButtonTextPreserveTooltip(metaButton, '元推理');
1338
  }
1339
  } else {
1340
  showError(result.error || '选择方法失败');
1341
+ metaButton.disabled = false;
1342
+ updateButtonTextPreserveTooltip(metaButton, '元推理');
1343
  }
1344
  } catch (error) {
1345
  console.error('Meta reasoning error:', error);
1346
  showError('执行元推理失败');
1347
  metaButton.disabled = false;
1348
+ updateButtonTextPreserveTooltip(metaButton, '元推理');
1349
  }
1350
  }
1351
 
 
1366
 
1367
  // Load configuration when page loads
1368
  document.addEventListener('DOMContentLoaded', loadConfig);
1369
+
1370
+ /**
1371
+ * Process Mermaid node text with character and line limits
1372
+ * @param {string} mermaidCode - Original Mermaid code
1373
+ * @param {number} maxCharsPerLine - Maximum characters per line
1374
+ * @param {number} maxLines - Maximum number of lines per node
1375
+ * @param {string} truncationSuffix - Suffix for truncated text
1376
+ * @returns {string} Processed Mermaid code
1377
+ */
1378
+ function processMermaidNodeText(mermaidCode, maxCharsPerLine, maxLines, truncationSuffix = "...") {
1379
+ // Node definition regex: matches patterns like A["text content"] with capture groups
1380
+ const nodeRegex = /([A-Za-z0-9_]+)\s*\[\s*"([^"]+)"\s*\]/g;
1381
+
1382
+ // Process each node's text content
1383
+ return mermaidCode.replace(nodeRegex, (match, nodeId, text) => {
1384
+ // Clean text by replacing existing <br> tags and newlines with spaces
1385
+ const cleanText = text.replace(/<br>/g, ' ').replace(/\n/g, ' ');
1386
+
1387
+ // Text wrapping function similar to Python's textwrap.wrap
1388
+ function wrapText(text, width) {
1389
+ const words = text.split(' ');
1390
+ const lines = [];
1391
+ let currentLine = '';
1392
+
1393
+ for (const word of words) {
1394
+ if (currentLine.length + word.length + (currentLine ? 1 : 0) <= width) {
1395
+ currentLine += (currentLine ? ' ' : '') + word;
1396
+ } else {
1397
+ lines.push(currentLine);
1398
+ currentLine = word;
1399
+ }
1400
+ }
1401
+
1402
+ if (currentLine) {
1403
+ lines.push(currentLine);
1404
+ }
1405
+
1406
+ return lines;
1407
+ }
1408
+
1409
+ // Wrap text according to max chars per line
1410
+ let wrappedLines = wrapText(cleanText, maxCharsPerLine);
1411
+
1412
+ // Limit number of lines and add truncation indicator if needed
1413
+ if (wrappedLines.length > maxLines) {
1414
+ wrappedLines = wrappedLines.slice(0, maxLines);
1415
+ const lastLine = wrappedLines[wrappedLines.length - 1];
1416
+ if (lastLine.length > maxCharsPerLine - truncationSuffix.length) {
1417
+ wrappedLines[wrappedLines.length - 1] =
1418
+ lastLine.substring(0, maxCharsPerLine - truncationSuffix.length) + truncationSuffix;
1419
+ } else {
1420
+ wrappedLines[wrappedLines.length - 1] = lastLine + truncationSuffix;
1421
+ }
1422
+ }
1423
+
1424
+ // Join lines with <br> for Mermaid formatting
1425
+ const processedText = wrappedLines.join('<br>');
1426
+
1427
+ // Return the node with processed text
1428
+ return `${nodeId}["${processedText}"]`;
1429
+ });
1430
+ }
1431
+
1432
+ // Override extractMermaidCode to apply text processing for Long Reasoning
1433
+ const originalExtractMermaidCode = extractMermaidCode;
1434
+
1435
+ extractMermaidCode = function(text) {
1436
+ // Call the original function to extract the code
1437
+ const mermaidCode = originalExtractMermaidCode(text);
1438
+
1439
+ if (mermaidCode) {
1440
+ // Get character and line limits from UI
1441
+ const maxCharsPerLine = parseInt(document.getElementById('chars-per-line').value) || 40;
1442
+ const maxLines = parseInt(document.getElementById('max-lines').value) || 4;
1443
+
1444
+ // Apply text processing to enforce character and line limits
1445
+ return processMermaidNodeText(mermaidCode, maxCharsPerLine, maxLines);
1446
+ }
1447
+
1448
+ return mermaidCode;
1449
+ };
1450
  </script>
1451
  </body>
1452
  </html>