├── aigist ├── __init__.py ├── app │ ├── __init__.py │ ├── database.py │ ├── lite_llm.py │ ├── models.py │ └── services.py ├── persistence.json ├── scripts │ ├── check_db.sh │ ├── create_folders.sh │ ├── setup.sh │ └── init.sh └── main.py ├── aigist.egg-info ├── dependency_links.txt ├── top_level.txt ├── entry_points.txt ├── requires.txt ├── SOURCES.txt └── PKG-INFO ├── .devcontainer ├── requirements.txt ├── devcontainer.json ├── schema.sql ├── .devcontainer │ └── schema.sql └── setup.sh ├── .gitignore ├── tests ├── test_services.py └── test_main.py ├── .github └── workflows │ └── python-package.yml ├── LICENSE ├── setup.py └── README.md /aigist/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /aigist/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /aigist/persistence.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /aigist.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /aigist.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | aigist 2 | -------------------------------------------------------------------------------- /aigist.egg-info/entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | aigist = aigist.main:main 3 | -------------------------------------------------------------------------------- /.devcontainer/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | uvicorn 3 | pydantic 4 | httpx 5 | sqlite3 6 | litellm 7 | -------------------------------------------------------------------------------- /aigist.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | twine 2 | setuptools 3 | wheel 4 | flake8 5 | black 6 | pytest 7 | pip-upgrader 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | *.pyo 4 | *.pyd 5 | *.db 6 | build 7 | dist 8 | .env 9 | aigist.egg-info 10 | PKG-INFO* -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Python FastAPI", 3 | "image": "mcr.microsoft.com/vscode/devcontainers/python:3.9", 4 | "postCreateCommand": "bash .devcontainer/setup.sh" 5 | } 6 | -------------------------------------------------------------------------------- /aigist/scripts/check_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DATABASE_FILE="../data/gists.db" 4 | 5 | # Check if the table 'gists' exists and display its contents 6 | sqlite3 "$DATABASE_FILE" <> ~/.bashrc 34 | ;; 35 | 3) 36 | echo "Optimizing environment..." 37 | # Add optimization commands here 38 | ;; 39 | 4) 40 | break 41 | ;; 42 | *) 43 | echo "Invalid choice!" 44 | ;; 45 | esac 46 | done 47 | 48 | echo "Setup complete!" 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from pathlib import Path 3 | 4 | # Read the contents of README.md 5 | this_directory = Path(__file__).parent 6 | long_description = (this_directory / "README.md").read_text() 7 | 8 | setup( 9 | name='aigist', 10 | version='0.0.4', 11 | packages=find_packages(), 12 | install_requires=[ 13 | 'twine', 14 | 'setuptools', 15 | 'wheel', 16 | 'flake8', 17 | 'black', 18 | 'pytest', 19 | 'pip-upgrader', 20 | ], 21 | entry_points={ 22 | 'console_scripts': [ 23 | 'aigist=aigist.main:main', # Corrected entry point 24 | ], 25 | }, 26 | author='rUv', 27 | author_email='', 28 | description='This project provides a FastAPI application to create and update GitHub gists using the GitHub API. It includes SQLite for persistence and is designed to run in a GitHub Codespace.', 29 | long_description=long_description, 30 | long_description_content_type='text/markdown', 31 | url='', 32 | license='Apache License 2.0', 33 | classifiers=[ 34 | 'Development Status :: 3 - Alpha', 35 | 'Intended Audience :: Developers', 36 | 'Topic :: Software Development :: Libraries :: Application Frameworks', 37 | 'License :: OSI Approved :: Apache Software License', 38 | 'Programming Language :: Python :: 3', 39 | 'Programming Language :: Python :: 3.7', 40 | 'Programming Language :: Python :: 3.8', 41 | 'Programming Language :: Python :: 3.9', 42 | 'Programming Language :: Python :: 3.10', 43 | ], 44 | ) 45 | -------------------------------------------------------------------------------- /aigist/app/services.py: -------------------------------------------------------------------------------- 1 | import os 2 | import httpx 3 | import json 4 | from typing import Dict, Optional, List 5 | from .database import get_db 6 | from .models import FileContent 7 | from datetime import datetime 8 | 9 | GITHUB_API_URL = "https://api.github.com/gists" 10 | GITHUB_TOKEN = os.getenv("GH_TOKEN") 11 | 12 | headers = { 13 | "Authorization": f"Bearer {GITHUB_TOKEN}", 14 | "Accept": "application/vnd.github+json" 15 | } 16 | 17 | async def create_gist(description: str, public: bool, files: Dict[str, FileContent]): 18 | async with httpx.AsyncClient() as client: 19 | response = await client.post( 20 | GITHUB_API_URL, 21 | headers=headers, 22 | json={"description": description, "public": public, "files": files} 23 | ) 24 | response.raise_for_status() 25 | gist = response.json() 26 | save_gist_to_db(gist) 27 | return gist 28 | 29 | async def update_gist(gist_id: str, description: Optional[str], files: Dict[str, FileContent]): 30 | async with httpx.AsyncClient() as client: 31 | response = await client.patch( 32 | f"{GITHUB_API_URL}/{gist_id}", 33 | headers=headers, 34 | json={"description": description, "files": files} 35 | ) 36 | response.raise_for_status() 37 | gist = response.json() 38 | save_gist_to_db(gist) 39 | return gist 40 | 41 | async def get_gist(gist_id: str): 42 | async with httpx.AsyncClient() as client: 43 | response = await client.get( 44 | f"{GITHUB_API_URL}/{gist_id}", 45 | headers=headers 46 | ) 47 | response.raise_for_status() 48 | return response.json() 49 | 50 | async def list_gists(page: int = 1, per_page: int = 30, since: Optional[str] = None, until: Optional[str] = None): 51 | params = { 52 | "page": page, 53 | "per_page": per_page 54 | } 55 | if since: 56 | params["since"] = since 57 | if until: 58 | params["until"] = until 59 | 60 | async with httpx.AsyncClient() as client: 61 | response = await client.get( 62 | GITHUB_API_URL, 63 | headers=headers, 64 | params=params 65 | ) 66 | response.raise_for_status() 67 | return response.json() 68 | 69 | def save_gist_to_db(gist): 70 | with get_db() as conn: 71 | cursor = conn.cursor() 72 | cursor.execute( 73 | "INSERT OR REPLACE INTO gists (id, description, public, files, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", 74 | (gist["id"], gist["description"], gist["public"], json.dumps(gist["files"]), gist["created_at"], gist["updated_at"]) 75 | ) 76 | conn.commit() 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | _____ _ _____ _ _ 3 | | _ |_| __|_|___| |_ 4 | | | | | | |_ -| _| 5 | |__|__|_|_____|_|___|_| 6 | 7 | Created by rUv 8 | ``` 9 | # Ai Gist 10 | 11 | Ai Gist is python application designed to help you create and manage GitHub gists effortlessly using the GitHub API. With built-in SQLite for persistence and seamless integration with AI language models (LLMs), Ai Gist offers powerful capabilities for automating your workflow. 12 | 13 | ## Key Features 14 | 15 | - **Easy Gist Management**: Create, update, and list GitHub gists with simple API calls. 16 | - **AI-Powered Interactions**: Utilize advanced AI language models to interpret user messages and perform actions based on responses. 17 | - **SQLite Integration**: Store and manage data locally with SQLite, ensuring persistence and reliability. 18 | - **FastAPI Framework**: Benefit from the high performance and ease of use provided by the FastAPI framework. 19 | 20 | Get started quickly by installing the application and running the server to leverage these features for an enhanced coding experience. 21 | 22 | 23 | ## Setup 24 | 25 | 1. **Create a GitHub Codespace**: 26 | - Create a new GitHub Codespace for your repository. 27 | - Ensure your GitHub token is stored as a secret in the Codespace. 28 | 29 | 2. **Install Dependencies**: 30 | - The setup script will automatically install necessary dependencies and set up the environment. 31 | 32 | 3. **Install the Package**: 33 | - Install the package in editable mode: 34 | ```bash 35 | pip install aigist 36 | ``` 37 | 38 | 4. **Initialize the Database**: 39 | - Run the database initialization script to create the necessary database and tables: 40 | ```bash 41 | python init_db.py 42 | ``` 43 | 44 | 5. **Run the Application**: 45 | - Start the FastAPI server using Uvicorn: 46 | ```bash 47 | aigist 48 | ``` 49 | 50 | ## Configuration 51 | 52 | - **Environment Variables**: 53 | - Ensure the following environment variables are set: 54 | - `GITHUB_TOKEN`: Your GitHub token. 55 | - `LITELLM_API_BASE`: The base URL for the LiteLLM API. 56 | - `LITELLM_API_KEY`: Your LiteLLM API key. 57 | - `LITELLM_MODEL`: The model to use for LiteLLM (e.g., gpt-4o-2024-05-1). 58 | 59 | ## Endpoints 60 | 61 | - **Create Gist**: `POST /gists` 62 | - **Request Body**: 63 | ```json 64 | { 65 | "description": "Sample Gist", 66 | "public": true, 67 | "files": [ 68 | { 69 | "filename": "sample.txt", 70 | "content": "This is a sample gist content." 71 | } 72 | ] 73 | } 74 | ``` 75 | 76 | - **Update Gist**: `PATCH /gists/{gist_id}` 77 | - **Request Body**: 78 | ```json 79 | { 80 | "description": "Updated Sample Gist", 81 | "files": [ 82 | { 83 | "filename": "sample.txt", 84 | "content": "This is the updated sample gist content." 85 | } 86 | ] 87 | } 88 | ``` 89 | 90 | - **List Gists**: `GET /gists` 91 | - **Query Parameters**: 92 | - `page`: The page number (default: 1). 93 | - `per_page`: The number of gists per page (default: 30, max: 100). 94 | - `since`: Filter gists created after this date (ISO 8601 format). 95 | - `until`: Filter gists created before this date (ISO 8601 format). 96 | 97 | - **Chat Completion**: `POST /chat` 98 | - **Request Body**: 99 | ```json 100 | { 101 | "messages": [ 102 | { 103 | "role": "user", 104 | "content": "Your message here" 105 | } 106 | ], 107 | "stream": false 108 | } 109 | ``` 110 | 111 | - **Chat Gist**: `POST /chat/gist` 112 | - **Request Body**: 113 | ```json 114 | { 115 | "messages": [ 116 | { 117 | "role": "user", 118 | "content": "Your message here" 119 | } 120 | ], 121 | "stream": false 122 | } 123 | ``` 124 | 125 | ## Testing 126 | 127 | Run the tests using pytest: 128 | ```bash 129 | pytest 130 | ``` 131 | 132 | ## Database Setup 133 | 134 | To ensure the database and table setup is correct, run the `init_db.py` script: 135 | ```bash 136 | python init_db.py 137 | ``` 138 | 139 | ### Example JSON Payloads 140 | 141 | - **Create Gist**: 142 | ```json 143 | { 144 | "description": "Sample Gist", 145 | "public": true, 146 | "files": [ 147 | { 148 | "filename": "sample.txt", 149 | "content": "This is a sample gist content." 150 | } 151 | ] 152 | } 153 | ``` 154 | 155 | - **Update Gist**: 156 | ```json 157 | { 158 | "description": "Updated Sample Gist", 159 | "files": [ 160 | { 161 | "filename": "sample.txt", 162 | "content": "This is the updated sample gist content." 163 | } 164 | ] 165 | } 166 | ``` 167 | 168 | ## Chat System 169 | 170 | The chat system leverages LiteLLM to interpret user messages and perform actions based on the responses. The endpoint `/chat/gist` handles incoming chat requests, processes them through LiteLLM, and performs actions such as creating or updating gists based on the responses. 171 | 172 | ### How It Works 173 | 174 | 1. **Receiving Chat Messages**: The endpoint `/chat/gist` receives a POST request with a list of chat messages. 175 | 2. **Calling LiteLLM API**: The `chat_completion` function sends these messages to the LiteLLM API using the GPT-4o model. 176 | 3. **Processing the Response**: Based on the `action` field in the response, the endpoint either creates or updates a gist. 177 | 4. **Handling Errors**: Any exceptions are caught and a 500 Internal Server Error is returned with the error details. 178 | 179 | ### LiteLLM Features 180 | 181 | LiteLLM offers numerous features that streamline interaction with various LLM providers: 182 | 183 | - **Unified Interface**: Supports 100+ LLMs using the same Input/Output format, including OpenAI, Hugging Face, Anthropic, Cohere, and more. 184 | - **Error Handling and Retries**: Automatic error handling and retry mechanism, switching to alternative providers if one fails. 185 | - **Streaming Support**: Efficiently handle memory-intensive tasks by retrieving large model outputs in chunks. 186 | - **Open-Source and Community-Driven**: Benefit from transparency and ongoing development by the open-source community. 187 | - **Reduced Complexity**: Simplifies interactions with different provider APIs. 188 | - **Increased Flexibility**: Allows experimentation with various LLMs. 189 | - **Improved Efficiency**: Saves time with uniform code structure and automated error handling. 190 | - **Cost-Effectiveness**: Optimizes costs by exploring different pricing models across providers. 191 | 192 | For more details, refer to the [LiteLLM documentation](https://docs.litellm.ai). 193 | -------------------------------------------------------------------------------- /aigist.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: aigist 3 | Version: 0.0.4 4 | Summary: This project provides a FastAPI application to create and update GitHub gists using the GitHub API. It includes SQLite for persistence and is designed to run in a GitHub Codespace. 5 | Home-page: 6 | Author: rUv 7 | Author-email: 8 | License: Apache License 2.0 9 | Classifier: Development Status :: 3 - Alpha 10 | Classifier: Intended Audience :: Developers 11 | Classifier: Topic :: Software Development :: Libraries :: Application Frameworks 12 | Classifier: License :: OSI Approved :: Apache Software License 13 | Classifier: Programming Language :: Python :: 3 14 | Classifier: Programming Language :: Python :: 3.7 15 | Classifier: Programming Language :: Python :: 3.8 16 | Classifier: Programming Language :: Python :: 3.9 17 | Classifier: Programming Language :: Python :: 3.10 18 | Description-Content-Type: text/markdown 19 | License-File: LICENSE 20 | Requires-Dist: twine 21 | Requires-Dist: setuptools 22 | Requires-Dist: wheel 23 | Requires-Dist: flake8 24 | Requires-Dist: black 25 | Requires-Dist: pytest 26 | Requires-Dist: pip-upgrader 27 | 28 | ``` 29 | _____ _ _____ _ _ 30 | | _ |_| __|_|___| |_ 31 | | | | | | |_ -| _| 32 | |__|__|_|_____|_|___|_| 33 | 34 | Created by rUv 35 | ``` 36 | # Ai Gist 37 | 38 | Ai Gist is python application designed to help you create and manage GitHub gists effortlessly using the GitHub API. With built-in SQLite for persistence and seamless integration with AI language models (LLMs), Ai Gist offers powerful capabilities for automating your workflow. 39 | 40 | ## Key Features 41 | 42 | - **Easy Gist Management**: Create, update, and list GitHub gists with simple API calls. 43 | - **AI-Powered Interactions**: Utilize advanced AI language models to interpret user messages and perform actions based on responses. 44 | - **SQLite Integration**: Store and manage data locally with SQLite, ensuring persistence and reliability. 45 | - **FastAPI Framework**: Benefit from the high performance and ease of use provided by the FastAPI framework. 46 | 47 | Get started quickly by installing the application and running the server to leverage these features for an enhanced coding experience. 48 | 49 | 50 | ## Setup 51 | 52 | 1. **Create a GitHub Codespace**: 53 | - Create a new GitHub Codespace for your repository. 54 | - Ensure your GitHub token is stored as a secret in the Codespace. 55 | 56 | 2. **Install Dependencies**: 57 | - The setup script will automatically install necessary dependencies and set up the environment. 58 | 59 | 3. **Install the Package**: 60 | - Install the package in editable mode: 61 | ```bash 62 | pip install aigist 63 | ``` 64 | 65 | 4. **Initialize the Database**: 66 | - Run the database initialization script to create the necessary database and tables: 67 | ```bash 68 | python init_db.py 69 | ``` 70 | 71 | 5. **Run the Application**: 72 | - Start the FastAPI server using Uvicorn: 73 | ```bash 74 | aigist 75 | ``` 76 | 77 | ## Configuration 78 | 79 | - **Environment Variables**: 80 | - Ensure the following environment variables are set: 81 | - `GITHUB_TOKEN`: Your GitHub token. 82 | - `LITELLM_API_BASE`: The base URL for the LiteLLM API. 83 | - `LITELLM_API_KEY`: Your LiteLLM API key. 84 | - `LITELLM_MODEL`: The model to use for LiteLLM (e.g., gpt-4o-2024-05-1). 85 | 86 | ## Endpoints 87 | 88 | - **Create Gist**: `POST /gists` 89 | - **Request Body**: 90 | ```json 91 | { 92 | "description": "Sample Gist", 93 | "public": true, 94 | "files": [ 95 | { 96 | "filename": "sample.txt", 97 | "content": "This is a sample gist content." 98 | } 99 | ] 100 | } 101 | ``` 102 | 103 | - **Update Gist**: `PATCH /gists/{gist_id}` 104 | - **Request Body**: 105 | ```json 106 | { 107 | "description": "Updated Sample Gist", 108 | "files": [ 109 | { 110 | "filename": "sample.txt", 111 | "content": "This is the updated sample gist content." 112 | } 113 | ] 114 | } 115 | ``` 116 | 117 | - **List Gists**: `GET /gists` 118 | - **Query Parameters**: 119 | - `page`: The page number (default: 1). 120 | - `per_page`: The number of gists per page (default: 30, max: 100). 121 | - `since`: Filter gists created after this date (ISO 8601 format). 122 | - `until`: Filter gists created before this date (ISO 8601 format). 123 | 124 | - **Chat Completion**: `POST /chat` 125 | - **Request Body**: 126 | ```json 127 | { 128 | "messages": [ 129 | { 130 | "role": "user", 131 | "content": "Your message here" 132 | } 133 | ], 134 | "stream": false 135 | } 136 | ``` 137 | 138 | - **Chat Gist**: `POST /chat/gist` 139 | - **Request Body**: 140 | ```json 141 | { 142 | "messages": [ 143 | { 144 | "role": "user", 145 | "content": "Your message here" 146 | } 147 | ], 148 | "stream": false 149 | } 150 | ``` 151 | 152 | ## Testing 153 | 154 | Run the tests using pytest: 155 | ```bash 156 | pytest 157 | ``` 158 | 159 | ## Database Setup 160 | 161 | To ensure the database and table setup is correct, run the `init_db.py` script: 162 | ```bash 163 | python init_db.py 164 | ``` 165 | 166 | ### Example JSON Payloads 167 | 168 | - **Create Gist**: 169 | ```json 170 | { 171 | "description": "Sample Gist", 172 | "public": true, 173 | "files": [ 174 | { 175 | "filename": "sample.txt", 176 | "content": "This is a sample gist content." 177 | } 178 | ] 179 | } 180 | ``` 181 | 182 | - **Update Gist**: 183 | ```json 184 | { 185 | "description": "Updated Sample Gist", 186 | "files": [ 187 | { 188 | "filename": "sample.txt", 189 | "content": "This is the updated sample gist content." 190 | } 191 | ] 192 | } 193 | ``` 194 | 195 | ## Chat System 196 | 197 | The chat system leverages LiteLLM to interpret user messages and perform actions based on the responses. The endpoint `/chat/gist` handles incoming chat requests, processes them through LiteLLM, and performs actions such as creating or updating gists based on the responses. 198 | 199 | ### How It Works 200 | 201 | 1. **Receiving Chat Messages**: The endpoint `/chat/gist` receives a POST request with a list of chat messages. 202 | 2. **Calling LiteLLM API**: The `chat_completion` function sends these messages to the LiteLLM API using the GPT-4o model. 203 | 3. **Processing the Response**: Based on the `action` field in the response, the endpoint either creates or updates a gist. 204 | 4. **Handling Errors**: Any exceptions are caught and a 500 Internal Server Error is returned with the error details. 205 | 206 | ### LiteLLM Features 207 | 208 | LiteLLM offers numerous features that streamline interaction with various LLM providers: 209 | 210 | - **Unified Interface**: Supports 100+ LLMs using the same Input/Output format, including OpenAI, Hugging Face, Anthropic, Cohere, and more. 211 | - **Error Handling and Retries**: Automatic error handling and retry mechanism, switching to alternative providers if one fails. 212 | - **Streaming Support**: Efficiently handle memory-intensive tasks by retrieving large model outputs in chunks. 213 | - **Open-Source and Community-Driven**: Benefit from transparency and ongoing development by the open-source community. 214 | - **Reduced Complexity**: Simplifies interactions with different provider APIs. 215 | - **Increased Flexibility**: Allows experimentation with various LLMs. 216 | - **Improved Efficiency**: Saves time with uniform code structure and automated error handling. 217 | - **Cost-Effectiveness**: Optimizes costs by exploring different pricing models across providers. 218 | 219 | For more details, refer to the [LiteLLM documentation](https://docs.litellm.ai). 220 | 221 | -------------------------------------------------------------------------------- /aigist/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '.'))) 4 | 5 | import json 6 | import os 7 | from fastapi import FastAPI, HTTPException, Query 8 | from typing import Optional 9 | from app.models import GistCreateRequest, GistUpdateRequest, ChatRequest, ChatMessage 10 | from app.services import create_gist, update_gist, get_gist, list_gists 11 | from app.lite_llm import chat_completion 12 | from litellm import completion 13 | import uvicorn 14 | import httpx 15 | import sqlite3 16 | from contextlib import asynccontextmanager 17 | from dotenv import load_dotenv 18 | import logging 19 | 20 | # Load environment variables from .env file 21 | load_dotenv() 22 | 23 | # Configure logging 24 | logging.basicConfig(level=logging.INFO) 25 | 26 | app = FastAPI() 27 | 28 | DATABASE_FILE = "./data/gists.db" 29 | ENV_FILE = ".env" 30 | 31 | def check_env_variables(): 32 | # Check if GITHUB_TOKEN is set 33 | if not os.getenv("GITHUB_TOKEN"): 34 | raise RuntimeError("❌ GITHUB_TOKEN is not set. Please set it to proceed.") 35 | else: 36 | print("✅ GITHUB_TOKEN is set.") 37 | 38 | # Check if LITELLM_API_BASE is set 39 | if not os.getenv("LITELLM_API_BASE"): 40 | raise RuntimeError("❌ LITELLM_API_BASE is not set. Please set it to proceed.") 41 | else: 42 | print("✅ LITELLM_API_BASE is set.") 43 | 44 | # Check if LITELLM_API_KEY is set 45 | if not os.getenv("LITELLM_API_KEY"): 46 | raise RuntimeError("❌ LITELLM_API_KEY is not set. Please set it to proceed.") 47 | else: 48 | print("✅ LITELLM_API_KEY is set.") 49 | 50 | # Check if LITELLM_MODEL is set 51 | if not os.getenv("LITELLM_MODEL"): 52 | raise RuntimeError("❌ LITELLM_MODEL is not set. Please set it to proceed.") 53 | else: 54 | print("✅ LITELLM_MODEL is set.") 55 | 56 | def set_env_variable(key, value): 57 | os.environ[key] = value 58 | with open(ENV_FILE, "a") as f: 59 | f.write(f"{key}={value}\n") 60 | 61 | def prompt_user_for_env_vars(): 62 | import readline # Optional, will allow Up/Down/History in the prompt 63 | print("🔧 Some required environment variables are missing. Please enter them:") 64 | 65 | # Prompt for GITHUB_TOKEN 66 | if not os.getenv("GITHUB_TOKEN"): 67 | github_token = input("Enter GITHUB_TOKEN: ") 68 | set_env_variable("GITHUB_TOKEN", github_token) 69 | 70 | # Prompt for LITELLM_API_BASE 71 | if not os.getenv("LITELLM_API_BASE"): 72 | print("Select the LITELLM_API_BASE from the following options:") 73 | options = [ 74 | "OpenAI (https://api.openai.com/v1)", 75 | "Microsoft Azure (https://api.cognitive.microsoft.com)", 76 | "Google Cloud (https://ai-platform.googleapis.com)", 77 | "IBM Watson (https://api.us-south.assistant.watson.cloud.ibm.com)", 78 | "Amazon AWS (https://runtime.sagemaker.amazonaws.com)", 79 | "Hugging Face (https://api-inference.huggingface.co)", 80 | "Cohere (https://api.cohere.ai)", 81 | "Anthropic (https://api.anthropic.com)", 82 | "AI21 Labs (https://api.ai21.com/studio/v1)", 83 | "AssemblyAI (https://api.assemblyai.com)", 84 | "Other" 85 | ] 86 | 87 | for i, option in enumerate(options, start=1): 88 | print(f"{i}. {option}") 89 | 90 | choice = int(input("Enter the number of your choice: ")) 91 | if choice in range(1, len(options) + 1): 92 | api_base = options[choice - 1].split(" ")[-1].strip("()") 93 | else: 94 | api_base = input("Enter LITELLM_API_BASE: ") 95 | set_env_variable("LITELLM_API_BASE", api_base) 96 | 97 | # Prompt for LITELLM_API_KEY 98 | if not os.getenv("LITELLM_API_KEY"): 99 | litellm_api_key = input("Enter LITELLM_API_KEY: ") 100 | set_env_variable("LITELLM_API_KEY", litellm_api_key) 101 | 102 | # Prompt for LITELLM_MODEL 103 | if not os.getenv("LITELLM_MODEL"): 104 | print("Enter the model for LITELLM (e.g., gpt-4o-2024-05-1):") 105 | litellm_model = input("Enter LITELLM_MODEL: ") 106 | set_env_variable("LITELLM_MODEL", litellm_model) 107 | 108 | def check_db_and_table(): 109 | # ASCII Art Header 110 | print(r""" 111 | _____ _ _____ _ _ 112 | | _ |_| __|_|___| |_ 113 | | | | | | |_ -| _| 114 | |__|__|_|_____|_|___|_| 115 | 116 | Created by rUv 117 | """) 118 | 119 | # Check and prompt for environment variables 120 | try: 121 | check_env_variables() 122 | except RuntimeError as e: 123 | print(e) 124 | prompt_user_for_env_vars() 125 | 126 | # Re-check the environment variables after prompting 127 | check_env_variables() 128 | 129 | # Check if sqlite3 is installed 130 | if not os.system("command -v sqlite3"): 131 | print("✅ SQLite3 is installed.") 132 | else: 133 | raise RuntimeError("❌ SQLite3 is not installed. Please install it to proceed.") 134 | 135 | # Check if the database file exists 136 | if not os.path.isfile(DATABASE_FILE): 137 | raise RuntimeError(f"❌ Database file '{DATABASE_FILE}' does not exist. Please create it to proceed.") 138 | else: 139 | print(f"✅ Database file '{DATABASE_FILE}' exists.") 140 | 141 | # Check if the 'gists' table exists 142 | print("🔍 Checking if the 'gists' table exists...") 143 | try: 144 | conn = sqlite3.connect(DATABASE_FILE) 145 | cursor = conn.cursor() 146 | cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='gists';") 147 | table_exists = cursor.fetchone() 148 | if table_exists: 149 | print("✅ The 'gists' table exists.") 150 | else: 151 | raise RuntimeError("❌ The 'gists' table does not exist.") 152 | except sqlite3.Error as e: 153 | raise RuntimeError(f"❌ An error occurred: {e}") 154 | finally: 155 | if conn: 156 | conn.close() 157 | 158 | @asynccontextmanager 159 | async def lifespan(app: FastAPI): 160 | check_db_and_table() 161 | yield 162 | # Any cleanup can be done here 163 | print("🚀 AiGist Started") 164 | 165 | app = FastAPI(lifespan=lifespan) 166 | 167 | @app.post("/gists") 168 | async def create_gist_endpoint(request: GistCreateRequest): 169 | files = {file.filename: {"content": file.content} for file in request.files} 170 | try: 171 | gist = await create_gist(request.description, request.public, files) 172 | return gist 173 | except httpx.HTTPStatusError as e: 174 | raise HTTPException(status_code=e.response.status_code, detail=e.response.text) 175 | 176 | @app.patch("/gists/{gist_id}") 177 | async def update_gist_endpoint(gist_id: str, request: GistUpdateRequest): 178 | files = {file.filename: {"content": file.content} for file in request.files} 179 | try: 180 | gist = await update_gist(gist_id, request.description, files) 181 | return gist 182 | except httpx.HTTPStatusError as e: 183 | raise HTTPException(status_code=e.response.status_code, detail=e.response.text) 184 | 185 | @app.post("/chat") 186 | async def chat_endpoint(chat_request: ChatRequest): 187 | try: 188 | response = await chat_completion(chat_request.messages) 189 | return response 190 | except Exception as e: 191 | raise HTTPException(status_code=500, detail=str(e)) 192 | 193 | @app.get("/gists") 194 | async def list_gists_endpoint( 195 | page: int = Query(1, gt=0), 196 | per_page: int = Query(30, gt=0, le=100), 197 | since: Optional[str] = Query(None), 198 | until: Optional[str] = Query(None) 199 | ): 200 | try: 201 | gists = await list_gists(page, per_page, since, until) 202 | return gists 203 | except httpx.HTTPStatusError as e: 204 | raise HTTPException(status_code=e.response.status_code, detail=e.response.text) 205 | 206 | 207 | def main(): 208 | import uvicorn 209 | uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) 210 | 211 | if __name__ == "__main__": 212 | main() 213 | -------------------------------------------------------------------------------- /aigist/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Create directory structure 4 | mkdir -p project_root/.devcontainer 5 | mkdir -p project_root/app 6 | mkdir -p project_root/data 7 | mkdir -p project_root/scripts 8 | mkdir -p project_root/tests 9 | 10 | # Create and populate .devcontainer/devcontainer.json 11 | cat < project_root/.devcontainer/devcontainer.json 12 | { 13 | "name": "Python FastAPI", 14 | "image": "mcr.microsoft.com/vscode/devcontainers/python:3.9", 15 | "postCreateCommand": "bash .devcontainer/setup.sh" 16 | } 17 | EOL 18 | 19 | # Create and populate .devcontainer/setup.sh 20 | cat < project_root/.devcontainer/setup.sh 21 | #!/bin/bash 22 | 23 | set -e 24 | 25 | echo "Setting up the development environment..." 26 | 27 | # Install dependencies 28 | pip install -r requirements.txt 29 | 30 | # Set up SQLite database 31 | if [ ! -f data/gists.db ]; then 32 | echo "Creating SQLite database..." 33 | sqlite3 data/gists.db < .devcontainer/schema.sql 34 | fi 35 | 36 | # Menu for additional setup options 37 | while true; do 38 | echo "Choose an option:" 39 | echo "1. Install additional packages" 40 | echo "2. Configure environment variables" 41 | echo "3. Optimize environment" 42 | echo "4. Exit" 43 | read -p "Enter your choice [1-4]: " choice 44 | case \$choice in 45 | 1) 46 | read -p "Enter package name: " package 47 | pip install \$package 48 | ;; 49 | 2) 50 | read -p "Enter environment variable name: " var_name 51 | read -p "Enter environment variable value: " var_value 52 | export \$var_name=\$var_value 53 | echo "export \$var_name=\$var_value" >> ~/.bashrc 54 | ;; 55 | 3) 56 | echo "Optimizing environment..." 57 | # Add optimization commands here 58 | ;; 59 | 4) 60 | break 61 | ;; 62 | *) 63 | echo "Invalid choice!" 64 | ;; 65 | esac 66 | done 67 | 68 | echo "Setup complete!" 69 | EOL 70 | 71 | # Create and populate .devcontainer/schema.sql 72 | cat < project_root/.devcontainer/schema.sql 73 | CREATE TABLE IF NOT EXISTS gists ( 74 | id TEXT PRIMARY KEY, 75 | description TEXT, 76 | public BOOLEAN, 77 | files TEXT, 78 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 79 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 80 | ); 81 | EOL 82 | 83 | # Create and populate app/__init__.py 84 | touch project_root/app/__init__.py 85 | 86 | # Create and populate app/main.py 87 | cat < project_root/app/main.py 88 | from fastapi import FastAPI, HTTPException, Depends 89 | from .models import GistCreateRequest, GistUpdateRequest, ChatRequest 90 | from .services import create_gist, update_gist, get_gist 91 | from .lite_llm import chat_completion 92 | 93 | app = FastAPI() 94 | 95 | @app.post("/gists") 96 | async def create_gist_endpoint(request: GistCreateRequest): 97 | files = {file.filename: {"content": file.content} for file in request.files} 98 | try: 99 | gist = await create_gist(request.description, request.public, files) 100 | return gist 101 | except httpx.HTTPStatusError as e: 102 | raise HTTPException(status_code=e.response.status_code, detail=e.response.text) 103 | 104 | @app.patch("/gists/{gist_id}") 105 | async def update_gist_endpoint(gist_id: str, request: GistUpdateRequest): 106 | files = {file.filename: {"content": file.content} for file in request.files} 107 | try: 108 | gist = await update_gist(gist_id, request.description, files) 109 | return gist 110 | except httpx.HTTPStatusError as e: 111 | raise HTTPException(status_code=e.response.status_code, detail=e.response.text) 112 | 113 | @app.post("/chat") 114 | async def chat_endpoint(chat_request: ChatRequest): 115 | try: 116 | response = await chat_completion(chat_request.messages) 117 | return response 118 | except Exception as e: 119 | raise HTTPException(status_code=500, detail=str(e)) 120 | 121 | @app.post("/chat/gist") 122 | async def chat_gist_endpoint(chat_request: ChatRequest): 123 | try: 124 | response = await chat_completion(chat_request.messages) 125 | action = response.get("action") 126 | if action == "create_gist": 127 | gist_data = response.get("gist_data") 128 | gist = await create_gist(gist_data["description"], gist_data["public"], gist_data["files"]) 129 | return gist 130 | elif action == "update_gist": 131 | gist_id = response.get("gist_id") 132 | gist_data = response.get("gist_data") 133 | existing_gist = await get_gist(gist_id) 134 | updated_files = {**existing_gist["files"], **gist_data["files"]} 135 | gist = await update_gist(gist_id, gist_data.get("description", existing_gist["description"]), updated_files) 136 | return gist 137 | else: 138 | raise HTTPException(status_code=400, detail="Invalid action") 139 | except Exception as e: 140 | raise HTTPException(status_code=500, detail=str(e)) 141 | EOL 142 | 143 | # Create and populate app/models.py 144 | cat < project_root/app/models.py 145 | from pydantic import BaseModel 146 | from typing import List, Dict, Optional 147 | 148 | class FileContent(BaseModel): 149 | content: str 150 | 151 | class GistFile(BaseModel): 152 | filename: str 153 | content: str 154 | 155 | class GistCreateRequest(BaseModel): 156 | description: str 157 | public: bool 158 | files: List project_root/app/services.py 171 | import os 172 | import httpx 173 | import json 174 | from .database import get_db 175 | 176 | GITHUB_API_URL = "https://api.github.com/gists" 177 | GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") 178 | 179 | headers = { 180 | "Authorization": f"Bearer {GITHUB_TOKEN}", 181 | "Accept": "application/vnd.github+json" 182 | } 183 | 184 | async def create_gist(description: str, public: bool, files: Dict[str, FileContent]): 185 | async with httpx.AsyncClient() as client: 186 | response = await client.post( 187 | GITHUB_API_URL, 188 | headers=headers, 189 | json={"description": description, "public": public, "files": files} 190 | ) 191 | response.raise_for_status() 192 | gist = response.json() 193 | save_gist_to_db(gist) 194 | return gist 195 | 196 | async def update_gist(gist_id: str, description: Optional[str], files: Dict[str, FileContent]): 197 | async with httpx.AsyncClient() as client: 198 | response = await client.patch( 199 | f"{GITHUB_API_URL}/{gist_id}", 200 | headers=headers, 201 | json={"description": description, "files": files} 202 | ) 203 | response.raise_for_status() 204 | gist = response.json() 205 | save_gist_to_db(gist) 206 | return gist 207 | 208 | async def get_gist(gist_id: str): 209 | async with httpx.AsyncClient() as client: 210 | response = await client.get( 211 | f"{GITHUB_API_URL}/{gist_id}", 212 | headers=headers 213 | ) 214 | response.raise_for_status() 215 | return response.json() 216 | 217 | def save_gist_to_db(gist): 218 | with get_db() as conn: 219 | cursor = conn.cursor() 220 | cursor.execute( 221 | "INSERT OR REPLACE INTO gists (id, description, public, files, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)", 222 | (gist["id"], gist["description"], gist["public"], json.dumps(gist["files"]), gist["created_at"], gist["updated_at"]) 223 | ) 224 | conn.commit() 225 | EOL 226 | 227 | # Create and populate app/database.py 228 | cat < project_root/app/database.py 229 | import sqlite3 230 | from contextlib import contextmanager 231 | 232 | DATABASE = "data/gists.db" 233 | 234 | @contextmanager 235 | def get_db(): 236 | conn = sqlite3.connect(DATABASE) 237 | try: 238 | yield conn 239 | finally: 240 | conn.close() 241 | EOL 242 | 243 | # Create and populate app/lite_llm.py 244 | cat < project_root/app/lite_llm.py 245 | import os 246 | from lite_llm import completion 247 | 248 | async def chat_completion(messages): 249 | response = await completion( 250 | model="gpt-4o", 251 | messages=messages, 252 | api_base=os.getenv("LITELLM_API_BASE"), 253 | api_key=os.getenv("LITELLM_API_KEY"), 254 | stream=False 255 | ) 256 | return response 257 | EOL 258 | 259 | # Create and populate scripts/create_folders.sh 260 | cat < project_root/scripts/create_folders.sh 261 | #!/bin/bash 262 | 263 | echo "Enter the name for the new directory:" 264 | read NEW_DIR 265 | 266 | if [ ! -d "\$NEW_DIR" ]; then 267 | mkdir -p "\$NEW_DIR" 268 | echo "Directory \$NEW_DIR created." 269 | else 270 | echo "Directory \$NEW_DIR already exists." 271 | fi 272 | 273 | echo "Enter the names of the files to create (space-separated):" 274 | read FILE_NAMES 275 | 276 | for FILE in \$FILE_NAMES; do 277 | touch "\$NEW_DIR/\$FILE" 278 | echo "File \$FILE created in \$NEW_DIR." 279 | done 280 | EOL 281 | 282 | # Create and populate tests/test_main.py 283 | cat < project_root/tests/test_main.py 284 | from fastapi.testclient import TestClient 285 | from app.main import app 286 | 287 | client = TestClient(app) 288 | 289 | def test_create_gist(): 290 | response = client.post("/gists", json={ 291 | "description": "Test Gist", 292 | "public": True, 293 | "files": [{"filename": "test.txt", "content": "Hello World"}] 294 | }) 295 | assert response.status_code == 200 296 | assert response.json()["description"] == "Test Gist" 297 | 298 | def test_update_gist(): 299 | gist_id = "existing_gist_id" 300 | response = client.patch(f"/gists/{gist_id}", json={ 301 | "description": "Updated Gist", 302 | "files": [{"filename": "test.txt", "content": "Updated Content"}] 303 | }) 304 | assert response.status_code == 200 305 | assert response.json()["description"] == "Updated Gist" 306 | EOL 307 | 308 | # Create and populate tests/test_services.py 309 | cat < project_root/tests/test_services.py 310 | import pytest 311 | from app.services import create_gist, update_gist 312 | 313 | @pytest.mark.asyncio 314 | async def test_create_gist(): 315 | gist = await create_gist("Test Gist", True, {"test.txt": {"content": "Hello World"}}) 316 | assert gist["description"] == "Test Gist" 317 | 318 | @pytest.mark.asyncio 319 | async def test_update_gist(): 320 | gist_id = "existing_gist_id" 321 | gist = await update_gist(gist_id, "Updated Gist", {"test.txt": {"content": "Updated Content"}}) 322 | assert gist["description"] == "Updated Gist" 323 | EOL 324 | 325 | # Create and populate .gitignore 326 | cat < project_root/.gitignore 327 | __pycache__/ 328 | *.pyc 329 | *.pyo 330 | *.pyd 331 | *.db 332 | EOL 333 | 334 | # Create and populate README.md 335 | cat < project_root/README.md 336 | # FastAPI Gist Manager 337 | 338 | This project provides a FastAPI application to create and update GitHub gists using the GitHub API. It includes SQLite for persistence and is designed to run in a GitHub Codespace. 339 | 340 | ## Setup 341 | 342 | 1. **Create a GitHub Codespace**: 343 | - Create a new GitHub Codespace for your repository. 344 | - Ensure your GitHub token is stored as a secret in the Codespace. 345 | 346 | 2. **Install Dependencies**: 347 | - The setup script will automatically install necessary dependencies and set up the environment. 348 | 349 | 3. **Run the Application**: 350 | - Start the FastAPI server using Uvicorn: 351 | \`\`\`bash 352 | uvicorn app.main:app --reload 353 | \`\`\` 354 | 355 | ## Endpoints 356 | 357 | - **Create Gist**: \`POST /gists\` 358 | - **Update Gist**: \`PATCH /gists/{gist_id}\` 359 | - **Chat Completion**: \`POST /chat\` 360 | - **Chat Gist**: \`POST /chat/gist\` 361 | 362 | ## Testing 363 | 364 | Run the tests using pytest: 365 | \`\`\`bash 366 | pytest 367 | \`\`\` 368 | EOL 369 | 370 | # Create and populate requirements.txt 371 | cat < project_root/requirements.txt 372 | fastapi 373 | uvicorn 374 | pydantic 375 | httpx 376 | sqlite3 377 | lite-llm 378 | EOL 379 | 380 | # Create persistence.json 381 | touch project_root/persistence.json 382 | 383 | echo "Project structure created successfully." 384 | --------------------------------------------------------------------------------