Intro to Solveit

code
deep learning
solveit
Author

AgentRichi

Published

January 8, 2026

Solveit is like a jupyter notebook environment where you can code along with your AI assistant - think pair programming but with an LLM model! Here is a link for more information: Solveit.

Jeremy Howard has also released a course on how to use the platform - I will be posting my notes as blog posts here. Below is a live dialogue example on how I used Solveit to help set up my automated blogging workflow. It’s almost like this blog built itself…

I want to pubish my course notes as blogposts on my existing github pages using quarto, write a quick guide on how to do this

I’ll help you publish your course notes as blog posts using Quarto on GitHub Pages. Let me break this down into digestible steps.

First, let’s clarify your setup: Do you already have a Quarto project started, or are we creating one from scratch? And is your GitHub Pages site already set up with a repository?

Once I know where you’re starting from, I can guide you through the specific steps you need, whether that’s:

  • Creating a Quarto blog project

  • Configuring it to work with your existing GitHub Pages

  • Converting your notes to blog posts

  • Setting up the publishing workflow

What’s your current situation?

Yes, github pages is set up and I have published a couple of posts using quarto to convert jupyter notebook and rmarkdown reports into websites

Perfect! Since you’re already familiar with Quarto and have published posts, here’s what you need to add blog functionality:

In your _quarto.yml file, you’ll want to add a blog listing. Here’s a basic structure:

project:
  type: website

website:
  title: "Your Site Name"
  navbar:
    left:

      - href: index.qmd
        text: Home

      - href: blog.qmd
        text: Blog

format:
  html:
    theme: cosmo

Then create a blog.qmd file that lists your posts:


---
title: "Blog"
listing:
  contents: posts
  sort: "date desc"
  type: default

---

Finally, create a posts/ directory where each post goes in its own subfolder (e.g., posts/lesson-1/index.qmd).

Do you already have a _quarto.yml file you’re working with, or would you like help setting one up?

Well one issue I am having is that the formatting does not match for markdown cells, e.g. the bullet point lists are not getting converted and are being printed in a single line

That’s a common issue with Quarto rendering notebooks. The problem is usually that markdown cells need proper spacing.

In your notebook markdown cells, make sure you have:

  • A blank line before lists start

  • A blank line after lists end

  • Consistent indentation for nested items

Can you show me an example of one of the markdown cells that’s not rendering correctly? That way I can see exactly what’s happening.

Is there a way I can add this as a code block in the notebook itself?

Yes! You can add this directly as a code cell in your notebook. Just copy the function into a cell and run it.

However, you’ll need to be careful about the path - if you run it from within the notebook, you’d be modifying the file while it’s open. A safer approach is to either:

  1. Run it on a copy of the notebook
  2. Run it from a separate notebook or script

Which approach would you prefer?

Why not just write a bash script I can use for automated formatting and publishing?

Good idea! A bash script would automate the whole workflow.

Do you want the script to:

  1. Just fix the markdown spacing in notebooks?
  2. Fix spacing AND run quarto render?
  3. The full pipeline: fix, render, commit, and push to GitHub?

Which level of automation would be most helpful for you?

I still need to preview locally before committing, so option 2

Perfect! Here’s a bash script that fixes markdown spacing and renders with Quarto:

#!/bin/bash

# Fix markdown spacing in all notebooks
python3 << 'EOF'
import nbformat
from pathlib import Path

def fix_markdown_spacing(notebook_path):
    nb = nbformat.read(notebook_path, as_version=4)
    
    for cell in nb.cells:
        if cell.cell_type == 'markdown':
            lines = cell.source.split('\n')
            fixed_lines = []
            
            for i, line in enumerate(lines):
                if line.strip().startswith(('-', '*', '1.')) and i > 0:
                    if fixed_lines and fixed_lines[-1].strip():
                        fixed_lines.append('')
                fixed_lines.append(line)
            
            cell.source = '\n'.join(fixed_lines)
    
    nbformat.write(nb, notebook_path)

for nb_path in Path('posts').rglob('*.ipynb'):
    print(f"Fixing {nb_path}")
    fix_markdown_spacing(nb_path)
EOF

# Render with Quarto
quarto render

Save this as publish.sh, make it executable with chmod +x publish.sh, then run ./publish.sh.

Does this look good, or would you like any adjustments?

I like the way solveit formats the promts vs responses, is there a way we can replicate this?

Interesting idea! Are you asking about:

  1. Styling the rendered blog posts to visually distinguish prompts from responses (like a chat interface)?
  2. Keeping the actual solveit dialog structure in your published notes?

Which aspect of the formatting are you looking to replicate?

More the former - the indenting to visually separate prompts and responses. Also the copied notebook keeps the emojis which don’t look very clean

Good points! For the visual separation, you can add custom CSS to your Quarto site. In your _quarto.yml, add:

format:
  html:
    css: styles.css

Then create a styles.css file with styling for prompts and responses. You could use divs or blockquotes to wrap them.

For the emojis, we need to strip those out. Should I update the bash script to remove those emojis from markdown cells when it processes the notebooks?

yes, strip them out please

Here’s the updated script with emoji removal:

#!/bin/bash

python3 << 'EOF'
import nbformat
from pathlib import Path
import re

def fix_notebook(notebook_path):
    nb = nbformat.read(notebook_path, as_version=4)
    
    for cell in nb.cells:
        if cell.cell_type == 'markdown':
            # Remove robot emoji
            cell.source = re.sub(r'\s*', '', cell.source)
            
            # Fix list spacing
            lines = cell.source.split('\n')
            fixed_lines = []
            
            for i, line in enumerate(lines):
                if line.strip().startswith(('-', '*', '1.')) and i > 0:
                    if fixed_lines and fixed_lines[-1].strip():
                        fixed_lines.append('')
                fixed_lines.append(line)
            
            cell.source = '\n'.join(fixed_lines)
    
    nbformat.write(nb, notebook_path)

for nb_path in Path('posts').rglob('*.ipynb'):
    print(f"Processing {nb_path}")
    fix_notebook(nb_path)
EOF

quarto render

This now removes the emojis along with fixing the spacing. Want to test it out?

Write the styles.css file for me

Here’s a basic styles.css to separate prompts and responses:

/* Style for user prompts */
.prompt {
  margin-left: 0;
  padding: 10px;
  background-color: #f5f5f5;
  border-left: 3px solid #666;
}

/* Style for AI responses */
.response {
  margin-left: 20px;
  padding: 10px;
  background-color: #fff;
  border-left: 3px solid #4a90e2;
}

To use these styles, you’ll need to wrap your content in divs with these classes in your markdown. Would you like me to show you how to structure that in your notebook cells?

Well I am not going to manually wrap each notebook cell right?

You’re absolutely right! We should automate that in the bash script.

Do you want the script to automatically wrap markdown cells based on some pattern (like detecting if it’s a prompt vs response), or would you prefer a different approach?

Yep, that would be ideal

Great! To automate wrapping cells, I need to understand how to identify prompts vs responses in your notebooks.

Are your prompt cells marked in a specific way (like starting with a certain phrase, or having metadata), or should the script alternate between prompt/response based on cell order?

I am surprised you cannot see the structure of this notebook already. THis is what an example cell looks like: # %% [markdown] # This is my current _quarto.yml: # # # # I’d be happy to help you modify it! Could you share the contents of your _quarto.yml file so I can see what you’re currently working with?

Ah, I see! The <!-- SOLVEIT_SEPARATOR_7f3a9b2c --> comment marks where responses start. So everything before that separator is the prompt, and everything after is the response.

Should the script split cells at that separator and wrap the two parts with the appropriate div classes?

Yep, that sounds good

Perfect! Here’s the updated script:

#!/bin/bash

python3 << 'EOF'
import nbformat
from pathlib import Path
import re

def fix_notebook(notebook_path):
    nb = nbformat.read(notebook_path, as_version=4)
    
    for cell in nb.cells:
        if cell.cell_type == 'markdown':
            # Remove robot emoji
            cell.source = re.sub(r'\s*', '', cell.source)
            
            # Split on separator and wrap with divs
            separator = re.compile(r'<!-- SOLVEIT_SEPARATOR_\w+ -->')
            parts = separator.split(cell.source)
            
            if len(parts) == 2:
                prompt, response = parts
                cell.source = f'<div class="prompt">\n\n{prompt.strip()}\n\n</div>\n\n<div class="response">\n\n{response.strip()}\n\n</div>'
            
            # Fix list spacing
            lines = cell.source.split('\n')
            fixed_lines = []
            
            for i, line in enumerate(lines):
                if line.strip().startswith(('-', '*', '1.')) and i > 0:
                    if fixed_lines and fixed_lines[-1].strip():
                        fixed_lines.append('')
                fixed_lines.append(line)
            
            cell.source = '\n'.join(fixed_lines)
    
    nbformat.write(nb, notebook_path)

for nb_path in Path('posts').rglob('*.ipynb'):
    print(f"Processing {nb_path}")
    fix_notebook(nb_path)
EOF

quarto render

Want to give it a try?

Can you make sure it removes the word “Reply” as well?

Sure! I’ll add a line to remove “Reply” from the cells:

# Remove "Reply" text
cell.source = re.sub(r'\bReply\b', '', cell.source)

Add this right after the robot emoji removal line. Does that work for you?

That still leaves ##### in the markdown, which causes an extra line of space to be rendered

Good catch! We should remove the ##### heading markers too. Add this line after removing “Reply”:

cell.source = re.sub(r'^#+\s*$', '', cell.source, flags=re.MULTILINE)

This removes lines that are just hash marks with optional whitespace. Want me to show you the full updated script with all the changes together?

That seems error prone. You shoul use regex that matches on four hashes followed by Reply

You’re right, that’s more precise! Here’s the better regex:

cell.source = re.sub(r'#{4,5}\s*Reply\s*', '', cell.source)

This specifically targets the or pattern. Should I show you the complete updated script now?