# Project export: Aesop.ai

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: Avengers! When kids hear that they think FUN! Yet, when kids hear learning they Cry! AESOP uses generative AI to allow their favorite superheros into dynamic mentors to them about educational topics.
- Devpost: https://devpost.com/software/alsop-ai
- GitHub: https://github.com/Devanshusp/aesop.git
- Team: 2 GitHub contributor(s) — Devanshu Sen Pandey (32 commits), brad (12 commits)

## Devpost submission (written by the team)

### Inspiration

We are passionate about teaching and inspiring kids to succeed. After brainstorming, we discovered that we all had relatable frustrations of being unable to engage children to learn school topics from our tutoring experiences. All the kids would love to talk about the latest superhero movies and would rather listen and talk about them than to engage with us. Knowing that, we decided to harness the generative AI and let the heroes take over the teaching from us.

### What it does

Utilizes multimodal AI agents to generate text and illustrations to create engaging and interactive choose-your-own educational adventures. The child picks his favorite character, his grade, and the subject he wants to learn so that they can build their own unique experience

### How we built it

We utilized: Groq's LiteLLM and LPU backend to generate text for our stories. Reflex's pure-Python and batteries-included approach to Web apps to accelerate our front and backend development Cartesia to build a clone superhero voice for superhero narrations. Hyperbolic's Dall-E 3 to create imaginative images based on the text provided

### Challenges we ran into

Implementing the vector database and content databases. Getting around the Reflex's data modeling limitations Implementing tracing with Arize Phoenix for child safety.

### Accomplishments we're proud of

We are proud of our end to end product. We are proud of how the we completed the process of how we completed the process of brainstorming to building a product in 36 hours. We can proudly say that we built a product that can potentially be used for an socially positive use case .

### What we learned

We learned how there can be a lot of problems throughout the project process and we learned that persistencey is the key. We learned that communication is especially important in relaying and solving problems. As for the techinal side we learned a lot about how to use git along with learning how to string together different AI agents to become one successful product.

### What's next

chromadb to create vector databases that store word embeddings and enable fast searches. As Arize Phoenix's API had a bit of learning curve and child safety is an important issue worth deep discussions, Implementing tracing has become our next time priority Accessibility features: Multilingual support, dyslexic and learning handicap features -Fetch.ai to connect to agent-verse

## README (from the GitHub repository)

# aesop

personalized and accessible storytelling

## setup environment

```bash
# create virtual environment
pip install uv
uv venv

# activate virtual environment
source .venv/bin/activate   # mac/linux
.venv\Scripts\activate      # windows

# download all requirements.txt
uv pip install -r requirements.txt

# add package
uv pip install <package-name>
uv pip freeze > requirements.txt

# remove package
uv pip uninstall <package-name>
uv pip freeze > requirements.txt
```

## run app

```bash
# start application
reflex run
```


## Detected evidence (automated analysis)

Indexed codebase: 31 recognized source files, 48 KB.
- Python (language) — detected in the code
- Supabase (technology) — detected in the code

## Codebase structure (from repository index)

### Files (36 of 36)

```
.gitignore
aesop/__init__.py
aesop/aesop.py
aesop/backend/dboperations.py
aesop/backend/embeddings.py
aesop/backend/generate_story.py
aesop/backend/llm/__init__.py
aesop/backend/llm/llm_api.py
aesop/backend/llm/llm_model.py
aesop/backend/llm/llm_provider.py
aesop/backend/llm/prompts.py
aesop/frontend/components/footer.py
aesop/frontend/components/header.py
aesop/frontend/components/login.py
aesop/frontend/components/logo.py
aesop/frontend/pages/create.py
aesop/frontend/pages/home.py
aesop/frontend/pages/story.py
aesop/frontend/pages/talk.py
aesop/frontend/utils.py
aesop/models/__init__.py
aesop/models/llm_message.py
aesop/models/story_card.py
aesop/models/story_content.py
alembic.ini
alembic/env.py
alembic/README
alembic/script.py.mako
alembic/versions/00510c65088f_.py
alembic/versions/f75081739d96_test.py
config.py
config.yaml
docker-compose.yml
README.md
requirements.txt
rxconfig.py
```

### Dependencies

- requirements.txt: Authlib@==1.3.2, h2@==4.1.0, litellm@==1.50.0, pillow@==11.0.0, psycopg2-binary@==2.9.10, pydantic-settings@==2.6.0, reflex-clerk@==1.0.3, supabase@==2.9.1

### Recent commits (newest first)

- Merge pull request #18 from Devanshusp/ai-components
- working published
- audio components
- Merge pull request #17 from Devanshusp/agents-jooho
- db ops complete
- complete
- handoff to dev
- Merge pull request #16 from Devanshusp/agents-jooho
- rxconfig update
- updated
- Merge pull request #15 from Devanshusp/ai-components
- text_generation
- localized
- Merge pull request #14 from Devanshusp/frontend-devanshu
- more login stuff
- more login stuff
- Merge pull request #13 from Devanshusp/frontend-devanshu
- renamed some pages, talk page created, required signin for certain pages
- Merge pull request #12 from Devanshusp/frontend-devanshu
- Merge pull request #11 from Devanshusp/reflexbackend-jooho#2

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
Authlib==1.3.2
h2==4.1.0
litellm==1.50.0
pillow==11.0.0
psycopg2-binary==2.9.10
pydantic-settings==2.6.0
reflex-clerk==1.0.3
supabase==2.9.1

```

### docker-compose.yml

```yaml
services:
  aesop-ui:

  aesop-vdb:
    image: chromadb/chroma:latest
    expose:
      - "8000:8000"
  zrok:
    image: openziti/zrok:latest
    environment:
        # Replace with your actual Zrok account token
        ZROK_TOKEN: "your_zrok_token"
        ZROK_TARGET: "your_app:80"
        volumes:
          - zrok_env:/mnt/.zrok
        restart: unless-stopped
  

```

### rxconfig.py

```python
import reflex as rx
from dotenv import load_dotenv
import os
# config
# url = 
# print(url)
# url = db_url= f"postgresql://{os.getenv('DB_LOGIN')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_URL')}:5432/storiesdb"
# print(url)
config = rx.Config(
    app_name="aesop",
	db_url= "postgresql://aesopadmin:aesop123@aesop2-1.ctq6oauek6z5.us-east-2.rds.amazonaws.com:5432/storiesdb"
	)
```

### config.yaml

```yaml
clerk_config:
  clerk_publishable_key: "pk_test_Z2xhZC1maXJlZmx5LTEyLmNsZXJrLmFjY291bnRzLmRldiQ"
  clerk_secret_key: "sk_test_B6nGT0qYZdbXE4BAaue419MY98xZK6RNesgiGYzgxZ"
llm_config:
  qrok_api_key: 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
supabase_config:
  supabase_url: https://rmizyvlqtbziaicqgsfj.supabase.co
  supabase_key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJtaXp5dmxxdGJ6aWFpY3Fnc2ZqIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcyOTM5MzgzMywiZXhwIjoyMDQ0OTY5ODMzfQ.WHym6T6O1O5kgZGJyerjo4DH-i7FuZ39P0lCAhcpuC0

```

### config.py

```python
import os

import yaml
from pydantic import BaseModel, ValidationError


class ClerkConfig(BaseModel):
    clerk_publishable_key: str
    clerk_secret_key: str


class LLMConfig(BaseModel):
    qrok_api_key: str


class AppConfig(BaseModel):
    clerk_config: ClerkConfig
    llm_config: LLMConfig


# Global variable to store the loaded configuration
_config = None


def load_config_from_yaml(file_path: str) -> AppConfig:
    print("Loading configurations...")
    try:
        with open(file_path, "r") as file:
            config_dict = yaml.safe_load(file)
            config = AppConfig(**config_dict)

            set_clerk_keys_as_env(config)

            return config
    except FileNotFoundError:
        print(f"Error: Configuration file {file_path} not found.")
        raise
    except ValidationError as e:
        print(f"Error loading configuration: {e}")
        raise


def set_clerk_keys_as_env(config: AppConfig):
    os.environ["CLERK_PUBLISHABLE_KEY"] = config.clerk_config.clerk_publishable_key
    os.environ["CLERK_SECRET_KEY"] = config.clerk_config.clerk_secret_key
    print("Clerk keys have been set in environment variables.")


def get_config(file_path: str = "config.yaml") -> AppConfig:
    global _config
    if _config is None:
        _config = load_config_from_yaml(file_path)
    return _config


# Call get_config() whenever you need to access the config
config = get_config()

```

### aesop/aesop.py

```python
"""Main app page."""

import reflex as rx
from reflex_clerk import install_signin_page

from aesop.frontend.pages.create import create_page
from aesop.frontend.pages.home import home_page
from aesop.frontend.pages.story import story_page
from aesop.frontend.pages.talk import talk_page

app = rx.App(  # pylint: disable=E1102
    theme=rx.theme(
        appearance="light",
        has_background=True,
        accent_color="orange",
        panel_background="translucent",
    )
)

install_signin_page(app)


app.add_page(story_page, route="/story/[story_id]")
app.add_page(create_page, route="/create")
app.add_page(talk_page, route="/talk")
app.add_page(home_page, route="/")

```

### alembic/env.py

```python
from logging.config import fileConfig

from sqlalchemy import engine_from_config
from sqlalchemy import pool

from alembic import context

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
    fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline() -> None:
    """Run migrations in 'offline' mode.

    This configures the context with just a URL
    and not an Engine, though an Engine is acceptable
    here as well.  By skipping the Engine creation
    we don't even need a DBAPI to be available.

    Calls to context.execute() here emit the given string to the
    script output.

    """
    url = config.get_main_option("sqlalchemy.url")
    context.configure(
        url=url,
        target_metadata=target_metadata,
        literal_binds=True,
        dialect_opts={"paramstyle": "named"},
    )

    with context.begin_transaction():
        context.run_migrations()


def run_migrations_online() -> None:
    """Run migrations in 'online' mode.

    In this scenario we need to create an Engine
    and associate a connection with the context.

    """
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix="sqlalchemy.",
        poolclass=pool.NullPool,
    )

    with connectable.connect() as connection:
        context.configure(
            connection=connection, target_metadata=target_metadata
        )

        with context.begin_transaction():
            context.run_migrations()


if context.is_offline_mode():
    run_migrations_offline()
else:
    run_migrations_online()

```

### aesop/models/story_content.py

```python
import reflex as rf


class StoryContent(rf.Model, table=True):
    title: str
    story_id: str
    body: str

```

### aesop/models/__init__.py

```python
from aesop.models.llm_message import LLMMessage
from aesop.models.story_card import StoryCard

__all__ = ["StoryCard", "LLMMessage"]

```

### aesop/frontend/utils.py

```python
import reflex as rx

from config import get_config


class BaseState(rx.State):
    """The app state."""

    def get_configs(self) -> None:
        get_config("config.yaml")

```

[20 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]