Spaces:
Sleeping
Sleeping
[feat] first commit
Browse files- .gitignore +2 -0
- About.md +1 -0
- app.py +190 -0
- requirements.txt +4 -0
- src/schema.py +13 -0
- src/skill_db.py +98 -0
- src/tools.py +34 -0
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
__pycache__/
|
About.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# Skill Library Hub
|
app.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List
|
| 2 |
+
import streamlit as st
|
| 3 |
+
import extra_streamlit_components as stx
|
| 4 |
+
from streamlit_searchbox import st_searchbox
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from streamlit_tags import st_tags
|
| 7 |
+
from src.tools import hf_pull_skill, time_ago
|
| 8 |
+
from src.skill_db import SkillsDB
|
| 9 |
+
from src.schema import Skill
|
| 10 |
+
st.title("Skill Library Hub")
|
| 11 |
+
|
| 12 |
+
skill_db = SkillsDB('skills.db')
|
| 13 |
+
all_skills = skill_db.get_skills()
|
| 14 |
+
|
| 15 |
+
all_tags = skill_db.get_tags()
|
| 16 |
+
|
| 17 |
+
if all_skills is None:
|
| 18 |
+
all_skills = []
|
| 19 |
+
|
| 20 |
+
def search(searchterm: str) -> List[any]:
|
| 21 |
+
global all_skills
|
| 22 |
+
global skill_db
|
| 23 |
+
if all_skills is None:
|
| 24 |
+
if skill_db is None:
|
| 25 |
+
skill_db = SkillsDB('skills.db')
|
| 26 |
+
all_skills = skill_db.get_skills()
|
| 27 |
+
result = []
|
| 28 |
+
if all_skills is not None and len(all_skills) > 0:
|
| 29 |
+
for skill in all_skills:
|
| 30 |
+
if searchterm.lower() in skill.skill_name.lower():
|
| 31 |
+
result.append(skill.skill_name.lower())
|
| 32 |
+
if searchterm.lower() in skill.author.lower():
|
| 33 |
+
result.append(skill.author.lower())
|
| 34 |
+
if len(result)>10:
|
| 35 |
+
return result
|
| 36 |
+
if len(result)<3:
|
| 37 |
+
for skill in all_skills:
|
| 38 |
+
if searchterm.lower() in skill.skill_description.lower():
|
| 39 |
+
result.append(skill.skill_description.lower())
|
| 40 |
+
if len(result)>10:
|
| 41 |
+
return result
|
| 42 |
+
return result
|
| 43 |
+
return result
|
| 44 |
+
|
| 45 |
+
program_languages = ["python", "r", "julia", "javascript", "html", "shell", "applescript"]
|
| 46 |
+
|
| 47 |
+
def main_page():
|
| 48 |
+
global all_skills
|
| 49 |
+
global skill_db
|
| 50 |
+
if all_skills is None:
|
| 51 |
+
if skill_db is None:
|
| 52 |
+
skill_db = SkillsDB('skills.db')
|
| 53 |
+
all_skills = skill_db.get_skills()
|
| 54 |
+
|
| 55 |
+
selected_languages = []
|
| 56 |
+
selected_tags = []
|
| 57 |
+
with st.sidebar:
|
| 58 |
+
st.title("🎈 Search Skills")
|
| 59 |
+
with st.expander("✨ PROGRAM", True):
|
| 60 |
+
if st.checkbox("Python"):
|
| 61 |
+
selected_languages.append("python")
|
| 62 |
+
if st.checkbox("Julia"):
|
| 63 |
+
selected_languages.append("julia")
|
| 64 |
+
if st.checkbox("R"):
|
| 65 |
+
selected_languages.append("r")
|
| 66 |
+
if st.checkbox("Javascript"):
|
| 67 |
+
selected_languages.append("javascript")
|
| 68 |
+
if st.checkbox("Html"):
|
| 69 |
+
selected_languages.append("html")
|
| 70 |
+
if st.checkbox("Shell"):
|
| 71 |
+
selected_languages.append("shell")
|
| 72 |
+
if st.checkbox("AppleScript"):
|
| 73 |
+
selected_languages.append("applescript")
|
| 74 |
+
|
| 75 |
+
with st.expander("🔖 TAGS", True):
|
| 76 |
+
if all_tags is not None:
|
| 77 |
+
for tag in all_tags:
|
| 78 |
+
if st.checkbox(tag):
|
| 79 |
+
selected_tags.append(tag)
|
| 80 |
+
|
| 81 |
+
if not selected_languages:
|
| 82 |
+
selected_languages = program_languages
|
| 83 |
+
|
| 84 |
+
all_skills = [item for item in all_skills if item.skill_program_language in selected_languages]
|
| 85 |
+
|
| 86 |
+
if not selected_tags and all_tags:
|
| 87 |
+
selected_tags = all_tags
|
| 88 |
+
all_skills = [item for item in all_skills if set(item.skill_tags) & set(selected_tags)]
|
| 89 |
+
|
| 90 |
+
selected_value = st_searchbox(
|
| 91 |
+
search,
|
| 92 |
+
key="",
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
print(f"selected_value: {selected_value}")
|
| 96 |
+
selected_skills = all_skills
|
| 97 |
+
if selected_value is None and len(selected_skills)!= len(all_skills):
|
| 98 |
+
selected_skills = all_skills
|
| 99 |
+
|
| 100 |
+
if selected_value is not None:
|
| 101 |
+
all_skills = [item for item in all_skills if selected_value.lower() in item.skill_name.lower() or
|
| 102 |
+
selected_value.lower() in item.author.lower() or
|
| 103 |
+
selected_value.lower() in item.skill_description.lower()]
|
| 104 |
+
|
| 105 |
+
if all_skills is not None and len(all_skills) > 0:
|
| 106 |
+
for skill in all_skills:
|
| 107 |
+
with st.expander(f"{skill.author}/{skill.skill_name}", expanded=True):
|
| 108 |
+
tags = "`{}`".format("`\t\t`".join(skill.skill_tags))
|
| 109 |
+
st.markdown(f"{tags}")
|
| 110 |
+
st.markdown(f"**<a href='https://huggingface.co/spaces/{skill.repo_id}?page=detail_page&skill_name={skill.skill_name}' style='font-size: 22px;' target='_self'>{skill.author}/{skill.skill_name}</a>**", unsafe_allow_html=True)
|
| 111 |
+
st.markdown(f">**{skill.skill_description}**")
|
| 112 |
+
st.markdown("Install:")
|
| 113 |
+
install = f"import creator\ncreator.create(huggingface_repo_id=\"{skill.repo_id}\", huggingface_skill_path=\"{skill.skill_name}\")"
|
| 114 |
+
st.code(install, language=skill.skill_program_language)
|
| 115 |
+
st.markdown("Usage:")
|
| 116 |
+
usage = f"{skill.skill_usage_example}"
|
| 117 |
+
if skill.skill_program_language.lower() == "python":
|
| 118 |
+
usage = f"from {skill.repo_id.replace('/', '.')}.skill_code import {skill.skill_name}\n"+usage
|
| 119 |
+
st.code(usage, language=skill.skill_program_language)
|
| 120 |
+
st.markdown(f"`Created {time_ago(skill.created_at)}` `{skill.skill_program_language}`")
|
| 121 |
+
else:
|
| 122 |
+
st.markdown(f"### 🎉 Search results for `{selected_value}`")
|
| 123 |
+
st.markdown(f"**No results found**")
|
| 124 |
+
|
| 125 |
+
def about_page():
|
| 126 |
+
file_path = "About.md"
|
| 127 |
+
with open(file_path, 'r', encoding='utf-8') as file:
|
| 128 |
+
content = file.read()
|
| 129 |
+
st.write(content, unsafe_allow_html=True)
|
| 130 |
+
|
| 131 |
+
def submit_page():
|
| 132 |
+
global all_skills
|
| 133 |
+
global skill_db
|
| 134 |
+
if all_skills is None:
|
| 135 |
+
if skill_db is None:
|
| 136 |
+
skill_db = SkillsDB('skills.db')
|
| 137 |
+
all_skills = skill_db.get_skills()
|
| 138 |
+
st.markdown(f"### ✉️✨ Submit your skill here!")
|
| 139 |
+
with st.form(key='my_form'):
|
| 140 |
+
skill_repository = st.text_input('Skill Repo Id:')
|
| 141 |
+
skill_names = st_tags(
|
| 142 |
+
label="Skills' name:",
|
| 143 |
+
text='Press enter to add every skill folder',
|
| 144 |
+
value=[],
|
| 145 |
+
suggestions=[],
|
| 146 |
+
maxtags = 10,
|
| 147 |
+
key='1')
|
| 148 |
+
|
| 149 |
+
if st.form_submit_button('Submit'):
|
| 150 |
+
error = False
|
| 151 |
+
skills = []
|
| 152 |
+
if skill_names is None or len(skill_names)<1:
|
| 153 |
+
st.error("Please enter at least one skill name! And remeber to press 'Enter' after each skill name.")
|
| 154 |
+
error = True
|
| 155 |
+
return
|
| 156 |
+
if skill_names is not None and len(skill_names) > 0:
|
| 157 |
+
for skill_name in skill_names:
|
| 158 |
+
skill = hf_pull_skill(skill_repository, skill_name)
|
| 159 |
+
if isinstance(skill, Exception):
|
| 160 |
+
st.error(skill)
|
| 161 |
+
error = True
|
| 162 |
+
return
|
| 163 |
+
skill["repo_id"] = skill_repository
|
| 164 |
+
skills.append(skill)
|
| 165 |
+
if skills is not None and len(skills) > 0:
|
| 166 |
+
for skill in skills:
|
| 167 |
+
if skill is not None:
|
| 168 |
+
if skill not in all_skills:
|
| 169 |
+
skill_obj = Skill(skill["repo_id"], skill["skill_name"], skill["skill_description"], skill["skill_metadata"]["author"], skill["skill_metadata"]["created_at"], skill["skill_usage_example"], skill["skill_program_language"], skill["skill_tags"])
|
| 170 |
+
result = skill_db.add_skill(skill_obj)
|
| 171 |
+
if result != "ok":
|
| 172 |
+
st.error(result)
|
| 173 |
+
error = True
|
| 174 |
+
continue
|
| 175 |
+
all_skills.append(skill_obj)
|
| 176 |
+
if not error:
|
| 177 |
+
st.success("Skill(s) submitted successfully!")
|
| 178 |
+
|
| 179 |
+
chosen_id = stx.tab_bar(data=[
|
| 180 |
+
stx.TabBarItemData(id=1, title="🏅 Skill Library", description=""),
|
| 181 |
+
stx.TabBarItemData(id=2, title="📝 About", description=""),
|
| 182 |
+
stx.TabBarItemData(id=3, title="🚀 Submit here!", description=""),
|
| 183 |
+
], default=1)
|
| 184 |
+
|
| 185 |
+
if chosen_id == '1':
|
| 186 |
+
main_page()
|
| 187 |
+
elif chosen_id == '2':
|
| 188 |
+
about_page()
|
| 189 |
+
elif chosen_id == '3':
|
| 190 |
+
submit_page()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit-searchbox
|
| 2 |
+
extra_streamlit_components
|
| 3 |
+
streamlit-tags
|
| 4 |
+
huggingface_hub
|
src/schema.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class Skill:
|
| 2 |
+
def __init__(self, repo_id, skill_name, skill_description, author, created_at, skill_usage_example, skill_program_language, skill_tags):
|
| 3 |
+
self.repo_id = repo_id
|
| 4 |
+
self.skill_name = skill_name
|
| 5 |
+
self.skill_description = skill_description
|
| 6 |
+
self.author = author
|
| 7 |
+
self.created_at = created_at
|
| 8 |
+
self.skill_usage_example = skill_usage_example
|
| 9 |
+
self.skill_program_language = skill_program_language
|
| 10 |
+
self.skill_tags = skill_tags
|
| 11 |
+
|
| 12 |
+
def __str__(self) -> str:
|
| 13 |
+
return f"Skill: repo_id: {self.repo_id}, name: {self.skill_name}, description: {self.skill_description}, author: {self.author}, created_at: {self.created_at}, skill_usage_example: {self.skill_usage_example}, skill_program_language: {self.skill_program_language}, skill_tags: {self.skill_tags}"
|
src/skill_db.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import os
|
| 3 |
+
from src.schema import Skill
|
| 4 |
+
|
| 5 |
+
class SkillsDB:
|
| 6 |
+
def __init__(self, db_name="skills.db"):
|
| 7 |
+
# Check if the database file exists
|
| 8 |
+
db_exists = os.path.exists(db_name)
|
| 9 |
+
|
| 10 |
+
# Connect to the database
|
| 11 |
+
self.conn = sqlite3.connect(db_name)
|
| 12 |
+
self.cursor = self.conn.cursor()
|
| 13 |
+
|
| 14 |
+
# If the database file didn't exist before, create and initialize the database
|
| 15 |
+
if not db_exists:
|
| 16 |
+
self.create_db()
|
| 17 |
+
|
| 18 |
+
def create_db(self):
|
| 19 |
+
# Create skill table
|
| 20 |
+
self.cursor.execute('''
|
| 21 |
+
CREATE TABLE IF NOT EXISTS skills (
|
| 22 |
+
id INTEGER PRIMARY KEY,
|
| 23 |
+
repo_id TEXT NOT NULL,
|
| 24 |
+
skill_name TEXT NOT NULL,
|
| 25 |
+
skill_description TEXT,
|
| 26 |
+
author TEXT,
|
| 27 |
+
created_at TEXT,
|
| 28 |
+
skill_usage_example TEXT,
|
| 29 |
+
skill_program_language TEXT,
|
| 30 |
+
skill_tags TEXT
|
| 31 |
+
);
|
| 32 |
+
''')
|
| 33 |
+
|
| 34 |
+
# Create tags table
|
| 35 |
+
self.cursor.execute('''
|
| 36 |
+
CREATE TABLE IF NOT EXISTS tags (
|
| 37 |
+
id INTEGER PRIMARY KEY,
|
| 38 |
+
tag TEXT NOT NULL UNIQUE
|
| 39 |
+
);
|
| 40 |
+
''')
|
| 41 |
+
|
| 42 |
+
self.conn.commit()
|
| 43 |
+
|
| 44 |
+
def add_skill(self, skill):
|
| 45 |
+
self.cursor.execute('SELECT id FROM skills WHERE skill_name = ? AND author = ?;', (skill.skill_name, skill.author))
|
| 46 |
+
if self.cursor.fetchone() is not None:
|
| 47 |
+
return f"Skill with name '{skill.skill_name}' by author '{skill.author}' already exists!"
|
| 48 |
+
# Handle tags: check if they exist; if not, insert them
|
| 49 |
+
tag_ids = []
|
| 50 |
+
for tag in skill.skill_tags:
|
| 51 |
+
self.cursor.execute('SELECT id FROM tags WHERE tag = ?;', (tag,))
|
| 52 |
+
tag_id = self.cursor.fetchone()
|
| 53 |
+
if tag_id is None:
|
| 54 |
+
self.cursor.execute('INSERT INTO tags (tag) VALUES (?);', (tag,))
|
| 55 |
+
tag_id = self.cursor.lastrowid
|
| 56 |
+
else:
|
| 57 |
+
tag_id = tag_id[0]
|
| 58 |
+
tag_ids.append(str(tag_id))
|
| 59 |
+
tags_str = ",".join(tag_ids)
|
| 60 |
+
|
| 61 |
+
# Insert skill into skills table
|
| 62 |
+
self.cursor.execute('''
|
| 63 |
+
INSERT INTO skills (repo_id, skill_name, skill_description, author, created_at, skill_usage_example, skill_program_language, skill_tags)
|
| 64 |
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
| 65 |
+
''', (skill.repo_id, skill.skill_name, skill.skill_description, skill.author, skill.created_at, skill.skill_usage_example, skill.skill_program_language, tags_str))
|
| 66 |
+
|
| 67 |
+
self.conn.commit()
|
| 68 |
+
return "ok"
|
| 69 |
+
|
| 70 |
+
def get_skills(self):
|
| 71 |
+
# Fetch all skills from the skills table along with column names
|
| 72 |
+
self.cursor.execute('SELECT * FROM skills;')
|
| 73 |
+
col_names = [col[0] for col in self.cursor.description]
|
| 74 |
+
skills_data = self.cursor.fetchall()
|
| 75 |
+
|
| 76 |
+
# Extract data using column names and create Skill objects
|
| 77 |
+
skills = []
|
| 78 |
+
for skill_data in skills_data:
|
| 79 |
+
skill_dict = dict(zip(col_names, skill_data))
|
| 80 |
+
tag_ids = skill_dict['skill_tags'].split(',')
|
| 81 |
+
self.cursor.execute('SELECT tag FROM tags WHERE id IN (%s);' % ','.join(['?'] * len(tag_ids)), tag_ids)
|
| 82 |
+
tags = [tag[0] for tag in self.cursor.fetchall()]
|
| 83 |
+
skill_obj = Skill(skill_dict['repo_id'], skill_dict['skill_name'], skill_dict['skill_description'], skill_dict['author'],
|
| 84 |
+
skill_dict['created_at'], skill_dict['skill_usage_example'], skill_dict['skill_program_language'], tags)
|
| 85 |
+
skills.append(skill_obj)
|
| 86 |
+
|
| 87 |
+
return skills
|
| 88 |
+
|
| 89 |
+
def get_tags(self):
|
| 90 |
+
# Fetch all tags from the tags table
|
| 91 |
+
self.cursor.execute('SELECT tag FROM tags;')
|
| 92 |
+
tags = self.cursor.fetchall()
|
| 93 |
+
if tags is not None:
|
| 94 |
+
return [tag[0] for tag in tags]
|
| 95 |
+
return []
|
| 96 |
+
|
| 97 |
+
def close(self):
|
| 98 |
+
self.conn.close()
|
src/tools.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from huggingface_hub import hf_hub_download
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
|
| 5 |
+
def hf_pull_skill(repo_id, huggingface_skill_path):
|
| 6 |
+
try:
|
| 7 |
+
return_path = hf_hub_download(repo_id=repo_id, subfolder=huggingface_skill_path, repo_type="space", filename="skill.json")
|
| 8 |
+
with open(return_path) as f:
|
| 9 |
+
skill_json = json.load(f)
|
| 10 |
+
return skill_json
|
| 11 |
+
except Exception as e:
|
| 12 |
+
return e
|
| 13 |
+
|
| 14 |
+
def time_ago(updated_at_str):
|
| 15 |
+
now = datetime.now()
|
| 16 |
+
updated_at = datetime.strptime(updated_at_str, '%Y-%m-%d %H:%M:%S')
|
| 17 |
+
delta = now - updated_at
|
| 18 |
+
|
| 19 |
+
minutes = delta.total_seconds() / 60
|
| 20 |
+
hours = minutes / 60
|
| 21 |
+
days = hours / 24
|
| 22 |
+
months = days / 30.44 # An average month length
|
| 23 |
+
years = days / 365.25 # Account for leap years
|
| 24 |
+
|
| 25 |
+
if minutes < 60:
|
| 26 |
+
return f"{int(minutes)} minutes ago"
|
| 27 |
+
elif hours < 24:
|
| 28 |
+
return f"{int(hours)} hours ago"
|
| 29 |
+
elif days < 30.44:
|
| 30 |
+
return f"{int(days)} days ago"
|
| 31 |
+
elif months < 12:
|
| 32 |
+
return f"{int(months)} months ago"
|
| 33 |
+
else:
|
| 34 |
+
return f"{int(years)} years ago"
|