NotebookLM / theme.py
internomega-terrablue
Claude Sonnet 4.6
fix: target Gradio 4 chatbot wrapper for compact user bubble
ba4c4fa
Raw History Blame Contribute Delete
15.2 kB
"""Dark theme, custom CSS, and logo SVGs for the NotebookLM Gradio app."""
import base64
import gradio as gr
# ── Logo SVGs ────────────────────────────────────────────────────────────────
LOGO_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 280 60">
<defs>
<linearGradient id="lg1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea"/>
<stop offset="100%" style="stop-color:#764ba2"/>
</linearGradient>
<linearGradient id="lg2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#a78bfa"/>
<stop offset="100%" style="stop-color:#667eea"/>
</linearGradient>
<linearGradient id="sp" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#fbbf24"/>
<stop offset="100%" style="stop-color:#f59e0b"/>
</linearGradient>
</defs>
<g transform="translate(4,6)">
<rect x="2" y="4" width="36" height="44" rx="4" fill="url(#lg1)"/>
<rect x="2" y="4" width="8" height="44" rx="3" fill="url(#lg2)" opacity="0.7"/>
<line x1="16" y1="16" x2="32" y2="16" stroke="rgba(255,255,255,0.5)" stroke-width="1.8" stroke-linecap="round"/>
<line x1="16" y1="23" x2="30" y2="23" stroke="rgba(255,255,255,0.4)" stroke-width="1.8" stroke-linecap="round"/>
<line x1="16" y1="30" x2="32" y2="30" stroke="rgba(255,255,255,0.5)" stroke-width="1.8" stroke-linecap="round"/>
<line x1="16" y1="37" x2="28" y2="37" stroke="rgba(255,255,255,0.4)" stroke-width="1.8" stroke-linecap="round"/>
<g transform="translate(32,2)">
<path d="M6 0 L7.5 4.5 L12 6 L7.5 7.5 L6 12 L4.5 7.5 L0 6 L4.5 4.5 Z" fill="url(#sp)"/>
<path d="M14 8 L14.8 10.2 L17 11 L14.8 11.8 L14 14 L13.2 11.8 L11 11 L13.2 10.2 Z" fill="#fbbf24" opacity="0.7"/>
</g>
</g>
<text x="56" y="28" font-family="Inter,-apple-system,sans-serif" font-size="22" font-weight="700">
<tspan fill="url(#lg1)">Notebook</tspan><tspan fill="#a78bfa" font-weight="800">LM</tspan>
</text>
<text x="57" y="46" font-family="Inter,-apple-system,sans-serif" font-size="10.5" fill="#8888aa" font-weight="400" letter-spacing="0.8">
AI-Powered Study Companion
</text>
</svg>"""
LOGO_ICON_SVG = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 60">
<defs>
<linearGradient id="ig1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea"/>
<stop offset="100%" style="stop-color:#764ba2"/>
</linearGradient>
<linearGradient id="ig2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#a78bfa"/>
<stop offset="100%" style="stop-color:#667eea"/>
</linearGradient>
<linearGradient id="isp" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#fbbf24"/>
<stop offset="100%" style="stop-color:#f59e0b"/>
</linearGradient>
</defs>
<g transform="translate(2,4)">
<rect x="2" y="4" width="36" height="44" rx="5" fill="url(#ig1)"/>
<rect x="2" y="4" width="9" height="44" rx="4" fill="url(#ig2)" opacity="0.7"/>
<line x1="16" y1="16" x2="32" y2="16" stroke="rgba(255,255,255,0.55)" stroke-width="2" stroke-linecap="round"/>
<line x1="16" y1="23" x2="30" y2="23" stroke="rgba(255,255,255,0.4)" stroke-width="2" stroke-linecap="round"/>
<line x1="16" y1="30" x2="32" y2="30" stroke="rgba(255,255,255,0.55)" stroke-width="2" stroke-linecap="round"/>
<line x1="16" y1="37" x2="28" y2="37" stroke="rgba(255,255,255,0.4)" stroke-width="2" stroke-linecap="round"/>
<g transform="translate(30,0)">
<path d="M7 0 L8.8 5.3 L14 7 L8.8 8.8 L7 14 L5.2 8.8 L0 7 L5.2 5.3 Z" fill="url(#isp)"/>
<path d="M16 9 L17 11.5 L19.5 12.5 L17 13.5 L16 16 L15 13.5 L12.5 12.5 L15 11.5 Z" fill="#fbbf24" opacity="0.7"/>
</g>
</g>
</svg>"""
def get_logo_b64(svg_str: str) -> str:
return base64.b64encode(svg_str.encode()).decode()
LOGO_B64 = get_logo_b64(LOGO_SVG)
ICON_B64 = get_logo_b64(LOGO_ICON_SVG)
# ── Gradio Dark Theme ────────────────────────────────────────────────────────
dark_theme = gr.themes.Base(
primary_hue=gr.themes.colors.indigo,
secondary_hue=gr.themes.colors.purple,
neutral_hue=gr.themes.colors.slate,
font=gr.themes.GoogleFont("Inter"),
).set(
body_background_fill="#0e1117",
body_background_fill_dark="#0e1117",
block_background_fill="rgba(255,255,255,0.02)",
block_background_fill_dark="rgba(255,255,255,0.02)",
block_border_color="rgba(255,255,255,0.08)",
block_border_color_dark="rgba(255,255,255,0.08)",
block_label_text_color="#a0a0b8",
block_label_text_color_dark="#a0a0b8",
block_title_text_color="#e0e0f0",
block_title_text_color_dark="#e0e0f0",
body_text_color="#c8c8d8",
body_text_color_dark="#c8c8d8",
button_primary_background_fill="linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
button_primary_background_fill_dark="linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
button_primary_text_color="white",
button_primary_text_color_dark="white",
button_secondary_background_fill="rgba(255,255,255,0.04)",
button_secondary_background_fill_dark="rgba(255,255,255,0.04)",
button_secondary_border_color="rgba(255,255,255,0.12)",
button_secondary_border_color_dark="rgba(255,255,255,0.12)",
button_secondary_text_color="#d0d0e0",
button_secondary_text_color_dark="#d0d0e0",
border_color_primary="rgba(255,255,255,0.08)",
border_color_primary_dark="rgba(255,255,255,0.08)",
input_background_fill="rgba(255,255,255,0.03)",
input_background_fill_dark="rgba(255,255,255,0.03)",
input_border_color="rgba(255,255,255,0.1)",
input_border_color_dark="rgba(255,255,255,0.1)",
shadow_drop="none",
shadow_drop_lg="none",
shadow_spread="none",
)
# ── Custom CSS ───────────────────────────────────────────────────────────────
CUSTOM_CSS = """
/* ── Import Google Font ── */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
/* ── Sidebar ── */
#sidebar {
background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%) !important;
border-right: 1px solid rgba(255,255,255,0.06);
padding: 16px !important;
min-height: 100vh;
position: sticky;
top: 0;
align-self: flex-start;
max-height: 100vh;
overflow-y: auto;
border-radius: 0 !important;
}
#sidebar .gr-button-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
border-radius: 10px !important;
}
#sidebar .gr-button-secondary {
background: rgba(255,255,255,0.05) !important;
border: 1px solid rgba(255,255,255,0.1) !important;
border-radius: 10px !important;
color: #d0d0e0 !important;
}
/* ── Notebook selector radio ── */
#notebook-selector label {
border-radius: 10px !important;
padding: 8px 14px !important;
transition: all 0.2s ease !important;
font-size: 0.85rem !important;
}
#notebook-selector label.selected {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
}
/* ── Tab styling ── */
.tabs > .tab-nav > button {
border-radius: 10px !important;
padding: 10px 24px !important;
font-weight: 500 !important;
font-size: 0.9rem !important;
}
.tabs > .tab-nav > button.selected {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
}
/* ── Chat ── */
#chatbot {
border-radius: 14px !important;
border: 1px solid rgba(255,255,255,0.08) !important;
}
#chatbot .message {
border-radius: 14px !important;
padding: 14px 18px !important;
}
/* User bubble β€” shrink to fit content */
#chatbot .bot-row { justify-content: flex-start !important; }
#chatbot .user-row { justify-content: flex-end !important; }
#chatbot .user-row .message-bubble-border {
max-width: fit-content !important;
}
#chatbot .message.user {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
border: none !important;
box-shadow: 0 4px 14px rgba(102,126,234,0.25);
max-width: fit-content !important;
}
/* Assistant bubble */
#chatbot .message.bot {
background: rgba(40, 44, 66, 0.85) !important;
border: 1px solid rgba(102, 126, 234, 0.3) !important;
color: #c8c8d8 !important;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.25);
border-radius: 14px !important;
padding: 14px 18px !important;
backdrop-filter: blur(6px);
}
/* ── Cards ── */
.source-card {
display: flex;
align-items: center;
gap: 16px;
padding: 16px 20px;
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 14px;
margin-bottom: 10px;
transition: all 0.2s ease;
}
.source-card:hover {
border-color: rgba(102,126,234,0.3);
background: rgba(255,255,255,0.04);
}
.source-icon {
width: 48px; height: 48px; border-radius: 12px;
display: flex; align-items: center; justify-content: center;
font-size: 1.5rem; flex-shrink: 0;
}
.source-icon.pdf { background: rgba(239,68,68,0.15); }
.source-icon.pptx { background: rgba(249,115,22,0.15); }
.source-icon.txt { background: rgba(59,130,246,0.15); }
.source-icon.url { background: rgba(34,197,94,0.15); }
.source-icon.youtube { background: rgba(239,68,68,0.15); }
.source-info { flex: 1; min-width: 0; }
.source-info .name {
font-weight: 600; font-size: 0.95rem; color: #e0e0f0;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.source-info .meta { font-size: 0.8rem; color: #707088; margin-top: 2px; }
.source-badge {
padding: 4px 12px; border-radius: 20px; font-size: 0.75rem;
font-weight: 600; letter-spacing: 0.3px;
}
.source-badge.ready { background: rgba(34,197,94,0.15); color: #22c55e; }
.source-badge.processing {
background: rgba(234,179,8,0.15); color: #eab308;
animation: pulse-badge 1.5s ease-in-out infinite;
}
.source-badge.failed { background: rgba(239,68,68,0.15); color: #ef4444; cursor: help; }
@keyframes pulse-badge {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* ── Welcome hero ── */
.welcome-hero {
text-align: center; padding: 80px 40px;
background: linear-gradient(135deg, rgba(102,126,234,0.08) 0%, rgba(118,75,162,0.08) 100%);
border-radius: 20px; border: 1px solid rgba(102,126,234,0.15); margin: 20px 0;
}
.welcome-hero h1 {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
font-size: 2.5rem; font-weight: 700; margin-bottom: 12px;
}
.welcome-hero p { color: #9090a8; font-size: 1.1rem; line-height: 1.6; }
/* ── Empty state ── */
.empty-state {
text-align: center; padding: 60px 30px; color: #707088;
}
.empty-state h3 { color: #a0a0b8; margin-bottom: 8px; font-weight: 600; }
.empty-state p { font-size: 0.95rem; line-height: 1.5; }
/* ── Notebook header ── */
.notebook-header {
padding: 0 0 16px 0; margin-bottom: 16px;
border-bottom: 1px solid rgba(255,255,255,0.06);
}
.notebook-header h2 {
font-weight: 700; font-size: 1.5rem; margin: 0; color: #e8e8f8;
}
.notebook-header .meta { font-size: 0.85rem; color: #707088; margin-top: 4px; }
/* ── Citation chip ── */
.citation-chip {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 14px; background: rgba(102,126,234,0.1);
border: 1px solid rgba(102,126,234,0.2); border-radius: 20px;
font-size: 0.8rem; color: #a0b0f0; margin: 3px 4px;
}
/* ── Artifact section header ── */
.artifact-section-header {
display: flex; align-items: center; gap: 10px; margin-bottom: 12px;
}
.artifact-section-icon {
width: 36px; height: 36px; border-radius: 10px;
display: flex; align-items: center; justify-content: center; font-size: 1.1rem;
}
/* ── Locked state ── */
.locked-state {
text-align: center; padding: 50px 30px;
background: rgba(255,255,255,0.02);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 16px;
}
/* ── File uploader ── */
.gr-file-upload {
border-radius: 14px !important;
border: 2px dashed rgba(102,126,234,0.3) !important;
background: rgba(102,126,234,0.03) !important;
}
/* ── Primary button ── */
.gr-button-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important; border-radius: 10px !important;
font-weight: 600 !important;
}
.gr-button-primary:hover {
opacity: 0.9 !important;
transform: translateY(-1px) !important;
box-shadow: 0 4px 15px rgba(102,126,234,0.3) !important;
}
/* ── Source delete dropdown ── */
#source-delete-dropdown {
border: 1px solid rgba(102,126,234,0.3);
border-radius: 12px;
background: rgba(102,126,234,0.06);
}
#source-delete-dropdown label {
color: #a0a0f0 !important;
font-weight: 600;
}
#source-delete-dropdown input, #source-delete-dropdown .wrap {
background: rgba(14,17,23,0.8) !important;
border-color: rgba(102,126,234,0.25) !important;
color: #e0e0f0 !important;
border-radius: 10px !important;
}
#source-delete-dropdown ul {
background: #1a1d2e !important;
border: 1px solid rgba(102,126,234,0.3) !important;
border-radius: 10px !important;
}
#source-delete-dropdown li {
color: #c0c0e0 !important;
}
#source-delete-dropdown li:hover, #source-delete-dropdown li.selected {
background: rgba(102,126,234,0.15) !important;
color: #e0e0f0 !important;
}
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.25); }
/* ── Hide Gradio footer ── */
footer { display: none !important; }
/* ── Auth gate ── */
#auth-gate { max-width: 500px; margin: 100px auto; }
"""
# ── Reusable HTML Templates ──────────────────────────────────────────────────
WELCOME_HTML = f"""
<div class="welcome-hero">
<img src="data:image/svg+xml;base64,{ICON_B64}" style="width:64px; margin-bottom:16px;" />
<h1>NotebookLM</h1>
<p>Your AI-powered study companion.<br>
Sign in with your Hugging Face account to get started.</p>
</div>
"""
NO_NOTEBOOKS_HTML = """
<div class="welcome-hero">
<h1>NotebookLM</h1>
<p>Create a notebook from the sidebar to get started.</p>
</div>
"""
SIDEBAR_LOGO_HTML = f"""
<div style="padding: 8px 0 4px 0;">
<img src="data:image/svg+xml;base64,{LOGO_B64}" style="width:100%; max-width:240px;" />
</div>
"""