# Project export: Blocker

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: TreeHacks 2024
- Tagline: Write Dockerfiles fast with a streamlined drag-and-drop UI
- Devpost: https://devpost.com/software/blocker-qwjn46
- GitHub: https://github.com/claby2/blocker
- Team: 3 GitHub contributor(s) — Edward Wibowo (10 commits), Michael Fu (7 commits), Lucas Sta Maria (6 commits)

## Devpost submission (written by the team)

### Overview

Dev tool

### Inspiration

Inspired by Warp Terminal's AI-enhanced productivity and modern UI, we recognized the need for efficiency in creating Dockerfiles—a task essential for containerizing applications, setting up student development environments, and testing programs. To simplify this often tedious process, we developed an intuitive UI, utilizing generative AI to streamline the architecture of Docker containers, making it a more user-friendly and less labor-intensive experience.

### What it does

Blocker revolutionizes Dockerfile customization through its block-based, drag-and-drop interface, offering users precise control over the configuration. Users can effortlessly rearrange and modify these blocks, and Blocker employs generative AI to seamlessly synthesize these adjustments into a complete Dockerfile. This approach not only simplifies the process but also enhances the flexibility and accuracy of Dockerfile generation.

### How we built it

React.js, OpenAI, Flask, TailwindCSS.

### What's next

More blocks. Allow importing of existing Dockerfiles.

## README (from the GitHub repository)

# Blocker

Blocker is a block-based WYSIWYG editor for Dockerfiles.


## Detected evidence (automated analysis)

Indexed codebase: 17 recognized source files, 20 KB.
- CSS (language) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (24 of 24)

```
backend/.gitignore
backend/backend.py
backend/requirements.txt
frontend/.eslintrc.cjs
frontend/.gitignore
frontend/index.html
frontend/package.json
frontend/postcss.config.js
frontend/README.md
frontend/src/App.tsx
frontend/src/components/Block.tsx
frontend/src/components/Canvas.tsx
frontend/src/components/InteractiveBlock.tsx
frontend/src/components/Preview.tsx
frontend/src/components/Sidebar.tsx
frontend/src/Home.tsx
frontend/src/index.css
frontend/src/main.tsx
frontend/src/vite-env.d.ts
frontend/tailwind.config.js
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
README.md
```

### Dependencies

- backend/requirements.txt: Flask, Flask-CORS, openai, python-dotenv
- frontend/package.json: @headlessui/react@^1.7.18, @heroicons/react@^2.1.1, @types/react@^18.2.55, @types/react-dom@^18.2.19, @types/react-syntax-highlighter@^15.5.11, @typescript-eslint/eslint-plugin@^6.21.0, @typescript-eslint/parser@^6.21.0, @vitejs/plugin-react@^4.2.1, autoprefixer@^10.4.17, dnd-core@^16.0.1, eslint@^8.56.0, eslint-plugin-react-hooks@^4.6.0, eslint-plugin-react-refresh@^0.4.5, immutability-helper@^3.1.1, postcss@^8.4.35, react@^18.2.0, react-dnd@^16.0.1, react-dnd-html5-backend@^16.0.1, react-dom@^18.2.0, react-router-dom@^6.22.1, react-syntax-highlighter@^15.5.0, tailwindcss@^3.4.1, typescript@^5.2.2, vite@^5.1.0

### Recent commits (newest first)

- added delete
- fix: modify backend for frontend
- feat: not working
- updated CORS
- updated CORS for backend
- feat: add preview
- prompt engineeeeeeer
- docs: add base README
- chore: add react dependencies
- feat: redesign home page
- fix: add default base image
- charlie duong
- feat: add home page
- feat: add icons
- create submit handler
- fix: change wording
- fix: hopefully
- feat: might not work
- style: add semicolons
- working on adding openai call to submit

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

### backend/requirements.txt

```
Flask
Flask-CORS
openai
python-dotenv

```

### frontend/package.json

```
{
  "name": "blocker",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "@headlessui/react": "^1.7.18",
    "@heroicons/react": "^2.1.1",
    "@types/react-syntax-highlighter": "^15.5.11",
    "autoprefixer": "^10.4.17",
    "dnd-core": "^16.0.1",
    "immutability-helper": "^3.1.1",
    "postcss": "^8.4.35",
    "react": "^18.2.0",
    "react-dnd": "^16.0.1",
    "react-dnd-html5-backend": "^16.0.1",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.22.1",
    "react-syntax-highlighter": "^15.5.0",
    "tailwindcss": "^3.4.1"
  },
  "devDependencies": {
    "@types/react": "^18.2.55",
    "@types/react-dom": "^18.2.19",
    "@typescript-eslint/eslint-plugin": "^6.21.0",
    "@typescript-eslint/parser": "^6.21.0",
    "@vitejs/plugin-react": "^4.2.1",
    "eslint": "^8.56.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.5",
    "typescript": "^5.2.2",
    "vite": "^5.1.0"
  }
}

```

### frontend/src/main.tsx

```typescript
import ReactDOM from 'react-dom/client';
import Home from './Home.tsx';
import App from './App.tsx';
import './index.css';
import {
  createBrowserRouter,
  RouterProvider,
} from "react-router-dom";

const router = createBrowserRouter([
  { path: "/build", element: <App /> },
  { path: "/", element: <Home /> },
]);

ReactDOM.createRoot(document.getElementById('root')!).render(
  <RouterProvider router={router}/>
)

```

### frontend/src/App.tsx

```typescript
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import Sidebar from "./components/Sidebar";
import Canvas from "./components/Canvas";
import { useState } from "react";
import { BlockData, BlockType } from "./components/Block";
import Preview from "./components/Preview";

function App() {
  const [generated, setGenerated] = useState(false);
  const [blocks, setBlocks] = useState<BlockData[]>([
    {
      type: BlockType.BaseImage,
      subtitle: "Select a base image",
      data: "debian",
    },
  ]);

  return (
    <DndProvider backend={HTML5Backend}>
      <div className="flex bg-gradient-to-r from-slate-700 to-slate-800">
        <div className="w-1/4 h-screen">
          <Sidebar
            generated={generated}
            setBlocks={setBlocks}
            setGenerated={setGenerated}
          />
        </div>
        <div className="w-3/4 h-screen">
          {generated ? (
            <Preview blocks={blocks} />
          ) : (
            <Canvas setBlocks={setBlocks} blocks={blocks} />
          )}
        </div>
      </div>
    </DndProvider>
  );
}

export default App;

```

### frontend/postcss.config.js

```javascript
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

### frontend/vite.config.ts

```typescript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
})

```

### frontend/tailwind.config.js

```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### backend/backend.py

```python
from flask import Flask, request, jsonify, abort
from typing import *
from dataclasses import dataclass, asdict
from openai import OpenAI
from dotenv import load_dotenv
import os
from flask_cors import CORS, cross_origin  # Import CORS

app = Flask(__name__)
CORS(app)
load_dotenv()
app.config['CORS_HEADERS'] = 'Content-Type'

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))


@dataclass
class Block:
    type: str
    data: str


@app.route('/submit', methods=['POST', 'OPTIONS'])
@cross_origin()
def handle_blocks():
    try:
        content = request.json
        base_image: Block = Block(**content['base_image'])
        blocks: List[Block] = [Block(**blk) for blk in content['blocks']]

        print(content)
        print(base_image)
        print(blocks)

        # docker_image = f"create a docker file using a {base_image.data} base image with the following instructions: "
        if len(blocks) == 0:
            commands = [
                f"create a docker file with a single line that uses the base image {base_image.data}"
            ]
        else:
            commands = [
                f"create a docker file using a base image {base_image.data} and follow these instructions: "
            ]
            for index, block in enumerate(blocks):
                res = ""
                block_type = block.type.lower()
                if block_type == "base image":
                    res = f"{index}.) derive from the image: {block.data}"
                elif block_type == "install packages":
                    res = f"{index}.) install package(s): {block.data}"
                elif block_type == "environment variables":
                    res = f"{index}.) set an environment variable: {block.data}"
                elif block_type == "run command":
                    res = f"{index}.) create and run this command: {block.data}"
                else:
                    abort(500, description="error with block type")
                commands.append(res)
            commands.append(
                "execute each step in a separate command. include comments.")
        commands.append(
            "do not include any other text, only the docker file in plaintext. do not include the markdown code prefix and suffix.")

        # Assuming your processing here...

        completion = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{
                "role":
                "system",
                "content":
                "SPOCs (Systems Programmer, Operator, and Consultants) \
                assist in the installation, maintenance, development, and documentation of local software. \
                You are an expert in linux commands, shell commands, and setting up docker images for developers.\
                You are also proficient in writing readable code, with good documentation and comments.\
                You are a SPOC that is building a docker image for people given specific instructions."
            }, {
                "role": "user",
                "content": "\n".join(commands)
            }])
        # return commands, 200
        return completion.choices[0].message.content, 200
        # return jsonify({
        #                 "input blocks": blocks,
        #                 "output": completion.choices[0].message.content
        #                 }), 200
    except Exception as e:
        # Log the error here
        print(f"Error: {e}")
        abort(500, description="Internal Server Error")


@app.route('/test', methods=['GET', 'POST', 'OPTIONS'])
def test_route():
    return jsonify({"message": "Test successful"}), 200


if __name__ == '__main__':
    app.run(port=8000, debug=True)

```

### frontend/src/vite-env.d.ts

```typescript
/// <reference types="vite/client" />

```

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