Spaces:
Sleeping
Sleeping
Update pages/artifacts.py
Browse files- pages/artifacts.py +71 -21
pages/artifacts.py
CHANGED
|
@@ -1,10 +1,35 @@
|
|
|
|
|
| 1 |
"""Artifacts tab: Summary, Podcast, and Quiz generation."""
|
| 2 |
|
|
|
|
|
|
|
| 3 |
from datetime import datetime
|
| 4 |
from state import UserData, get_active_notebook, get_all_artifacts, get_latest_artifact
|
| 5 |
from mock_data import generate_mock_artifact
|
| 6 |
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
def _format_time(iso_str: str) -> str:
|
| 9 |
try:
|
| 10 |
dt = datetime.fromisoformat(iso_str)
|
|
@@ -36,7 +61,8 @@ def _render_artifact_content(artifact) -> str:
|
|
| 36 |
f'border-radius:10px;">'
|
| 37 |
f'<div style="font-size:0.82rem; color:#8090d0; margin-bottom:8px;">π§ Podcast Audio</div>'
|
| 38 |
f'<audio controls style="width:100%; border-radius:6px;">'
|
| 39 |
-
f'<source src="/file={artifact.audio_path}" type="audio/
|
|
|
|
| 40 |
f'Your browser does not support the audio element.'
|
| 41 |
f'</audio>'
|
| 42 |
f'</div>'
|
|
@@ -47,10 +73,10 @@ def _render_artifact_content(artifact) -> str:
|
|
| 47 |
'background:rgba(102,126,234,0.06); border:1px solid rgba(102,126,234,0.15); '
|
| 48 |
'border-radius:10px; margin-top:8px;">'
|
| 49 |
'<span style="font-size:1.3rem;">π</span>'
|
| 50 |
-
'<span style="font-size:0.85rem; color:#8888aa;">Audio generation failed
|
| 51 |
-
'Install <code>bark</code> and <code>scipy</code> to enable TTS.</span>'
|
| 52 |
'</div>'
|
| 53 |
)
|
|
|
|
| 54 |
return html
|
| 55 |
|
| 56 |
|
|
@@ -96,17 +122,20 @@ def render_conv_summary_section(state: UserData) -> str:
|
|
| 96 |
if not nb:
|
| 97 |
return ""
|
| 98 |
if not nb.messages:
|
| 99 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
summaries = get_all_artifacts(nb, "conversation_summary")
|
| 101 |
if not summaries:
|
| 102 |
-
return "
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
md += "\n\n---\n\n**Previous summaries:** " + prev
|
| 109 |
-
return md
|
| 110 |
def handle_gen_conv_summary(style: str, state: UserData) -> tuple[UserData, str]:
|
| 111 |
nb = get_active_notebook(state)
|
| 112 |
if not nb or not nb.messages:
|
|
@@ -125,14 +154,12 @@ def render_doc_summary_section(state: UserData) -> str:
|
|
| 125 |
return ""
|
| 126 |
summaries = get_all_artifacts(nb, "document_summary")
|
| 127 |
if not summaries:
|
| 128 |
-
return "
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
md += "\n\n---\n\n**Previous summaries:** " + prev
|
| 135 |
-
return md
|
| 136 |
def handle_gen_doc_summary(style: str, state: UserData) -> tuple[UserData, str]:
|
| 137 |
nb = get_active_notebook(state)
|
| 138 |
if not nb:
|
|
@@ -263,4 +290,27 @@ def handle_gen_quiz(num_questions: int, state: UserData) -> tuple[UserData, str]
|
|
| 263 |
num_q = int(num_questions) if num_questions else 5
|
| 264 |
artifact = generate_quiz(nb, num_q)
|
| 265 |
nb.artifacts.append(artifact)
|
| 266 |
-
return state, render_quiz_section(state)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
"""Artifacts tab: Summary, Podcast, and Quiz generation."""
|
| 3 |
|
| 4 |
+
import os
|
| 5 |
+
import tempfile
|
| 6 |
from datetime import datetime
|
| 7 |
from state import UserData, get_active_notebook, get_all_artifacts, get_latest_artifact
|
| 8 |
from mock_data import generate_mock_artifact
|
| 9 |
|
| 10 |
|
| 11 |
+
def _save_artifact_to_temp_file(artifact) -> str | None:
|
| 12 |
+
"""Save artifact content to a temporary file. Returns the file path."""
|
| 13 |
+
try:
|
| 14 |
+
if artifact.type == "podcast" and artifact.audio_path:
|
| 15 |
+
if os.path.exists(artifact.audio_path):
|
| 16 |
+
return artifact.audio_path
|
| 17 |
+
else:
|
| 18 |
+
safe_title = artifact.title.replace(" ", "_")[:50]
|
| 19 |
+
# Save quizzes as HTML (they contain styled HTML), others as markdown
|
| 20 |
+
suffix = '.html' if artifact.type == "quiz" else '.md'
|
| 21 |
+
with tempfile.NamedTemporaryFile(
|
| 22 |
+
mode='w', suffix=suffix, prefix=f'{safe_title}_',
|
| 23 |
+
delete=False, encoding='utf-8'
|
| 24 |
+
) as f:
|
| 25 |
+
f.write(artifact.content)
|
| 26 |
+
return f.name
|
| 27 |
+
except Exception as e:
|
| 28 |
+
import logging
|
| 29 |
+
logging.error(f"Failed to save artifact: {e}")
|
| 30 |
+
return None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
def _format_time(iso_str: str) -> str:
|
| 34 |
try:
|
| 35 |
dt = datetime.fromisoformat(iso_str)
|
|
|
|
| 61 |
f'border-radius:10px;">'
|
| 62 |
f'<div style="font-size:0.82rem; color:#8090d0; margin-bottom:8px;">π§ Podcast Audio</div>'
|
| 63 |
f'<audio controls style="width:100%; border-radius:6px;">'
|
| 64 |
+
f'<source src="/gradio_api/file={artifact.audio_path}" type="audio/mpeg">'
|
| 65 |
+
f'<source src="/file={artifact.audio_path}" type="audio/mpeg">'
|
| 66 |
f'Your browser does not support the audio element.'
|
| 67 |
f'</audio>'
|
| 68 |
f'</div>'
|
|
|
|
| 73 |
'background:rgba(102,126,234,0.06); border:1px solid rgba(102,126,234,0.15); '
|
| 74 |
'border-radius:10px; margin-top:8px;">'
|
| 75 |
'<span style="font-size:1.3rem;">π</span>'
|
| 76 |
+
'<span style="font-size:0.85rem; color:#8888aa;">Audio generation failed. Verify <code>OPENAI_API_KEY</code> is set in Space secrets and your account has credits.</span>'
|
|
|
|
| 77 |
'</div>'
|
| 78 |
)
|
| 79 |
+
|
| 80 |
return html
|
| 81 |
|
| 82 |
|
|
|
|
| 122 |
if not nb:
|
| 123 |
return ""
|
| 124 |
if not nb.messages:
|
| 125 |
+
return (
|
| 126 |
+
'<div style="text-align:center; padding:30px 20px; color:#606078; '
|
| 127 |
+
'border:1px dashed rgba(255,255,255,0.08); border-radius:14px;">'
|
| 128 |
+
'<p style="margin:0;">No conversation yet. Start chatting in the <strong>Chat</strong> tab first.</p>'
|
| 129 |
+
'</div>'
|
| 130 |
+
)
|
| 131 |
summaries = get_all_artifacts(nb, "conversation_summary")
|
| 132 |
if not summaries:
|
| 133 |
+
return '<p style="color:#606078; text-align:center; padding:20px;">Click "Generate" to create a conversation summary.</p>'
|
| 134 |
+
html = _render_artifact_content(summaries[0])
|
| 135 |
+
html += _render_history(summaries, "conversation summaries")
|
| 136 |
+
return html
|
| 137 |
+
|
| 138 |
+
|
|
|
|
|
|
|
| 139 |
def handle_gen_conv_summary(style: str, state: UserData) -> tuple[UserData, str]:
|
| 140 |
nb = get_active_notebook(state)
|
| 141 |
if not nb or not nb.messages:
|
|
|
|
| 154 |
return ""
|
| 155 |
summaries = get_all_artifacts(nb, "document_summary")
|
| 156 |
if not summaries:
|
| 157 |
+
return '<p style="color:#606078; text-align:center; padding:20px;">Click "Generate" to create a document summary.</p>'
|
| 158 |
+
html = _render_artifact_content(summaries[0])
|
| 159 |
+
html += _render_history(summaries, "document summaries")
|
| 160 |
+
return html
|
| 161 |
+
|
| 162 |
+
|
|
|
|
|
|
|
| 163 |
def handle_gen_doc_summary(style: str, state: UserData) -> tuple[UserData, str]:
|
| 164 |
nb = get_active_notebook(state)
|
| 165 |
if not nb:
|
|
|
|
| 290 |
num_q = int(num_questions) if num_questions else 5
|
| 291 |
artifact = generate_quiz(nb, num_q)
|
| 292 |
nb.artifacts.append(artifact)
|
| 293 |
+
return state, render_quiz_section(state)
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
# ββ Download Handlers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 297 |
+
|
| 298 |
+
def _find_artifact_by_id(state: UserData, artifact_id: str):
|
| 299 |
+
"""Find an artifact by ID across all notebooks (current one only for now)."""
|
| 300 |
+
nb = get_active_notebook(state)
|
| 301 |
+
if not nb:
|
| 302 |
+
return None
|
| 303 |
+
for artifact in nb.artifacts:
|
| 304 |
+
if artifact.id == artifact_id:
|
| 305 |
+
return artifact
|
| 306 |
+
return None
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def download_artifact(artifact_id: str, state: UserData) -> str | None:
|
| 310 |
+
"""Download an artifact and return the file path for Gradio download."""
|
| 311 |
+
artifact = _find_artifact_by_id(state, artifact_id)
|
| 312 |
+
if not artifact:
|
| 313 |
+
return None
|
| 314 |
+
|
| 315 |
+
file_path = _save_artifact_to_temp_file(artifact)
|
| 316 |
+
return file_path
|