local-deep-research 0.5.2__py3-none-any.whl → 0.5.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,403 @@
1
+ """
2
+ ErrorReportGenerator - Create user-friendly error reports
3
+ """
4
+
5
+ from typing import Dict, Any, Optional
6
+ from loguru import logger
7
+
8
+ from .error_reporter import ErrorReporter
9
+
10
+
11
+ class ErrorReportGenerator:
12
+ """
13
+ Generates comprehensive, user-friendly error reports
14
+ """
15
+
16
+ def __init__(self, llm=None):
17
+ """
18
+ Initialize error report generator
19
+
20
+ Args:
21
+ llm: Optional LLM instance (unused, kept for compatibility)
22
+ """
23
+ self.error_reporter = ErrorReporter()
24
+
25
+ def generate_error_report(
26
+ self,
27
+ error_message: str,
28
+ query: str,
29
+ partial_results: Optional[Dict[str, Any]] = None,
30
+ search_iterations: int = 0,
31
+ research_id: Optional[int] = None,
32
+ ) -> str:
33
+ """
34
+ Generate a comprehensive error report
35
+
36
+ Args:
37
+ error_message: The error that occurred
38
+ query: The research query
39
+ partial_results: Any partial results that were collected
40
+ search_iterations: Number of search iterations completed
41
+ research_id: Research ID for reference
42
+
43
+ Returns:
44
+ str: Formatted error report in Markdown
45
+ """
46
+ try:
47
+ # Analyze the error
48
+ context = {
49
+ "query": query,
50
+ "search_iterations": search_iterations,
51
+ "research_id": research_id,
52
+ "partial_results": partial_results,
53
+ }
54
+
55
+ if partial_results:
56
+ context.update(partial_results)
57
+
58
+ error_analysis = self.error_reporter.analyze_error(
59
+ error_message, context
60
+ )
61
+
62
+ # Build the simplified report
63
+ report_parts = []
64
+
65
+ # Header with user-friendly error message and logs reference
66
+ user_friendly_message = self._make_error_user_friendly(
67
+ error_message
68
+ )
69
+ category_title = error_analysis.get("title", "Error")
70
+
71
+ report_parts.append("# ⚠️ Research Failed")
72
+ report_parts.append(f"\n**Error Type:** {category_title}")
73
+ report_parts.append(f"\n**What happened:** {user_friendly_message}")
74
+ report_parts.append(
75
+ '\n*For detailed error information, scroll down to the research logs and select "Errors" from the filter.*'
76
+ )
77
+
78
+ # Support links - moved up for better visibility
79
+ report_parts.append("\n## 💬 Get Help")
80
+ report_parts.append("We're here to help you get this working:")
81
+ report_parts.append(
82
+ "- 📖 **Documentation & guides:** [Wiki](https://github.com/LearningCircuit/local-deep-research/wiki)"
83
+ )
84
+ report_parts.append(
85
+ "- 💬 **Chat with the community:** [Discord #help-and-support](https://discord.gg/ttcqQeFcJ3)"
86
+ )
87
+ report_parts.append(
88
+ "- 🐛 **Report bugs or get help:** [GitHub Issues](https://github.com/LearningCircuit/local-deep-research/issues) *(don't hesitate to ask if you're stuck!)*"
89
+ )
90
+ report_parts.append(
91
+ "- 💭 **Join discussions:** [Reddit r/LocalDeepResearch](https://www.reddit.com/r/LocalDeepResearch/) *(checked less frequently)*"
92
+ )
93
+
94
+ # Show partial results if available (in expandable section)
95
+ if error_analysis.get("has_partial_results"):
96
+ partial_content = self._format_partial_results(partial_results)
97
+ if partial_content:
98
+ report_parts.append(
99
+ f"\n<details>\n<summary>📊 Partial Results Available</summary>\n\n{partial_content}\n</details>"
100
+ )
101
+
102
+ return "\n".join(report_parts)
103
+
104
+ except Exception as e:
105
+ # Fallback: always return something, even if error report generation fails
106
+ logger.exception(f"Failed to generate error report: {e}")
107
+ return f"""# ⚠️ Research Failed
108
+
109
+ **What happened:** {error_message}
110
+
111
+ ## 💬 Get Help
112
+ We're here to help you get this working:
113
+ - 📖 **Documentation & guides:** [Wiki](https://github.com/LearningCircuit/local-deep-research/wiki)
114
+ - 💬 **Chat with the community:** [Discord #help-and-support](https://discord.gg/ttcqQeFcJ3)
115
+ - 🐛 **Report bugs or get help:** [GitHub Issues](https://github.com/LearningCircuit/local-deep-research/issues) *(don't hesitate to ask if you're stuck!)*
116
+
117
+ *Note: Error report generation failed - showing basic error information.*"""
118
+
119
+ def _format_partial_results(
120
+ self, partial_results: Optional[Dict[str, Any]]
121
+ ) -> str:
122
+ """
123
+ Format partial results for display
124
+
125
+ Args:
126
+ partial_results: Partial results data
127
+
128
+ Returns:
129
+ str: Formatted partial results
130
+ """
131
+ if not partial_results:
132
+ return ""
133
+
134
+ formatted_parts = []
135
+
136
+ # Current knowledge summary
137
+ if "current_knowledge" in partial_results:
138
+ knowledge = partial_results["current_knowledge"]
139
+ if knowledge and len(knowledge.strip()) > 50:
140
+ formatted_parts.append("### Research Summary\n")
141
+ formatted_parts.append(
142
+ knowledge[:1000] + "..."
143
+ if len(knowledge) > 1000
144
+ else knowledge
145
+ )
146
+ formatted_parts.append("")
147
+
148
+ # Search results
149
+ if "search_results" in partial_results:
150
+ results = partial_results["search_results"]
151
+ if results:
152
+ formatted_parts.append("### Search Results Found\n")
153
+ for i, result in enumerate(results[:5], 1): # Show top 5
154
+ title = result.get("title", "Untitled")
155
+ url = result.get("url", "")
156
+ formatted_parts.append(f"{i}. **{title}**")
157
+ if url:
158
+ formatted_parts.append(f" - URL: {url}")
159
+ formatted_parts.append("")
160
+
161
+ # Findings
162
+ if "findings" in partial_results:
163
+ findings = partial_results["findings"]
164
+ if findings:
165
+ formatted_parts.append("### Research Findings\n")
166
+ for i, finding in enumerate(findings[:3], 1): # Show top 3
167
+ content = finding.get("content", "")
168
+ if content and not content.startswith("Error:"):
169
+ phase = finding.get("phase", f"Finding {i}")
170
+ formatted_parts.append(f"**{phase}:**")
171
+ formatted_parts.append(
172
+ content[:500] + "..."
173
+ if len(content) > 500
174
+ else content
175
+ )
176
+ formatted_parts.append("")
177
+
178
+ if formatted_parts:
179
+ formatted_parts.append(
180
+ "*Note: The above results were successfully collected before the error occurred.*"
181
+ )
182
+
183
+ return "\n".join(formatted_parts) if formatted_parts else ""
184
+
185
+ def _get_technical_context(
186
+ self,
187
+ error_analysis: Dict[str, Any],
188
+ partial_results: Optional[Dict[str, Any]],
189
+ ) -> str:
190
+ """
191
+ Get additional technical context for the error
192
+
193
+ Args:
194
+ error_analysis: Error analysis results
195
+ partial_results: Partial results if available
196
+
197
+ Returns:
198
+ str: Technical context information
199
+ """
200
+ context_parts = []
201
+
202
+ # Add timing information if available
203
+ if partial_results:
204
+ if "start_time" in partial_results:
205
+ context_parts.append(
206
+ f"- **Start Time:** {partial_results['start_time']}"
207
+ )
208
+
209
+ if "last_activity" in partial_results:
210
+ context_parts.append(
211
+ f"- **Last Activity:** {partial_results['last_activity']}"
212
+ )
213
+
214
+ # Add model information
215
+ if "model_config" in partial_results:
216
+ config = partial_results["model_config"]
217
+ context_parts.append(
218
+ f"- **Model:** {config.get('model_name', 'Unknown')}"
219
+ )
220
+ context_parts.append(
221
+ f"- **Provider:** {config.get('provider', 'Unknown')}"
222
+ )
223
+
224
+ # Add search information
225
+ if "search_config" in partial_results:
226
+ search_config = partial_results["search_config"]
227
+ context_parts.append(
228
+ f"- **Search Engine:** {search_config.get('engine', 'Unknown')}"
229
+ )
230
+ context_parts.append(
231
+ f"- **Max Results:** {search_config.get('max_results', 'Unknown')}"
232
+ )
233
+
234
+ # Add any error codes or HTTP status
235
+ if "status_code" in partial_results:
236
+ context_parts.append(
237
+ f"- **Status Code:** {partial_results['status_code']}"
238
+ )
239
+
240
+ if "error_code" in partial_results:
241
+ context_parts.append(
242
+ f"- **Error Code:** {partial_results['error_code']}"
243
+ )
244
+
245
+ # Add error-specific context based on category
246
+ category = error_analysis.get("category")
247
+ if category:
248
+ if "connection" in category.value.lower():
249
+ context_parts.append(
250
+ "- **Network Error:** Connection-related issue detected"
251
+ )
252
+ context_parts.append(
253
+ "- **Retry Recommended:** Check service status and try again"
254
+ )
255
+ elif "model" in category.value.lower():
256
+ context_parts.append(
257
+ "- **Model Error:** Issue with AI model or configuration"
258
+ )
259
+ context_parts.append(
260
+ "- **Check:** Model service availability and parameters"
261
+ )
262
+
263
+ return "\n".join(context_parts) if context_parts else ""
264
+
265
+ def generate_quick_error_summary(
266
+ self, error_message: str
267
+ ) -> Dict[str, str]:
268
+ """
269
+ Generate a quick error summary for API responses
270
+
271
+ Args:
272
+ error_message: The error message
273
+
274
+ Returns:
275
+ dict: Quick error summary
276
+ """
277
+ error_analysis = self.error_reporter.analyze_error(error_message)
278
+
279
+ return {
280
+ "title": error_analysis["title"],
281
+ "category": error_analysis["category"].value,
282
+ "severity": error_analysis["severity"],
283
+ "recoverable": error_analysis["recoverable"],
284
+ }
285
+
286
+ def _make_error_user_friendly(self, error_message: str) -> str:
287
+ """
288
+ Replace cryptic technical error messages with user-friendly versions
289
+
290
+ Args:
291
+ error_message: The original technical error message
292
+
293
+ Returns:
294
+ str: User-friendly error message, or original if no replacement found
295
+ """
296
+ # Dictionary of technical errors to user-friendly messages
297
+ error_replacements = {
298
+ "max_workers must be greater than 0": (
299
+ "The LLM failed to generate search questions. This usually means the LLM service isn't responding properly.\n\n"
300
+ "**Try this:**\n"
301
+ "- Check if your LLM service (Ollama/LM Studio) is running\n"
302
+ "- Restart the LLM service\n"
303
+ "- Try a different model"
304
+ ),
305
+ "POST predict.*EOF": (
306
+ "Lost connection to Ollama. This usually means Ollama stopped responding or there's a network issue.\n\n"
307
+ "**Try this:**\n"
308
+ "- Restart Ollama: `ollama serve`\n"
309
+ "- Check if Ollama is still running: `ps aux | grep ollama`\n"
310
+ "- Try a different port if 11434 is in use"
311
+ ),
312
+ "HTTP error 404.*research results": (
313
+ "The research completed but the results can't be displayed. The files were likely generated successfully.\n\n"
314
+ "**Try this:**\n"
315
+ "- Check the `research_outputs` folder for your report\n"
316
+ "- Ensure the folder has proper read/write permissions\n"
317
+ "- Restart the LDR web interface"
318
+ ),
319
+ "Connection refused|\\[Errno 111\\]": (
320
+ "Cannot connect to the LLM service. The service might not be running or is using a different address.\n\n"
321
+ "**Try this:**\n"
322
+ "- Start your LLM service (Ollama: `ollama serve`, LM Studio: launch the app)\n"
323
+ "- **Docker on Mac/Windows:** Change URL from `http://localhost:1234` to `http://host.docker.internal:1234`\n"
324
+ "- **Docker on Linux:** Use your host IP instead of localhost (find with `hostname -I`)\n"
325
+ "- Check the service URL in settings matches where your LLM is running\n"
326
+ "- Verify the port number is correct (Ollama: 11434, LM Studio: 1234)"
327
+ ),
328
+ "The search is longer than 256 characters": (
329
+ "Your search query is too long for GitHub's API (max 256 characters).\n\n"
330
+ "**Try this:**\n"
331
+ "- Shorten your research query\n"
332
+ "- Use a different search engine (DuckDuckGo, Searx, etc.)\n"
333
+ "- Break your research into smaller, focused queries"
334
+ ),
335
+ "No module named.*local_deep_research": (
336
+ "Installation issue detected. The package isn't properly installed.\n\n"
337
+ "**Try this:**\n"
338
+ "- Reinstall: `pip install -e .` from the project directory\n"
339
+ "- Check you're using the right Python environment\n"
340
+ "- For Docker users: rebuild the container"
341
+ ),
342
+ "Failed to create search engine|could not be found": (
343
+ "Search engine configuration problem.\n\n"
344
+ "**Try this:**\n"
345
+ "- Use the default search engine (auto)\n"
346
+ "- Check search engine settings in Advanced Options\n"
347
+ "- Ensure required API keys are set for external search engines"
348
+ ),
349
+ "TypeError.*Context.*Size|'<' not supported between": (
350
+ "Model configuration issue. The context size setting might not be compatible with your model.\n\n"
351
+ "**Try this:**\n"
352
+ "- Check your model's maximum context size\n"
353
+ "- Leave context size settings at default\n"
354
+ "- Try a different model"
355
+ ),
356
+ "Model.*not found in Ollama": (
357
+ "The specified model isn't available in Ollama.\n\n"
358
+ "**Try this:**\n"
359
+ "- Check available models: `ollama list`\n"
360
+ "- Pull the model: `ollama pull <model-name>`\n"
361
+ "- Use the exact model name shown in `ollama list` (e.g., 'gemma2:9b' not 'gemma:latest')"
362
+ ),
363
+ "No auth credentials found|401.*API key": (
364
+ "API key is missing or incorrectly configured.\n\n"
365
+ "**Try this:**\n"
366
+ "- Set API key in the web UI settings (not in .env files)\n"
367
+ "- Go to Settings → Advanced → enter your API key\n"
368
+ "- For custom endpoints, ensure the key format matches what your provider expects"
369
+ ),
370
+ "Attempt to write readonly database": (
371
+ "Permission issue with the database file.\n\n"
372
+ "**Try this:**\n"
373
+ "- On Windows: Run as Administrator\n"
374
+ "- On Linux/Mac: Check folder permissions\n"
375
+ "- Delete and recreate the database file if corrupted"
376
+ ),
377
+ "Invalid value.*SearXNG|database.*locked": (
378
+ "SearXNG configuration or rate limiting issue.\n\n"
379
+ "**Try this:**\n"
380
+ "- Keep 'Search snippets only' enabled (don't turn it off)\n"
381
+ "- Restart SearXNG: `docker restart searxng`\n"
382
+ "- If rate limited, wait a few minutes or use a VPN"
383
+ ),
384
+ "host.*localhost.*Docker|127\\.0\\.0\\.1.*Docker|localhost.*1234.*Docker|LM.*Studio.*Docker.*Mac": (
385
+ "Docker networking issue - can't connect to services on host.\n\n"
386
+ "**Try this:**\n"
387
+ "- **On Mac/Windows Docker:** Replace 'localhost' or '127.0.0.1' with 'host.docker.internal'\n"
388
+ "- **On Linux Docker:** Use your host's actual IP address (find with `hostname -I`)\n"
389
+ "- **Example:** Change `http://localhost:1234` to `http://host.docker.internal:1234`\n"
390
+ "- Ensure the service port isn't blocked by firewall\n"
391
+ "- Alternative: Use host networking mode (see wiki for setup)"
392
+ ),
393
+ }
394
+
395
+ # Check each pattern and replace if found
396
+ for pattern, replacement in error_replacements.items():
397
+ import re
398
+
399
+ if re.search(pattern, error_message, re.IGNORECASE):
400
+ return f"{replacement}\n\nTechnical error: {error_message}"
401
+
402
+ # If no specific replacement found, return original message
403
+ return error_message
@@ -254,7 +254,7 @@ def get_research_details(research_id):
254
254
  )
255
255
 
256
256
 
257
- @history_bp.route("/report/<int:research_id>")
257
+ @history_bp.route("/history/report/<int:research_id>")
258
258
  def get_report(research_id):
259
259
  conn = get_db_connection()
260
260
  conn.row_factory = lambda cursor, row: {
@@ -17,6 +17,7 @@ from ...utilities.threading_utils import thread_context, thread_with_app_context
17
17
  from ..database.models import ResearchStrategy, ResearchHistory
18
18
  from ..models.database import calculate_duration
19
19
  from .socket_service import SocketIOService
20
+ from ...error_handling.report_generator import ErrorReportGenerator
20
21
 
21
22
  # Output directory for research results
22
23
  _PROJECT_ROOT = Path(__file__).parents[4]
@@ -657,8 +658,40 @@ def run_research_process(
657
658
  )
658
659
 
659
660
  try:
660
- # Get the synthesized content from the LLM directly
661
- clean_markdown = raw_formatted_findings
661
+ # Check if we have an error in the findings and use enhanced error handling
662
+ if isinstance(
663
+ raw_formatted_findings, str
664
+ ) and raw_formatted_findings.startswith("Error:"):
665
+ logger.info(
666
+ "Generating enhanced error report using ErrorReportGenerator"
667
+ )
668
+
669
+ # Get LLM for error explanation if available
670
+ try:
671
+ llm = get_llm(research_id=research_id)
672
+ except Exception:
673
+ llm = None
674
+ logger.warning(
675
+ "Could not get LLM for error explanation"
676
+ )
677
+
678
+ # Generate comprehensive error report
679
+ error_generator = ErrorReportGenerator(llm)
680
+ clean_markdown = error_generator.generate_error_report(
681
+ error_message=raw_formatted_findings,
682
+ query=query,
683
+ partial_results=results,
684
+ search_iterations=results.get("iterations", 0),
685
+ research_id=research_id,
686
+ )
687
+
688
+ logger.info(
689
+ "Generated enhanced error report with %d characters",
690
+ len(clean_markdown),
691
+ )
692
+ else:
693
+ # Get the synthesized content from the LLM directly
694
+ clean_markdown = raw_formatted_findings
662
695
 
663
696
  # Extract all sources from findings to add them to the summary
664
697
  all_links = []
@@ -878,10 +911,77 @@ def run_research_process(
878
911
  "solution": "Check API configuration and credentials."
879
912
  }
880
913
 
914
+ # Generate enhanced error report for failed research
915
+ enhanced_report_content = None
916
+ try:
917
+ # Get LLM for error explanation if available
918
+ try:
919
+ llm = get_llm(research_id=research_id)
920
+ except Exception:
921
+ llm = None
922
+ logger.warning(
923
+ "Could not get LLM for error explanation in failure handler"
924
+ )
925
+
926
+ # Get partial results if they exist
927
+ partial_results = results if "results" in locals() else None
928
+ search_iterations = (
929
+ results.get("iterations", 0) if partial_results else 0
930
+ )
931
+
932
+ # Generate comprehensive error report
933
+ error_generator = ErrorReportGenerator(llm)
934
+ enhanced_report_content = error_generator.generate_error_report(
935
+ error_message=f"Research failed: {str(e)}",
936
+ query=query,
937
+ partial_results=partial_results,
938
+ search_iterations=search_iterations,
939
+ research_id=research_id,
940
+ )
941
+
942
+ logger.info(
943
+ "Generated enhanced error report for failed research (length: %d)",
944
+ len(enhanced_report_content),
945
+ )
946
+
947
+ # Save enhanced error report as the actual report file
948
+ try:
949
+ reports_folder = OUTPUT_DIR
950
+ report_filename = f"research_{research_id}_error_report.md"
951
+ report_path = reports_folder / report_filename
952
+
953
+ with open(report_path, "w", encoding="utf-8") as f:
954
+ f.write(enhanced_report_content)
955
+
956
+ logger.info(
957
+ "Saved enhanced error report to: %s", report_path
958
+ )
959
+
960
+ # Store the report path so it can be retrieved later
961
+ report_path_to_save = str(
962
+ report_path.relative_to(reports_folder.parent)
963
+ )
964
+
965
+ except Exception as report_error:
966
+ logger.exception(
967
+ "Failed to save enhanced error report: %s", report_error
968
+ )
969
+ report_path_to_save = None
970
+
971
+ except Exception as error_gen_error:
972
+ logger.exception(
973
+ "Failed to generate enhanced error report: %s",
974
+ error_gen_error,
975
+ )
976
+ enhanced_report_content = None
977
+ report_path_to_save = None
978
+
881
979
  # Update metadata with more context about the error
882
980
  metadata = {"phase": "error", "error": user_friendly_error}
883
981
  if error_context:
884
982
  metadata.update(error_context)
983
+ if enhanced_report_content:
984
+ metadata["has_enhanced_report"] = True
885
985
 
886
986
  # If we still have an active research record, update its log
887
987
  if research_id in active_research:
@@ -934,6 +1034,10 @@ def run_research_process(
934
1034
  research.duration_seconds = duration_seconds
935
1035
  research.metadata = metadata
936
1036
 
1037
+ # Add error report path if available
1038
+ if "report_path_to_save" in locals() and report_path_to_save:
1039
+ research.report_path = report_path_to_save
1040
+
937
1041
  db_session.commit()
938
1042
 
939
1043
  try:
@@ -119,6 +119,18 @@ a:hover {
119
119
  color: var(--text-primary);
120
120
  }
121
121
 
122
+ .sidebar-nav li a:focus {
123
+ outline: 2px solid var(--accent-primary);
124
+ outline-offset: -2px;
125
+ background-color: var(--bg-tertiary);
126
+ color: var(--text-primary);
127
+ }
128
+
129
+ .sidebar-nav li a:focus .nav-shortcut {
130
+ background-color: var(--accent-primary);
131
+ color: var(--text-primary);
132
+ }
133
+
122
134
  .sidebar-nav li.active {
123
135
  color: var(--accent-primary);
124
136
  background-color: rgba(110, 79, 246, 0.1);
@@ -130,13 +142,53 @@ a:hover {
130
142
  color: inherit;
131
143
  }
132
144
 
145
+ /* Navigation shortcuts */
146
+ .nav-shortcut {
147
+ margin-left: auto;
148
+ background-color: var(--bg-tertiary);
149
+ color: var(--text-muted);
150
+ padding: 0.2rem 0.5rem;
151
+ border-radius: 4px;
152
+ font-size: 0.75rem;
153
+ font-weight: 600;
154
+ min-width: 1.5rem;
155
+ text-align: center;
156
+ transition: all 0.2s;
157
+ }
158
+
159
+ .sidebar-nav li:hover .nav-shortcut {
160
+ background-color: var(--accent-primary);
161
+ color: var(--text-primary);
162
+ }
163
+
164
+ .sidebar-nav li.active .nav-shortcut {
165
+ background-color: var(--accent-primary);
166
+ color: var(--text-primary);
167
+ }
168
+
133
169
  .sidebar-footer {
134
170
  padding: 1rem 1.5rem;
135
171
  border-top: 1px solid var(--border-color);
136
172
  color: var(--text-muted);
137
173
  font-size: 0.875rem;
138
174
  display: flex;
139
- justify-content: center;
175
+ flex-direction: column;
176
+ align-items: center;
177
+ gap: 0.5rem;
178
+ }
179
+
180
+ .nav-hint {
181
+ font-size: 0.75rem;
182
+ opacity: 0.7;
183
+ margin: 0;
184
+ }
185
+
186
+ .nav-hint kbd {
187
+ background-color: var(--bg-tertiary);
188
+ padding: 0.1rem 0.3rem;
189
+ border-radius: 3px;
190
+ font-size: 0.7rem;
191
+ border: 1px solid var(--border-color);
140
192
  }
141
193
 
142
194
  /* Main Content */
@@ -1046,6 +1098,11 @@ textarea:focus, input[type="text"]:focus {
1046
1098
  justify-content: center;
1047
1099
  }
1048
1100
 
1101
+ /* Hide navigation shortcuts on small screens */
1102
+ .nav-shortcut {
1103
+ display: none;
1104
+ }
1105
+
1049
1106
  /* Active state fix for icon view */
1050
1107
  .sidebar-nav li.active {
1051
1108
  border-left-width: 3px;
@@ -1581,6 +1638,10 @@ textarea:focus, input[type="text"]:focus {
1581
1638
  border-radius: 8px;
1582
1639
  background-color: rgba(64, 191, 255, 0.05);
1583
1640
  border: 1px solid rgba(64, 191, 255, 0.1);
1641
+ width: 100%;
1642
+ text-align: left;
1643
+ font-family: inherit;
1644
+ font-size: inherit;
1584
1645
  }
1585
1646
 
1586
1647
  .advanced-options-toggle:hover {
@@ -1605,11 +1666,25 @@ textarea:focus, input[type="text"]:focus {
1605
1666
  .advanced-options-panel {
1606
1667
  background-color: rgba(42, 42, 58, 0.5);
1607
1668
  border-radius: 8px;
1608
- padding: 1.5rem;
1669
+ padding: 0 1.5rem;
1609
1670
  margin-bottom: 1.5rem;
1610
1671
  border: 1px solid var(--border-color);
1611
- display: none;
1612
1672
  box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.1);
1673
+ max-height: 0;
1674
+ overflow: hidden;
1675
+ opacity: 0;
1676
+ visibility: hidden;
1677
+ pointer-events: none;
1678
+ transition: all 0.3s ease;
1679
+ }
1680
+
1681
+ .advanced-options-toggle.open + .advanced-options-panel,
1682
+ .advanced-options-panel.expanded {
1683
+ max-height: 1000px;
1684
+ opacity: 1;
1685
+ visibility: visible;
1686
+ pointer-events: auto;
1687
+ padding: 1.5rem;
1613
1688
  }
1614
1689
 
1615
1690
  .form-row {