Spaces:
Running
Running
Upload 16 files
Browse files- api_base.py +57 -9
- app.py +42 -0
- configs.py +14 -0
- cot_reasoning.py +9 -17
- l2m_reasoning.py +2 -2
- plain_text_reasoning.py +30 -0
- templates/index.html +633 -24
- templates/index_cn.html +641 -32
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
106 |
-
wrapped_lines
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
65 |
-
wrapped_lines
|
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
|
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
|
351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
</div>
|
353 |
</div>
|
354 |
<div class="links">
|
355 |
<a href="https://github.com/ZongqianLi/ReasonGraph"><u>Github</u> | </a><a href="https://arxiv.org/abs/2503.03979"><u>Paper</u> | </a><a href="mailto:[email protected]"><u>Email</u></a>
|
356 |
-
|
357 |
-
|
358 |
-
|
|
|
|
|
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 |
-
//
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
if (result.success) {
|
470 |
-
document.getElementById('api-key').value = result.api_key;
|
471 |
} else {
|
472 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
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 |
-
|
|
|
749 |
if (isMetaReasoning) {
|
750 |
-
metaButton
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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
|
806 |
-
|
807 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
808 |
} else {
|
809 |
showError('Failed to load method configuration');
|
810 |
-
metaButton.
|
|
|
811 |
}
|
812 |
} else {
|
813 |
showError(result.error || 'Failed to select method');
|
814 |
-
metaButton.
|
|
|
815 |
}
|
816 |
} catch (error) {
|
817 |
console.error('Meta reasoning error:', error);
|
818 |
showError('Failed to execute meta reasoning');
|
819 |
metaButton.disabled = false;
|
820 |
-
metaButton
|
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> | </a><a href="https://arxiv.org/abs/2503.03979"><u>Paper</u> | </a><a href="mailto:[email protected]"><u>Email</u></a>
|
450 |
+
<span class="link-separator"> | | </span><a href="./index.html" class="tooltip tooltip-top lang-tooltip english-tooltip"><u>English</u> / 
|
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
|
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"
|
351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
</div>
|
353 |
</div>
|
354 |
<div class="links">
|
355 |
-
<a href="https://github.com/ZongqianLi/ReasonGraph"><u>Github</u> | </a><a href="https://arxiv.org/abs/2503.03979"><u>论文</u> | </a><a href="mailto:[email protected]"><u
|
356 |
-
|
357 |
-
|
358 |
-
|
|
|
|
|
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
|
390 |
</div>
|
391 |
|
392 |
<div class="param-group">
|
@@ -395,7 +491,7 @@
|
|
395 |
</div>
|
396 |
|
397 |
<div class="output-section">
|
398 |
-
<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()"
|
|
|
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 |
-
//
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
if (result.success) {
|
470 |
-
document.getElementById('api-key').value = result.api_key;
|
471 |
} else {
|
472 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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('
|
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('
|
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 |
-
|
|
|
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 |
-
|
|
|
749 |
if (isMetaReasoning) {
|
750 |
-
metaButton
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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(
|
803 |
|
804 |
// Update button to show method was selected
|
805 |
-
metaButton
|
806 |
-
|
807 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
808 |
} else {
|
809 |
showError('加载方法配置失败');
|
810 |
-
metaButton.
|
|
|
811 |
}
|
812 |
} else {
|
813 |
showError(result.error || '选择方法失败');
|
814 |
-
metaButton.
|
|
|
815 |
}
|
816 |
} catch (error) {
|
817 |
console.error('Meta reasoning error:', error);
|
818 |
showError('执行元推理失败');
|
819 |
metaButton.disabled = false;
|
820 |
-
metaButton
|
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> | </a><a href="https://arxiv.org/abs/2503.03979"><u>论文</u> | </a><a href="mailto:[email protected]"><u>Email</u></a>
|
450 |
+
<span class="link-separator"> | | </span><a href="./index.html" class="tooltip tooltip-top lang-tooltip english-tooltip"><u>English</u> / 
|
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>
|