Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/holzerjm/civichacks-demo/llms.txt

Use this file to discover all available pages before exploring further.

The CivicHacks demo uses Gradio to create a polished web interface. You can customize the theme, colors, layout, and components to match your project’s branding.

Theme customization

Gradio provides built-in themes that you can customize with your own colors:

Change the theme

THEME = gr.themes.Soft(primary_hue="red", secondary_hue="slate")

app.launch(
    server_name="0.0.0.0",
    server_port=args.port,
    share=args.share,
    theme=THEME,
    css=CSS,
)
The theme is defined at line 210 and applied at line 289 in scripts/demo_step3_app.py.
Available theme bases:
  • gr.themes.Soft - Rounded corners, soft shadows (used in demo)
  • gr.themes.Glass - Glassmorphism effect
  • gr.themes.Monochrome - Black and white
  • gr.themes.Base - Minimal default styling

Available color hues

Gradio supports these built-in color options:
  • red (demo default)
  • blue
  • green
  • yellow
  • orange
  • pink
  • purple
  • gray
  • slate
  • zinc
  • emerald
  • teal
  • cyan
  • sky
  • indigo
  • violet
  • fuchsia
  • rose

Custom CSS styling

The demo includes custom CSS for header and footer styling:
CSS = """
    .header { text-align: center; margin-bottom: 1rem; }
    .header h1 { color: #CC0000; margin-bottom: 0.25rem; }
    .header h3 { color: #555; margin-top: 0; margin-bottom: 0.5rem; font-weight: normal; }
    .footer { text-align: center; font-size: 0.85rem; color: #888; margin-top: 1rem; }
"""
The CSS is defined at line 211-216 and applied at line 290 in scripts/demo_step3_app.py.

Modify the header

The dynamic header updates when users switch tracks:
def build_header_html(track_name):
    """Build the header HTML for a given track."""
    desc = TRACK_DESCRIPTIONS.get(track_name, "")
    return f"""
    <div class="header">
        <h1>🏛️ CivicHacks AI Assistant</h1>
        <h3>{track_name}</h3>
        <p>{desc}<br>
        Powered by <strong>open source AI</strong> running locally on <strong>{HOSTNAME}</strong><br>
        <em>Started: {STARTED_AT}</em> — no cloud, no cost, no data leaving this machine.</p>
    </div>
    """
The build_header_html function is at line 130-141 in scripts/demo_step3_app.py.

Customize track descriptions

Update the descriptions shown when users switch tracks:
scripts/demo_step3_app.py
TRACK_DESCRIPTIONS = {
    "🌿 EcoHack — Boston Environment": "Explore Boston's environmental quality, climate risks, and environmental justice data.",
    "🏙️ CityHack — Boston 311 Services": "Analyze Boston 311 service requests, response times, and equity in city services.",
    "📚 EduHack — Boston Public Schools": "Investigate achievement gaps, transportation barriers, and equity in Boston schools.",
    "⚖️ JusticeHack — MA Criminal Justice": "Examine pretrial detention, reentry programs, and policing patterns in Massachusetts.",
}
Replace with your own descriptions to match your data and use case.

Modify example questions

The clickable example questions update dynamically per track:
EXAMPLE_QUESTIONS = {
    "🌿 EcoHack — Boston Environment": [
        "Which neighborhoods face the worst environmental injustice?",
        "What are the biggest climate threats to Boston?",
        "How does tree canopy coverage affect neighborhood temperatures?",
    ],
}
Update the EXAMPLE_QUESTIONS dictionary at line 63-84 in scripts/demo_step3_app.py.

Change the chatbot appearance

Customize the chat interface:
chatbot = gr.Chatbot(
    label="Civic AI Chat",
    height=420,
    avatar_images=(None, "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Red_Hat_logo.svg/120px-Red_Hat_logo.svg.png"),
)
The chatbot component is defined at line 231-235 in scripts/demo_step3_app.py.
You can use emoji as avatars by passing a single emoji character instead of a URL:
avatar_images=("👤", "🤖")
Update the footer content:
gr.HTML(f"""
<div class="footer">
    <strong>Stack:</strong> Ollama + LlamaIndex + Gradio ·
    <strong>Model:</strong> Llama 3.1 8B ·
    <strong>Host:</strong> {HOSTNAME} ·
    <strong>Data Privacy:</strong> 100% local ·
    <strong>Cost:</strong> per-query estimate shown in each response<br>
    Built for <strong>CivicHacks 2026</strong> at Boston University ·
    Templates at <a href="https://aitemplates.io" target="_blank">aitemplates.io</a>
</div>
""")
The footer HTML is at line 252-261 in scripts/demo_step3_app.py.

Change input placeholder text

Customize the question input field:
scripts/demo_step3_app.py
question_input = gr.Textbox(
    label="Your Question",
    placeholder="Ask anything about the civic data...",  # Change this
    scale=4,
    lines=1,
)
Update to match your use case:
placeholder="What would you like to know about your city?"

Adjust layout and sizing

Modify component sizes and arrangement:
1

Change chatbot height

chatbot = gr.Chatbot(
    height=600,  # Increase from 420 to 600
)
2

Adjust input/button ratio

with gr.Row():
    question_input = gr.Textbox(
        scale=5,  # Changed from 4 (wider input)
    )
    submit_btn = gr.Button(
        "Ask",
        variant="primary",
        scale=1,  # Button stays small
    )
3

Reorder components

Move the track selector below the chat:
with gr.Blocks(title="CivicHacks AI Assistant") as app:
    header = gr.HTML(build_header_html(default_track))
    
    chatbot = gr.Chatbot(...)
    
    # Move track selector here (after chat)
    with gr.Row():
        track_selector = gr.Dropdown(...)

Update page title

Change the browser tab title:
scripts/demo_step3_app.py
with gr.Blocks(title="CivicHacks AI Assistant") as app:  # Change this
Update to your project name:
with gr.Blocks(title="My City AI") as app:

Dynamic updates on track change

The demo updates the header and examples when users switch tracks. This happens in the on_track_change function:
scripts/demo_step3_app.py
def on_track_change(track_name):
    """Update header and example questions when track changes."""
    header = build_header_html(track_name)
    questions = EXAMPLE_QUESTIONS.get(track_name, [])
    dataset = gr.Dataset(samples=[[q] for q in questions])
    return header, dataset

# Wire it up
track_selector.change(
    fn=on_track_change,
    inputs=[track_selector],
    outputs=[header, examples.dataset],
)
You can extend this to update other components when the track changes.

Next steps

Swap in your own data

Replace the demo datasets with your files

Change the AI model

Try different models for speed or quality

Build docs developers (and LLMs) love