Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions helpers/github_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,29 @@
import re
import logging
import requests
import time
from helpers.token_helpers import count_tokens
from models import DevContainer
from supabase_client import supabase

_url_exists_cache = {}

def _db_cache_ttl_seconds():
try:
return int(os.getenv("DEVCONTAINER_DB_CACHE_SECONDS", "300"))
except ValueError:
return 300

def _cache_key(url):
return url.rstrip("/")

def clear_url_exists_cache(url=None):
if url is None:
_url_exists_cache.clear()
return

_url_exists_cache.pop(_cache_key(url), None)

def is_valid_github_url(url):
pattern = r"^https?://github\.com/[\w-]+/[\w.-]+/?$"
return re.match(pattern, url) is not None
Expand Down Expand Up @@ -144,7 +163,31 @@ def traverse_dir(api_url, depth=0, prefix=""):

return "\n\n".join(context), existing_devcontainer, devcontainer_url

def check_url_exists(url):
existing = supabase.table("devcontainers").select("*").eq("url", url).order("created_at", desc=True).limit(1).execute()
def check_url_exists(url, use_cache=True):
cache_key = _cache_key(url)
ttl_seconds = _db_cache_ttl_seconds()
now = time.monotonic()

if use_cache and ttl_seconds > 0:
cached = _url_exists_cache.get(cache_key)
if cached and now - cached["cached_at"] < ttl_seconds:
return cached["result"]

existing = (
supabase.table("devcontainers")
.select("url,devcontainer_json,generated,devcontainer_url,created_at")
.eq("url", cache_key)
.order("created_at", desc=True)
.limit(1)
.execute()
)
existing_record = existing.data[0] if existing.data else None
return existing_record is not None, existing_record
result = existing_record is not None, existing_record

if use_cache and ttl_seconds > 0 and existing_record is not None:
_url_exists_cache[cache_key] = {
"cached_at": now,
"result": result,
}

return result
79 changes: 42 additions & 37 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from supabase_client import supabase

from helpers.openai_helpers import setup_azure_openai, setup_instructor
from helpers.github_helpers import fetch_repo_context, check_url_exists
from helpers.github_helpers import fetch_repo_context, check_url_exists, clear_url_exists_cache
from helpers.devcontainer_helpers import generate_devcontainer_json, validate_devcontainer_json
from helpers.token_helpers import count_tokens, truncate_to_token_limit
from models import DevContainer
Expand Down Expand Up @@ -101,6 +101,32 @@ def home():
async def get():
return home()

def render_devcontainer_result(devcontainer_json, source, repo_url):
return Div(
Article(f"Devcontainer.json {'found in ' + source if source in ['database', 'repository'] else 'generated'}"),
Pre(
Code(devcontainer_json, id="devcontainer-code", cls="overflow-auto"),
Div(
Button(
Img(cls="w-4 h-4", src="assets/icons/copy-icon.svg", alt="Copy"),
cls="icon-button copy-button",
title="Copy to clipboard",
),
Button(
Img(cls="w-4 h-4", src="assets/icons/regenerate.svg", alt="Regenerate"),
cls="icon-button regenerate-button",
hx_post=f"/generate?regenerate=true&repo_url={repo_url}",
hx_target="#result",
hx_indicator="#action-text",
title="Regenerate",
),
Span(cls="action-text", id="action-text"),
cls="button-group"
),
cls="code-container relative"
)
)

@rt("/generate", methods=["post"])
async def post(repo_url: str, regenerate: bool = False):
logging.info(f"Generating devcontainer.json for: {repo_url}")
Expand All @@ -109,23 +135,24 @@ async def post(repo_url: str, regenerate: bool = False):
repo_url = repo_url.rstrip('/')

try:
exists, existing_record = check_url_exists(repo_url)
exists, existing_record = check_url_exists(repo_url, use_cache=not regenerate)
logging.info(f"URL check result: exists={exists}, existing_record={existing_record}")

if exists and not regenerate:
logging.info(f"URL already exists in database. Returning existing devcontainer_json for: {repo_url}")
return render_devcontainer_result(
existing_record["devcontainer_json"],
"database",
repo_url,
)

repo_context, existing_devcontainer, devcontainer_url = fetch_repo_context(repo_url)
logging.info(f"Fetched repo context. Existing devcontainer: {'Yes' if existing_devcontainer else 'No'}")
logging.info(f"Devcontainer URL: {devcontainer_url}")

if exists and not regenerate:
logging.info(f"URL already exists in database. Returning existing devcontainer_json for: {repo_url}")
devcontainer_json = existing_record['devcontainer_json']
generated = existing_record['generated']
source = "database"
url = existing_record['devcontainer_url']
else:
devcontainer_json, url = generate_devcontainer_json(instructor_client, repo_url, repo_context, devcontainer_url, regenerate=regenerate)
generated = True
source = "generated" if url is None else "repository"
devcontainer_json, url = generate_devcontainer_json(instructor_client, repo_url, repo_context, devcontainer_url, regenerate=regenerate)
generated = True
source = "generated" if url is None else "repository"


if not exists or regenerate:
Expand Down Expand Up @@ -159,34 +186,12 @@ async def post(repo_url: str, regenerate: bool = False):

result = supabase.table("devcontainers").insert(devcontainer_dict).execute()
logging.info(f"Successfully saved to database with devcontainer_url: {devcontainer_url}")
clear_url_exists_cache(repo_url)
except Exception as e:
logging.error(f"Error while saving to database: {str(e)}")
raise

return Div(
Article(f"Devcontainer.json {'found in ' + source if source in ['database', 'repository'] else 'generated'}"),
Pre(
Code(devcontainer_json, id="devcontainer-code", cls="overflow-auto"),
Div(
Button(
Img(cls="w-4 h-4", src="assets/icons/copy-icon.svg", alt="Copy"),
cls="icon-button copy-button",
title="Copy to clipboard",
),
Button(
Img(cls="w-4 h-4", src="assets/icons/regenerate.svg", alt="Regenerate"),
cls="icon-button regenerate-button",
hx_post=f"/generate?regenerate=true&repo_url={repo_url}",
hx_target="#result",
hx_indicator="#action-text",
title="Regenerate",
),
Span(cls="action-text", id="action-text"),
cls="button-group"
),
cls="code-container relative"
)
)
return render_devcontainer_result(devcontainer_json, source, repo_url)
except Exception as e:
logging.error(f"An error occurred: {str(e)}", exc_info=True)
return Div(H2("Error"), P(f"An error occurred: {str(e)}"))
Expand All @@ -207,4 +212,4 @@ async def get(fname:str, ext:str):

if __name__ == "__main__":
logging.info("Starting FastHTML app...")
serve()
serve()