---
title: "Building an AI-Powered Ball Story Generator"
slug: "2024-03-30-building-an-ai-powered-ball-story-generator"
url: "https://haakony.no/posts/2024-03-30-building-an-ai-powered-ball-story-generator"
date: "2025-03-30"
updated: "2026-06-08"
categories: ["projects"]
tags: ["python", "ai", "ollama", "hugo", "openai", "comfyui", "story-generation", "image-generation"]
reading_time_minutes: 3
description: "Recently, I embarked on an interesting project: creating an AI-powered system that generates humorous stories and satirical news articles about various types of balls. The result is [A Balling Site](h…"
---

# Building an AI-Powered Ball Story Generator

Recently, I embarked on an interesting project: creating an AI-powered system that generates humorous stories and satirical news articles about various types of balls. The result is [A Balling Site](https://balls.no), where you can find these AI-generated tales.

## The Concept

The idea was to create a system that could:
1. Generate engaging stories and news articles about different types of balls
2. Create AI-generated images for both the main content and specific scenes
3. Automatically format and deploy everything to a Hugo static site

## Technical Stack

The system uses a combination of modern tools and APIs:

- **LLM Providers**: 
  - Ollama (primary) for story generation
  - OpenAI (optional) as a fallback
- **Image Generation**:
  - ComfyUI (primary) for high-quality images
  - DALL-E (optional) as an alternative
- **Static Site**: Hugo for content management and deployment

## Key Challenges

### 1. Managing Multiple AI Providers

One of the main challenges was creating a flexible system that could work with different AI providers. Here's how we handled the LLM provider selection:

```python
class LLMProvider:
    """Base class for LLM providers."""
    
    def generate_content(self, prompt: str) -> str:
        """Generate content using the provider."""
        raise NotImplementedError

class OllamaProvider(LLMProvider):
    def generate_content(self, prompt: str) -> str:
        """Generate content using Ollama API."""
        response = requests.post(
            f"{settings.OLLAMA_API_URL}/api/generate",
            json={
                "model": settings.OLLAMA_MODEL,
                "prompt": prompt,
                "stream": False,
                "format": "json"
            }
        )
        return response.json()["response"]
```

### 2. Image Generation and Scene Placement

Another challenge was ensuring that the generated images matched the story content and were placed appropriately. We solved this by:

1. Using a `[SCENE]` marker in the generated content
2. Creating two types of images:
   - Main image for the story header
   - Scene image for specific story moments

```python
def create_blog_post(data: Dict[str, Any], image_path: Optional[str] = None, scene_image_path: Optional[str] = None, content_type: str = "story") -> str:
    # Process the content to insert the scene image
    content_parts = content.split('[SCENE]')
    content_with_image = content_parts[0]
    if len(content_parts) > 1 and scene_image_path:
        content_with_image += f"\n\n[![scene]({scene_image_path})]({date}-{slug}-{timestamp})\n\n" + content_parts[1]
```

### 3. Content Cleaning and Formatting

The AI models sometimes return content with JSON artifacts or code blocks. We implemented robust cleaning functions:

```python
def clean_content(content: str, content_type: str = 'story') -> str:
    """Clean up story/article content by removing JSON artifacts and extra whitespace."""
    # Remove any code blocks and JSON artifacts
    content = re.sub(r'```.*?```', '', content, flags=re.DOTALL)
    content = re.sub(r'\{.*?\}', '', content, flags=re.DOTALL)
    content = re.sub(r'```json\s*\{.*?\}```', '', content, flags=re.DOTALL)
    
    # Remove any extra newlines
    content = re.sub(r'\n{3,}', '\n\n', content)
    
    return content.strip()
```

## Interesting Findings

1. **Ollama's JSON Output**: While Ollama is great for creative writing, getting consistent JSON output was challenging. We had to implement fallback mechanisms and robust parsing.

2. **Image Generation Timing**: ComfyUI image generation can take several seconds. We implemented proper waiting mechanisms and error handling to ensure reliability.

3. **Content Structure**: The AI models sometimes needed explicit instructions about content structure. We found that detailed prompts with examples worked best.

## The Result

The system now generates engaging content like this:

```markdown
---
title: "Great-Basketball"
date: 2025-03-30
draft: false
categories: ["story"]
tags: ['basketball', 'humor', 'kids', 'ollama', 'llama3.1:8b', 'comfyui']
---

[![image](/images/comfyui-090916.png)](2025-03-30-great-basketball-090951)

It was a typical Tuesday afternoon at Springdale Elementary when chaos erupted in the school gym...

[![scene](/images/comfyui-090951.png)](2025-03-30-great-basketball-090951)

But little did they know, Benny had bigger plans...
```

## Future Improvements

1. **Better Error Handling**: Implement more robust error recovery for API failures
2. **Content Quality**: Add content validation and filtering
3. **Image Quality**: Implement image quality checks and regeneration if needed
4. **Performance**: Optimize the image generation process

## Conclusion

Building this system was a great learning experience in working with multiple AI providers and handling their quirks. The result is a fun, automated content generation system that creates engaging stories and articles about balls.

You can check out the results at [balls.no](https://balls.no), and the source code is available on [GitHub](https://github.com/haakony/a-balling-blog).

---

*This project demonstrates how we can combine different AI technologies to create engaging content. While there were challenges in managing multiple providers and ensuring consistent output, the result is a fun and interesting experiment in AI-powered content generation.*
