ИИ и Python

Я создал приложение Streamlit для создания веб-сайтов за считанные секунды — попробуйте!

ChatGPT очень полезен для генерации кода, особенно для автономных приложений. По этой причине я решил создать потоковое приложение для быстрого и простого создания и отображения веб-приложений. Демонстрация показана ниже:

Вы также можете попробовать сами бесплатно! См. ссылку ниже:

https://site-generator.streamlit.app/

Если вы считаете, что веб-сайт, созданный с помощью приложения, хорош, вы можете отправить его с помощью кнопки. Затем я рассмотрю его и, если сочту, что он хорош, я добавлю его на сайт, чтобы другие люди могли его попробовать.

В оставшейся части статьи я объясню код, который использовался для создания приложения.

Код

Ниже приведены необходимые пакеты:

openai==0.27.2
pymongo==4.3.3
streamlit==1.19.0

Пакет pymongo используется для связи с базой данных MongoDB, чтобы можно было отправлять и отображать приложения.

Давайте импортируем пакеты:

import streamlit as st
import pymongo
from pathlib import Path
import re
import os
import openai
from streamlit.components.v1 import html

Далее определяются некоторые переменные:

@st.cache_resource()
def load_db():
    client = pymongo.MongoClient(f"mongodb+srv://{os.getenv('MONGODB_USER_PASSWORD')}@cluster0.jdhed.azure.mongodb.net/?retryWrites=true&w=majority")
    db = client.streamlit_html_generator
    return db

@st.cache_data(ttl=60*10)
def load_existing_sites(_db):
    return list(_db.sites.find({ "accepted": True }))

st.set_page_config(page_title="Streamlit Site Generator", layout="wide")
openai.api_key = os.getenv("OPENAI_API_KEY")
db = load_db()
websites = load_existing_sites(db)

Функция load_db устанавливает соединение с базой данных MongoDB (называемой streamlit_html_generator) и возвращает объект базы данных. Я использую переменную среды, чтобы пароль не раскрывался. Эти переменные полезны при развертывании приложения. Точно так же ключ API OpenAI предоставляется с переменной среды.

После этого метод load_existing_sites возвращает все общедоступные приложения, которые я принял. Оба метода используют кэширование для повышения производительности приложения, но cache_resource рекомендуется для определенных объектов, таких как модели машинного обучения или подключения к базе данных.

Затем создается функция для вызова OpenAI API:

def get_starting_convo():
    return [
        {
            "role": "system", 
            "content": "You are an assistant that helps the user create and improve a web page in HTML, CSS, and JavaScript."
        }
    ]

@st.cache_data(ttl=10, show_spinner=False)
def call_openai_api(conversation):
    st.spinner("The code is being generated...")
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=conversation,
        temperature=0.1
    )
    return response["choices"][0]["message"]["content"].strip()

Я использую API ChatGPT, и поэтому вместо одной подсказки выдается диалог. Беседа/сообщения инициализируются с помощью get_starting_convo. С помощью ChatGPT API вы обычно начинаете беседу с системного сообщения, а затем переключаетесь между пользователем (вы) и помощником (ИИ).

Входим в основную функцию:

def main():
    # initialize session state
    if "show_code" not in st.session_state:
        st.session_state.show_code = False
    if "messages" not in st.session_state:
        st.session_state.messages = get_starting_convo()

Я использую session_state streamlit для сохранения переменных во время сеанса пользователя. Эти строки инициализируют два из них.

Далее определяются заголовок и ввод:

st.title("Interactive Website Generator")
st.write(
    "Enter your desired website and let the AI generate it for you!"
)

user_input = st.text_area("Type your content:", height=200)

has_previously_generated = len(st.session_state.messages) > 1
reset = not has_previously_generated or st.checkbox("Reset", value=False)

Переменная reset позже используется для выбора между начальными инструкциями и более поздними инструкциями, где более поздние должны изменить ранее сгенерированный код.

Существует также флажок, который можно использовать для удаления исторических сообщений и перезапуска с нуля. Он виден только в том случае, если ранее действительно были сгенерированы сообщения, поэтому он добавляется после or, что означает, что он будет сгенерирован только в том случае, если первая переменная оценивается как False.

Далее идет логика, которая обрабатывает создание и изменение веб-сайта/приложения:

# change text depending on whether the user has previously generated code
if st.button("Generate website" if reset else "Ask AI to modify the generated website"):
    # set to starting conversation if reset
    messages = get_starting_convo() if reset else st.session_state.messages.copy()
    # decide prompt depending on whether the user has previously generated code
    if reset:
        messages.append({
            "role": "user", 
            "content": f"Create an HTML web page with accompanying CSS and JavaScript in a single HTML-file based on the following description: {user_input}"
        })
    else:
        messages.append({
            "role": "user", 
            "content": f"Modify the previous website to accomodate the following:\n\n{user_input}\n\n Note that you should recreate the HTML, CSS, and JavaScript code from scratch in its entirety. The new code should be self-contained in a single HTML-file."
        })
    # get the AI's response
    try:
        output = call_openai_api(messages)
    except Exception as e:
        st.error(f"Error while generating code. {str(e)}")
        return

    # extract the code block
    pattern = r"```(\w*)\n([\s\S]*?)\n```"
    code_blocks = re.findall(pattern, output)

    # should be in a single file
    if len(code_blocks) != 1:
        st.error("Something went wrong. Please try again or change the instructions.")
        return
    
    # append the assistant's response to the conversation
    messages.append({"role": "assistant", "content": output})

    # two groups are captured, the language and the code,
    # but only the code is needed
    st.session_state.code = code_blocks[0][1]
    st.session_state.output = output
    st.session_state.messages = messages

    # go from the beginning
    st.experimental_rerun()

Если кнопка нажата, код должен быть сгенерирован. В зависимости от того, изменяем ли мы предыдущий код или пишем новый код, используются разные инструкции. Если есть модификация, я подчеркиваю в сообщении-подсказке, что новый выведенный код должен быть исполняемым сам по себе. В противном случае он может просто добавить комментарий, говорящий мне сохранить старый код для определенных частей, но это трудно понять программно без дальнейшего использования ИИ.

Обратите внимание, что я использую копию предыдущих сообщений или новую версию сброса, а затем позже назначаю этот разговор состоянию сеанса:

messages = get_starting_convo() if reset else st.session_state.messages.copy()
...
session_state.messages = messages

Причина этого в том, что если приложение где-то выйдет из строя, разговор останется таким, каким он был до нажатия кнопки.

После этого я использую регулярное выражение для захвата кода внутри уценки. По умолчанию ChatGPT создает уценку и добавляет код в обратные кавычки, например:

```python
# some python code here
```

Я обнаружил, что часто бывает проще извлечь эти фрагменты кода с помощью регулярного выражения, чем пытаться убедить его никогда не выводить ничего, кроме кода. Другое преимущество заключается в том, что вы можете отобразить пояснения к коду позже. Недостатком является то, что будет потребляться больше токенов.

Затем я перезапускаю весь код для обновления переменных, используя st.experimental_rerun.

Теперь пришло время отобразить сгенерированный HTML/CSS/JavaScript сайта:

# display the generated html/css/javascript
# html is from from streamlit.components.v1 import html
if "code" in st.session_state and st.session_state.code is not None:
    st.header("Generated website")
    html(st.session_state.code, height=600, scrolling=True)
        
# display the code and explanations
if has_previously_generated and st.session_state.code is not None:
    # toggle between show and hide,
    # a placeholder (st.empty) is used to replace the
    # text of the button after it has been clicked
    show_hide_button = st.empty()
    get_show_code_text = lambda: "Show code and explanations" if not st.session_state.show_code else "Hide code and explanations"
    clicked = show_hide_button.button(get_show_code_text())
    if clicked:
        st.session_state.show_code = not st.session_state.show_code
        show_hide_button.button(get_show_code_text())
    if st.session_state.show_code:
        st.header("Code and explanations")
        st.markdown(st.session_state.output)

    # form to submit/publish the website
    st.header("Publish website")
    st.write("""
        If you publish the app and it is approved it will be displayed below for everyone to try out. By submitting
        you give me the full permission to do so.
    """)
    name = st.text_input("Add a name for the website")
    submit = st.button("Submit")

    if submit:
        if name == "":
            st.error("You need to add a name")
        else:
            db.sites.insert_one({ "name": name, "code": st.session_state.code, "accepted": False })
            st.success("Your website has been submitted for review.")

# display previously generated websites that can be opened
if websites:
    st.header("Try previously generated websites")
    for website in websites:
        click = st.button(website['name'], key=website["_id"])
        if click:
            db.sites.update_one({ "_id": website["_id"] }, { "$set": { "views": website.get("views", 0) + 1 } })
            html(website["code"], height=600, scrolling=True)

Все показано компонентом html из streamlit.components.v1. Затем есть кнопки, чтобы показать/скрыть код и пояснения от ИИ, а также возможность опубликовать веб-сайт. Я также обновляю счетчик количества просмотров каждого веб-сайта при нажатии на него.

Наконец, запускается код:

if __name__ == "__main__":
    main()

В общей сложности всего 150 строк кода.

Позвольте мне услышать, что вы думаете!

Если вам понравилась эта статья:

  • 👏 Хлоп, это поможет мне понять, что нравится моим читателям и чего они хотят больше
  • 🙏 Подписывайтесь или подписывайтесь, если хотите читать мои будущие статьи, новые каждую неделю!
  • 📚 Если вы ищете больше контента, посмотрите мои списки для чтения в разделах AI, Python или Data Science.
  • 🤝 Хотите нанять меня для консультационной работы? Свяжитесь со мной здесь

Спасибо за чтение и хорошего дня.