Spaces:
Sleeping
Sleeping
Update a lot...
Browse files- Gradio_UI.py +8 -2
- agents/single_agent.py +4 -2
- app.py +12 -14
- config.py +2 -2
- run_single_agent.py +1 -1
- tools/__init__.py +3 -1
- tools/analysis_tools.py +6 -12
- tools/formatting_tools.py +165 -207
Gradio_UI.py
CHANGED
@@ -197,9 +197,15 @@ class GradioUI:
|
|
197 |
|
198 |
def interact_with_agent(self, prompt, messages):
|
199 |
import gradio as gr
|
200 |
-
|
201 |
messages.append(gr.ChatMessage(role="user", content=prompt))
|
202 |
yield messages
|
|
|
|
|
|
|
|
|
|
|
|
|
203 |
for msg in stream_to_gradio(self.agent, task=prompt, reset_agent_memory=False):
|
204 |
messages.append(msg)
|
205 |
yield messages
|
@@ -275,7 +281,7 @@ class GradioUI:
|
|
275 |
2. Agent will **search for the lyrics**.
|
276 |
3. Agent will **summarize the song's meaning** based on the lyrics using pre-defined prompt (see files).
|
277 |
"""
|
278 |
-
|
279 |
with gr.Blocks(fill_height=True) as demo:
|
280 |
gr.Markdown(ui_tip_text)
|
281 |
stored_messages = gr.State([])
|
|
|
197 |
|
198 |
def interact_with_agent(self, prompt, messages):
|
199 |
import gradio as gr
|
200 |
+
|
201 |
messages.append(gr.ChatMessage(role="user", content=prompt))
|
202 |
yield messages
|
203 |
+
|
204 |
+
prompt = f"""1. Find and extract the lyrics of the song: {prompt}.
|
205 |
+
2. Perform deep lyrics analysis and return full lyrics and analysis
|
206 |
+
3. Generate an image based on the song analysis using generate_image tool
|
207 |
+
4. Return rich-formatted string.
|
208 |
+
"""
|
209 |
for msg in stream_to_gradio(self.agent, task=prompt, reset_agent_memory=False):
|
210 |
messages.append(msg)
|
211 |
yield messages
|
|
|
281 |
2. Agent will **search for the lyrics**.
|
282 |
3. Agent will **summarize the song's meaning** based on the lyrics using pre-defined prompt (see files).
|
283 |
"""
|
284 |
+
|
285 |
with gr.Blocks(fill_height=True) as demo:
|
286 |
gr.Markdown(ui_tip_text)
|
287 |
stored_messages = gr.State([])
|
agents/single_agent.py
CHANGED
@@ -9,6 +9,7 @@ from config import load_prompt_templates
|
|
9 |
from tools.analysis_tools import AnalyzeLyricsTool
|
10 |
from tools.formatting_tools import FormatAnalysisResultsTool
|
11 |
from tools.search_tools import ThrottledDuckDuckGoSearchTool, BraveSearchTool
|
|
|
12 |
|
13 |
|
14 |
def create_single_agent(model):
|
@@ -32,13 +33,14 @@ def create_single_agent(model):
|
|
32 |
VisitWebpageTool(),
|
33 |
AnalyzeLyricsTool(),
|
34 |
FormatAnalysisResultsTool(),
|
|
|
35 |
FinalAnswerTool(),
|
36 |
],
|
37 |
model=model,
|
38 |
-
additional_authorized_imports=['numpy', 'bs4', 'rich'],
|
39 |
max_steps=25,
|
40 |
verbosity_level=2,
|
41 |
-
description="Specialized agent for finding and extracting song lyrics specified by the user using search and analysis tools. Performs lyrics search and after getting the lyrics full text from the web, it performs the analysis using given tools. Provides detailed commentary with beautiful and human-readable format using rich library formatting. Format analysis results in rich colorful output.",
|
42 |
prompt_templates=prompt_templates
|
43 |
)
|
44 |
|
|
|
9 |
from tools.analysis_tools import AnalyzeLyricsTool
|
10 |
from tools.formatting_tools import FormatAnalysisResultsTool
|
11 |
from tools.search_tools import ThrottledDuckDuckGoSearchTool, BraveSearchTool
|
12 |
+
from tools.image_generation_tools import GenerateImageTool
|
13 |
|
14 |
|
15 |
def create_single_agent(model):
|
|
|
33 |
VisitWebpageTool(),
|
34 |
AnalyzeLyricsTool(),
|
35 |
FormatAnalysisResultsTool(),
|
36 |
+
GenerateImageTool(),
|
37 |
FinalAnswerTool(),
|
38 |
],
|
39 |
model=model,
|
40 |
+
additional_authorized_imports=['numpy', 'bs4', 'rich', 'json'],
|
41 |
max_steps=25,
|
42 |
verbosity_level=2,
|
43 |
+
description="Specialized agent for finding and extracting song lyrics specified by the user using search and analysis tools. Performs lyrics search and after getting the lyrics full text from the web, it performs the analysis using given tools. Generates visual representation of the song based on the analysis results. Provides detailed commentary with beautiful and human-readable format using rich library formatting. Format analysis results in rich colorful output.",
|
44 |
prompt_templates=prompt_templates
|
45 |
)
|
46 |
|
app.py
CHANGED
@@ -5,8 +5,6 @@ Lyrics Analyzer Agent - Main Entry Point
|
|
5 |
This module serves as the entry point for the Lyrics Analyzer application, which
|
6 |
uses a system of specialized agents to search for and analyze song lyrics.
|
7 |
"""
|
8 |
-
import os
|
9 |
-
|
10 |
from loguru import logger
|
11 |
from smolagents import LiteLLMModel
|
12 |
|
@@ -32,14 +30,16 @@ def main():
|
|
32 |
setup_logger()
|
33 |
load_api_keys()
|
34 |
|
35 |
-
|
|
|
|
|
36 |
|
37 |
# If using Ollama, we need to specify the API base URL
|
38 |
# Initialize the LLM model based on configuration
|
39 |
-
model_id = get_model_id(
|
40 |
|
41 |
logger.info(f"Initializing with model: {model_id}")
|
42 |
-
if
|
43 |
api_base = get_ollama_api_base()
|
44 |
logger.info(f"Using Ollama API base: {api_base}")
|
45 |
model = LiteLLMModel(model_id=model_id, api_base=api_base)
|
@@ -55,21 +55,19 @@ def main():
|
|
55 |
# Determine if we're in test mode (local) or production (HuggingFace)
|
56 |
# HuggingFace environment has SPACE_ID environment variable
|
57 |
|
58 |
-
gradio_config = get_gradio_config(
|
|
|
|
|
59 |
|
60 |
# Launch with appropriate configuration
|
61 |
launch_kwargs = {
|
62 |
"debug": gradio_config["debug"],
|
63 |
-
"share": gradio_config["share"]
|
|
|
|
|
64 |
}
|
65 |
|
66 |
-
#
|
67 |
-
if is_test:
|
68 |
-
launch_kwargs.update({
|
69 |
-
"server_name": gradio_config["server_name"],
|
70 |
-
"server_port": gradio_config["server_port"]
|
71 |
-
})
|
72 |
-
|
73 |
GradioUI(single_agent).launch(**launch_kwargs)
|
74 |
logger.success("Server started successfully")
|
75 |
|
|
|
5 |
This module serves as the entry point for the Lyrics Analyzer application, which
|
6 |
uses a system of specialized agents to search for and analyze song lyrics.
|
7 |
"""
|
|
|
|
|
8 |
from loguru import logger
|
9 |
from smolagents import LiteLLMModel
|
10 |
|
|
|
30 |
setup_logger()
|
31 |
load_api_keys()
|
32 |
|
33 |
+
# use_local = os.environ.get('SPACE_ID') is None
|
34 |
+
use_local_llm = False
|
35 |
+
use_localhost = True
|
36 |
|
37 |
# If using Ollama, we need to specify the API base URL
|
38 |
# Initialize the LLM model based on configuration
|
39 |
+
model_id = get_model_id(provider='openrouter')
|
40 |
|
41 |
logger.info(f"Initializing with model: {model_id}")
|
42 |
+
if use_local_llm:
|
43 |
api_base = get_ollama_api_base()
|
44 |
logger.info(f"Using Ollama API base: {api_base}")
|
45 |
model = LiteLLMModel(model_id=model_id, api_base=api_base)
|
|
|
55 |
# Determine if we're in test mode (local) or production (HuggingFace)
|
56 |
# HuggingFace environment has SPACE_ID environment variable
|
57 |
|
58 |
+
gradio_config = get_gradio_config(use_localhost=use_localhost)
|
59 |
+
|
60 |
+
# Инструкции агенту настраиваются непосредственно в GradioUI.py
|
61 |
|
62 |
# Launch with appropriate configuration
|
63 |
launch_kwargs = {
|
64 |
"debug": gradio_config["debug"],
|
65 |
+
"share": gradio_config["share"],
|
66 |
+
"server_name": gradio_config["server_name"],
|
67 |
+
"server_port": gradio_config["server_port"]
|
68 |
}
|
69 |
|
70 |
+
# Передаем инструкцию в конструктор GradioUI
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
GradioUI(single_agent).launch(**launch_kwargs)
|
72 |
logger.success("Server started successfully")
|
73 |
|
config.py
CHANGED
@@ -70,7 +70,7 @@ SEARCH_TOOL_CONFIG = {
|
|
70 |
|
71 |
|
72 |
# Gradio UI configuration
|
73 |
-
def get_gradio_config(
|
74 |
"""Get the appropriate Gradio UI configuration based on environment.
|
75 |
|
76 |
Args:
|
@@ -80,7 +80,7 @@ def get_gradio_config(is_test=True):
|
|
80 |
Returns:
|
81 |
Dictionary with Gradio configuration parameters.
|
82 |
"""
|
83 |
-
if
|
84 |
# Configuration for local development/testing
|
85 |
return {
|
86 |
"debug": True,
|
|
|
70 |
|
71 |
|
72 |
# Gradio UI configuration
|
73 |
+
def get_gradio_config(use_localhost=True):
|
74 |
"""Get the appropriate Gradio UI configuration based on environment.
|
75 |
|
76 |
Args:
|
|
|
80 |
Returns:
|
81 |
Dictionary with Gradio configuration parameters.
|
82 |
"""
|
83 |
+
if use_localhost:
|
84 |
# Configuration for local development/testing
|
85 |
return {
|
86 |
"debug": True,
|
run_single_agent.py
CHANGED
@@ -30,7 +30,7 @@ else:
|
|
30 |
# model_id='https://pflgm2locj2t89co.us-east-1.aws.endpoints.huggingface.cloud'
|
31 |
|
32 |
# Prompt the user for the song name
|
33 |
-
song_data = "
|
34 |
|
35 |
agent = create_single_agent(model)
|
36 |
prompt = f"""1. Find and extract the lyrics of the song: {song_data}.
|
|
|
30 |
# model_id='https://pflgm2locj2t89co.us-east-1.aws.endpoints.huggingface.cloud'
|
31 |
|
32 |
# Prompt the user for the song name
|
33 |
+
song_data = "Mechanical Me - Beachy Head"
|
34 |
|
35 |
agent = create_single_agent(model)
|
36 |
prompt = f"""1. Find and extract the lyrics of the song: {song_data}.
|
tools/__init__.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1 |
"""
|
2 |
-
Tools for interacting with search engines
|
3 |
"""
|
|
|
|
|
|
1 |
"""
|
2 |
+
Tools for interacting with search engines, analyzing song lyrics, and generating images.
|
3 |
"""
|
4 |
+
|
5 |
+
from .image_generation_tools import GenerateImageTool
|
tools/analysis_tools.py
CHANGED
@@ -75,17 +75,11 @@ class AnalyzeLyricsTool(Tool):
|
|
75 |
|
76 |
# Use the function with retry mechanism
|
77 |
logger.info("Analyzing lyrics for song: '{}' by '{}'", song_title, artist)
|
|
|
78 |
response_text = make_api_call_with_retry(model_to_use, prompt)
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
return response_json
|
85 |
-
except json.JSONDecodeError as e:
|
86 |
-
logger.error(f"Failed to parse lyrics analysis response as JSON: {str(e)}")
|
87 |
-
# Return the raw text response if parsing fails
|
88 |
-
# This will likely cause an error in the formatting step,
|
89 |
-
# but at least we'll have the raw output for debugging
|
90 |
-
return response_text
|
91 |
|
|
|
75 |
|
76 |
# Use the function with retry mechanism
|
77 |
logger.info("Analyzing lyrics for song: '{}' by '{}'", song_title, artist)
|
78 |
+
|
79 |
response_text = make_api_call_with_retry(model_to_use, prompt)
|
80 |
+
# Parse the string response into a JSON object (dictionary)
|
81 |
+
logger.debug(f"Parsing JSON response for {song_title}")
|
82 |
+
response_json = json.loads(response_text)
|
83 |
+
return response_json
|
84 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
|
tools/formatting_tools.py
CHANGED
@@ -2,9 +2,10 @@
|
|
2 |
Formatting tools for displaying analysis results with rich formatting.
|
3 |
"""
|
4 |
|
5 |
-
import
|
|
|
6 |
import traceback
|
7 |
-
from typing import Dict, Any,
|
8 |
from loguru import logger
|
9 |
from smolagents import Tool
|
10 |
from rich.console import Console
|
@@ -32,215 +33,172 @@ class FormatAnalysisResultsTool(Tool):
|
|
32 |
Formats the analysis results for better readability.
|
33 |
|
34 |
Args:
|
35 |
-
analysis_json:
|
36 |
-
|
37 |
-
# {
|
38 |
-
# "summary": "Overall analysis of the song vibes, meaning and mood",
|
39 |
-
# "main_themes": ["theme1", "theme2", ...],
|
40 |
-
# "mood": "The overall mood/emotion of the song",
|
41 |
-
# "sections_analysis": [
|
42 |
-
# {
|
43 |
-
# "section_type": "verse/chorus/bridge/etc.",
|
44 |
-
# "section_number": 1,
|
45 |
-
# "lines": ["line1", "line2", ...],
|
46 |
-
# "analysis": "Analysis of this section whith respect to the overall theme"
|
47 |
-
# },
|
48 |
-
# ...
|
49 |
-
# ],
|
50 |
-
# "conclusion": "The song vibes and concepts of the underlying meaning"
|
51 |
-
# }
|
52 |
-
pretty: Whether to use rich formatting (True) or simple text formatting (False)
|
53 |
|
54 |
Returns:
|
55 |
A formatted string representation of the analysis
|
56 |
"""
|
57 |
-
|
58 |
-
analysis = analysis_json
|
59 |
-
# Log the structure of the parsed analysis
|
60 |
-
logger.debug(f"Analysis structure keys: {list(analysis.keys()) if isinstance(analysis, dict) else 'Not a dictionary'}")
|
61 |
-
|
62 |
-
if pretty:
|
63 |
-
# Rich formatting with the rich library
|
64 |
-
try:
|
65 |
-
logger.debug("Starting rich formatting")
|
66 |
-
# Create a console that outputs to a temporary file
|
67 |
-
import tempfile
|
68 |
-
temp_file = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False)
|
69 |
-
console = Console(file=temp_file, width=100)
|
70 |
-
|
71 |
-
# Create a custom theme for consistent styling
|
72 |
-
# Using direct style definitions instead of named styles
|
73 |
-
custom_theme = Theme({
|
74 |
-
"heading": "bold cyan underline",
|
75 |
-
"highlight": "bold yellow",
|
76 |
-
"positive": "green",
|
77 |
-
"negative": "red",
|
78 |
-
"neutral": "magenta",
|
79 |
-
"quote": "italic yellow",
|
80 |
-
"metadata": "dim white",
|
81 |
-
"conclusion": "bold magenta" # Add style for conclusion
|
82 |
-
})
|
83 |
-
|
84 |
-
# Apply the theme to our console
|
85 |
-
console.theme = custom_theme
|
86 |
-
|
87 |
-
# Format the summary section
|
88 |
-
summary = analysis.get("summary", "No summary available")
|
89 |
-
logger.debug(f"Summary content: {summary if summary else 'Not available'}")
|
90 |
-
console.print(Panel(Text(summary, justify="center"), title="[heading]Song Analysis[/]", subtitle="[metadata]Summary[/]"))
|
91 |
-
|
92 |
-
# Create a table for the main themes and mood
|
93 |
-
info_table = Table(show_header=False, box=ROUNDED, expand=True)
|
94 |
-
info_table.add_column("Key", style="bold blue")
|
95 |
-
info_table.add_column("Value")
|
96 |
-
|
97 |
-
# Add the mood
|
98 |
-
mood = analysis.get("mood", "Not specified")
|
99 |
-
logger.debug(f"Mood content: {mood if mood else 'Not specified'}")
|
100 |
-
info_table.add_row("Mood", mood)
|
101 |
-
|
102 |
-
# Add the themes as a comma-separated list
|
103 |
-
themes = analysis.get("main_themes", [])
|
104 |
-
logger.debug(f"Themes: {themes if themes else 'Not available'}")
|
105 |
-
if themes:
|
106 |
-
themes_text = ", ".join([f"[highlight]{theme}[/]" for theme in themes])
|
107 |
-
info_table.add_row("Main Themes", themes_text)
|
108 |
-
|
109 |
-
console.print(info_table)
|
110 |
-
|
111 |
-
# Section-by-section analysis
|
112 |
-
sections = analysis.get("sections_analysis", [])
|
113 |
-
logger.debug(f"Sections count: {len(sections) if sections else 0}")
|
114 |
-
if sections:
|
115 |
-
console.print("\n[heading]Section-by-Section Analysis[/]")
|
116 |
-
|
117 |
-
for section in sections:
|
118 |
-
section_type = section.get("section_type", "Unknown")
|
119 |
-
section_number = section.get("section_number", "")
|
120 |
-
logger.debug(f"Processing section: {section_type} {section_number}")
|
121 |
-
section_title = f"{section_type.title()} {section_number}" if section_number else section_type.title()
|
122 |
-
|
123 |
-
section_analysis = section.get("analysis", "No analysis available")
|
124 |
-
lines = section.get("lines", [])
|
125 |
-
logger.debug(f"Section lines count: {len(lines) if lines else 0}")
|
126 |
-
|
127 |
-
# Create a group with the lyrics and analysis
|
128 |
-
section_content = []
|
129 |
-
|
130 |
-
if lines:
|
131 |
-
# Format lyrics in a more readable way
|
132 |
-
section_content.append(Text("Lyrics:", style="bold blue"))
|
133 |
-
# Форматируем каждую строку лирики с стилем quote
|
134 |
-
lyrics_lines = []
|
135 |
-
for line in lines:
|
136 |
-
lyrics_lines.append(f"[quote]{line}[/]")
|
137 |
-
|
138 |
-
lyrics_panel = Panel(
|
139 |
-
"\n".join(lyrics_lines),
|
140 |
-
border_style="blue",
|
141 |
-
padding=(1, 2)
|
142 |
-
)
|
143 |
-
section_content.append(lyrics_panel)
|
144 |
-
|
145 |
-
section_content.append(Text("Analysis:", style="bold blue"))
|
146 |
-
section_content.append(Text(section_analysis))
|
147 |
-
|
148 |
-
# Add the section panel
|
149 |
-
console.print(Panel(
|
150 |
-
Group(*section_content),
|
151 |
-
title=f"[bold cyan]{section_title}[/]",
|
152 |
-
border_style="cyan"
|
153 |
-
))
|
154 |
-
|
155 |
-
# We no longer have significant_lines in the new format
|
156 |
-
|
157 |
-
# Conclusion
|
158 |
-
conclusion = analysis.get("conclusion", "No conclusion available")
|
159 |
-
logger.debug(f"Conclusion content: {conclusion if conclusion else 'Not available'}")
|
160 |
-
console.print("\n[heading]Conclusion[/]")
|
161 |
-
console.print(Panel(conclusion, border_style="magenta"))
|
162 |
-
|
163 |
-
# Save output to file and read back as string
|
164 |
-
temp_file.close()
|
165 |
-
with open(temp_file.name, 'r', encoding='utf-8') as f:
|
166 |
-
result = f.read()
|
167 |
-
# Clean up the temp file
|
168 |
-
import os
|
169 |
-
try:
|
170 |
-
os.unlink(temp_file.name)
|
171 |
-
except Exception as e:
|
172 |
-
logger.warning(f"Could not delete temporary file {temp_file.name}: {str(e)}")
|
173 |
-
return result
|
174 |
-
|
175 |
-
except Exception as e:
|
176 |
-
error_traceback = traceback.format_exc()
|
177 |
-
logger.error(f"Error in rich formatting: {str(e)}\nTraceback:\n{error_traceback}")
|
178 |
-
# Log the current state of the analysis object for debugging
|
179 |
-
if isinstance(analysis, dict):
|
180 |
-
for key, value in analysis.items():
|
181 |
-
logger.debug(f"Key: {key}, Value type: {type(value)}, Value: {str(value)[:100]}{'...' if len(str(value)) > 100 else ''}")
|
182 |
-
# Fall back to simple formatting if rich formatting fails
|
183 |
-
logger.info("Falling back to simple text formatting")
|
184 |
-
pretty = False
|
185 |
-
|
186 |
-
if not pretty:
|
187 |
-
# Simple text formatting
|
188 |
-
formatted_text = []
|
189 |
-
|
190 |
-
# Summary
|
191 |
-
formatted_text.append("SONG ANALYSIS SUMMARY")
|
192 |
-
formatted_text.append("====================")
|
193 |
-
formatted_text.append(analysis.get("summary", "No summary available"))
|
194 |
-
formatted_text.append("")
|
195 |
|
196 |
-
|
197 |
-
themes = analysis.get("main_themes", [])
|
198 |
-
if themes:
|
199 |
-
formatted_text.append("MAIN THEMES")
|
200 |
-
formatted_text.append("===========")
|
201 |
-
for theme in themes:
|
202 |
-
formatted_text.append(f"• {theme}")
|
203 |
-
formatted_text.append("")
|
204 |
-
|
205 |
-
# Mood
|
206 |
-
mood = analysis.get("mood", "Not specified")
|
207 |
-
formatted_text.append("MOOD")
|
208 |
-
formatted_text.append("====")
|
209 |
-
formatted_text.append(mood)
|
210 |
-
formatted_text.append("")
|
211 |
-
|
212 |
-
# Sections analysis
|
213 |
-
sections = analysis.get("sections_analysis", [])
|
214 |
-
if sections:
|
215 |
-
formatted_text.append("SECTION-BY-SECTION ANALYSIS")
|
216 |
-
formatted_text.append("==========================")
|
217 |
-
|
218 |
-
for i, section in enumerate(sections):
|
219 |
-
section_type = section.get("section_type", "Unknown")
|
220 |
-
section_number = section.get("section_number", i+1)
|
221 |
-
section_analysis = section.get("analysis", "No analysis available")
|
222 |
-
|
223 |
-
formatted_text.append(f"{section_type.upper()} {section_number}")
|
224 |
-
formatted_text.append("-" * (len(section_type) + len(str(section_number)) + 1))
|
225 |
-
|
226 |
-
# Format the section lines
|
227 |
-
lines = section.get("lines", [])
|
228 |
-
if lines:
|
229 |
-
formatted_text.append("Lyrics:")
|
230 |
-
for line in lines:
|
231 |
-
formatted_text.append(f"> {line}")
|
232 |
-
formatted_text.append("")
|
233 |
-
|
234 |
-
formatted_text.append("Analysis:")
|
235 |
-
formatted_text.append(section_analysis)
|
236 |
-
formatted_text.append("")
|
237 |
-
|
238 |
-
# We no longer have significant_lines in the new format
|
239 |
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
|
246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
Formatting tools for displaying analysis results with rich formatting.
|
3 |
"""
|
4 |
|
5 |
+
import os
|
6 |
+
import tempfile
|
7 |
import traceback
|
8 |
+
from typing import Dict, Any, Tuple
|
9 |
from loguru import logger
|
10 |
from smolagents import Tool
|
11 |
from rich.console import Console
|
|
|
33 |
Formats the analysis results for better readability.
|
34 |
|
35 |
Args:
|
36 |
+
analysis_json: Dictionary containing the analysis results
|
37 |
+
pretty: Parameter kept for API compatibility but not used anymore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
Returns:
|
40 |
A formatted string representation of the analysis
|
41 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
|
43 |
+
analysis = analysis_json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
+
logger.debug(f"Analysis structure keys: {list(analysis.keys()) if isinstance(analysis, dict) else 'Not a dictionary'}")
|
46 |
+
|
47 |
+
try:
|
48 |
+
return self._format_rich(analysis)
|
49 |
+
except Exception as e:
|
50 |
+
error_traceback = traceback.format_exc()
|
51 |
+
logger.error(f"Error in rich formatting: {str(e)}\nTraceback:\n{error_traceback}")
|
52 |
+
self._log_analysis_debug_info(analysis)
|
53 |
+
# Return error message instead of falling back to simple formatting
|
54 |
+
return f"Error formatting analysis results: {str(e)}"
|
55 |
+
|
56 |
+
|
57 |
+
def _log_analysis_debug_info(self, analysis: Dict) -> None:
|
58 |
+
"""Log debug information about the analysis dictionary"""
|
59 |
+
if isinstance(analysis, dict):
|
60 |
+
for key, value in analysis.items():
|
61 |
+
logger.debug(f"Key: {key}, Value type: {type(value)}, Value: {str(value)[:100]}{'...' if len(str(value)) > 100 else ''}")
|
62 |
+
|
63 |
+
def _create_rich_console(self) -> Tuple[Console, Any]:
|
64 |
+
"""Create and configure a Rich console with a temporary file"""
|
65 |
+
logger.debug("Starting rich formatting")
|
66 |
+
temp_file = tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', delete=False)
|
67 |
+
# Reduce console width for better display in Gradio UI
|
68 |
+
console = Console(file=temp_file, width=70)
|
69 |
+
|
70 |
+
# Create a custom theme for consistent styling
|
71 |
+
custom_theme = Theme({
|
72 |
+
"heading": "bold cyan underline",
|
73 |
+
"highlight": "bold yellow",
|
74 |
+
"positive": "green",
|
75 |
+
"negative": "red",
|
76 |
+
"neutral": "magenta",
|
77 |
+
"quote": "italic yellow",
|
78 |
+
"metadata": "dim white",
|
79 |
+
"conclusion": "bold magenta"
|
80 |
+
})
|
81 |
+
|
82 |
+
# Apply the theme to our console
|
83 |
+
console.theme = custom_theme
|
84 |
+
return console, temp_file
|
85 |
+
|
86 |
+
def _format_summary(self, console: Console, analysis: Dict) -> None:
|
87 |
+
"""Format and print the summary section"""
|
88 |
+
summary = analysis.get("summary", "No summary available")
|
89 |
+
logger.debug(f"Summary content: {summary if summary else 'Not available'}")
|
90 |
+
console.print(Panel(
|
91 |
+
Text(summary, justify="center"),
|
92 |
+
title="[heading]Song Analysis[/]",
|
93 |
+
subtitle="[metadata]Summary[/]",
|
94 |
+
expand=True # Расширять панель на всю доступную ширину
|
95 |
+
))
|
96 |
+
|
97 |
+
def _format_info_table(self, console: Console, analysis: Dict) -> None:
|
98 |
+
"""Format and print the info table with themes and mood"""
|
99 |
+
info_table = Table(show_header=False, box=ROUNDED, expand=True)
|
100 |
+
info_table.add_column("Key", style="bold blue")
|
101 |
+
info_table.add_column("Value")
|
102 |
+
|
103 |
+
# Add the mood
|
104 |
+
mood = analysis.get("mood", "Not specified")
|
105 |
+
logger.debug(f"Mood content: {mood if mood else 'Not specified'}")
|
106 |
+
info_table.add_row("Mood", mood)
|
107 |
+
|
108 |
+
# Add the themes as a comma-separated list
|
109 |
+
themes = analysis.get("main_themes", [])
|
110 |
+
logger.debug(f"Themes: {themes if themes else 'Not available'}")
|
111 |
+
if themes:
|
112 |
+
themes_text = ", ".join([f"[highlight]{theme}[/]" for theme in themes])
|
113 |
+
info_table.add_row("Main Themes", themes_text)
|
114 |
+
|
115 |
+
console.print(info_table)
|
116 |
+
|
117 |
+
def _format_section(self, console: Console, section: Dict) -> None:
|
118 |
+
"""Format and print a single section"""
|
119 |
+
section_type = section.get("section_type", "Unknown")
|
120 |
+
section_number = section.get("section_number", "")
|
121 |
+
logger.debug(f"Processing section: {section_type} {section_number}")
|
122 |
+
section_title = f"{section_type.title()} {section_number}" if section_number else section_type.title()
|
123 |
+
|
124 |
+
section_analysis = section.get("analysis", "No analysis available")
|
125 |
+
lines = section.get("lines", [])
|
126 |
+
logger.debug(f"Section lines count: {len(lines) if lines else 0}")
|
127 |
+
|
128 |
+
# Create a group with the lyrics and analysis
|
129 |
+
section_content = []
|
130 |
+
|
131 |
+
if lines:
|
132 |
+
# Format lyrics in a more readable way
|
133 |
+
section_content.append(Text("Lyrics:", style="bold blue"))
|
134 |
+
# Format each lyrics line with the quote style
|
135 |
+
lyrics_lines = []
|
136 |
+
for line in lines:
|
137 |
+
lyrics_lines.append(f"[quote]{line}[/]")
|
138 |
|
139 |
+
lyrics_panel = Panel(
|
140 |
+
"\n".join(lyrics_lines),
|
141 |
+
border_style="blue",
|
142 |
+
padding=(1, 2),
|
143 |
+
expand=True # Расширять панель на всю доступную ширину
|
144 |
+
)
|
145 |
+
section_content.append(lyrics_panel)
|
146 |
+
|
147 |
+
section_content.append(Text("Analysis:", style="bold blue"))
|
148 |
+
section_content.append(Text(section_analysis))
|
149 |
+
|
150 |
+
# Add the section panel
|
151 |
+
console.print(Panel(
|
152 |
+
Group(*section_content),
|
153 |
+
title=f"[bold cyan]{section_title}[/]",
|
154 |
+
border_style="cyan",
|
155 |
+
expand=True # Расширять панель на всю доступную ширину
|
156 |
+
))
|
157 |
+
|
158 |
+
def _format_sections(self, console: Console, analysis: Dict) -> None:
|
159 |
+
"""Format and print all sections"""
|
160 |
+
sections = analysis.get("sections_analysis", [])
|
161 |
+
logger.debug(f"Sections count: {len(sections) if sections else 0}")
|
162 |
+
if sections:
|
163 |
+
console.print("\n[heading]Section-by-Section Analysis[/]")
|
164 |
+
for section in sections:
|
165 |
+
self._format_section(console, section)
|
166 |
+
|
167 |
+
def _format_conclusion(self, console: Console, analysis: Dict) -> None:
|
168 |
+
"""Format and print the conclusion"""
|
169 |
+
conclusion = analysis.get("conclusion", "No conclusion available")
|
170 |
+
logger.debug(f"Conclusion content: {conclusion if conclusion else 'Not available'}")
|
171 |
+
console.print("\n[heading]Conclusion[/]")
|
172 |
+
console.print(Panel(
|
173 |
+
Text(conclusion, justify="left"),
|
174 |
+
border_style="magenta",
|
175 |
+
expand=True # Расширять панель на всю доступную ширину
|
176 |
+
))
|
177 |
+
|
178 |
+
def _format_rich(self, analysis: Dict) -> str:
|
179 |
+
"""Format the analysis using rich formatting"""
|
180 |
+
console, temp_file = self._create_rich_console()
|
181 |
+
|
182 |
+
# Format each section
|
183 |
+
self._format_summary(console, analysis)
|
184 |
+
self._format_info_table(console, analysis)
|
185 |
+
self._format_sections(console, analysis)
|
186 |
+
self._format_conclusion(console, analysis)
|
187 |
+
|
188 |
+
# Save output to file and read back as string
|
189 |
+
temp_file.close()
|
190 |
+
with open(temp_file.name, 'r', encoding='utf-8') as f:
|
191 |
+
result = f.read()
|
192 |
+
|
193 |
+
# Градио не поддерживает HTML-теги pre, поэтому просто возвращаем текст как есть
|
194 |
+
# Текстовое форматирование Rich уже содержит необходимые отступы и пробелы
|
195 |
+
|
196 |
+
# Clean up the temp file
|
197 |
+
try:
|
198 |
+
os.unlink(temp_file.name)
|
199 |
+
except Exception as e:
|
200 |
+
logger.warning(f"Could not delete temporary file {temp_file.name}: {str(e)}")
|
201 |
+
|
202 |
+
return result
|
203 |
+
|
204 |
+
|