diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..219db65940de8eb2b1702fa7e3d48ab22310a95d Binary files /dev/null and b/.gitignore differ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..26d33521af10bcc7fd8cea344038eaaeb78d0ef5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/logos.iml b/.idea/logos.iml new file mode 100644 index 0000000000000000000000000000000000000000..2c80e1269497d12e018fd6afa29982e56b0fb70d --- /dev/null +++ b/.idea/logos.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..318f7ad3daa3b05a31d4e94744dcd5b341592caa --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..9746bb7a2a458074fcad2892ad1d8873cb2499b9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..94a25f7f4cb416c083d265558da75d457237d671 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1db34cd489e4ef3a719aed93d2de77a9ad520f66 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +--- +title: KonspektLogoped +emoji: 🚀 +colorFrom: blue +colorTo: green +sdk: gradio +sdk_version: 5.23.3 +app_file: app/main_gradio_test_FS.py +pinned: false +short_description: Помощник в создании логопедических конспектов +--- + + +CHECK +CHECK +CHECK +CHECK +git add . +git commit -m "..." +git push space main \ No newline at end of file diff --git a/TASKS.md b/TASKS.md new file mode 100644 index 0000000000000000000000000000000000000000..e6cbce5e7229807d2b32d937ba9f2b40af24bebf --- /dev/null +++ b/TASKS.md @@ -0,0 +1,27 @@ +[x] +[ ] + +## to-do +[ ] доп. функция - поиск по базе (file_search) +[ ] поле ввода веб-источников (ограничение web search) +[ ] сохранение истории сессий пользователя — очень легко сделать через st.session_state["history"]. +[ ] деплой в Hugging Face Spaces +[ ] анкета для тестовых пользователей - UX, содержание +[ ] файн-тюнинг мистейков +[ ] для тестовых пользователей - форма отправки мистейка +[ ] сделать базу с основными источниками - вместо web search как бы file search -- однозначная платная опция +[ ] Функция учета предыдущей генерации (assistants message - conversation) для модификации плана занятия (параметр store) +## in progress + +[ ] stream=True +[ ] web search настройка +[ ] ссылки удобнее в конце или на каждом источнике? + +## done +[x] интервью с логопедом о нужном функционале и параметрах +[x] определение входящих параметров +[x] определение используемых методов +[x] базовый промпт +[x] базовый MVP +[x] если при генерации я меняю настройки - генерация сбивается +[x] переход на gradio \ No newline at end of file diff --git a/app/.gradio/certificate.pem b/app/.gradio/certificate.pem new file mode 100644 index 0000000000000000000000000000000000000000..b85c8037f6b60976b2546fdbae88312c5246d9a3 --- /dev/null +++ b/app/.gradio/certificate.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/app/Stable.py b/app/Stable.py new file mode 100644 index 0000000000000000000000000000000000000000..642ae72b689216b938f6a9fe9856988ec544c593 --- /dev/null +++ b/app/Stable.py @@ -0,0 +1,168 @@ +import os +import asyncio +from dotenv import load_dotenv +from openai import OpenAI +import streamlit as st + +# Загрузка переменных окружения +load_dotenv() +api_key = os.getenv("API_KEY_openai") + + +def generate_lesson_plan(params): + client = OpenAI(api_key=api_key) + + prompt = f""" + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры: + - **Основное нарушение:** {params['нарушение']} + - **Возраст ребенка:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тематика:** {params['тематика']} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Инвентарь:** {params['инвентарь']} + - **Особые условия:** {params['особые_условия']} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Разрешен web search:** {params['разрешен_web_search']} + + На основе этих параметров составь конспект занятия, который должен включать: + 1. **Тема занятия, цель и задачи** + 2. **Необходимые материалы** + 3. **Ход занятия**. Разбей занятие на логически последовательные этапы. + В каждый этап встрой конкретные упражнения с достаточным количеством примеров. + ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников. + Обязательно интегрируй ссылки на найденные источники в текст ответа. + 5. **Домашнее задание** (если параметр "наличие домашнего задания: True") + 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия") + """ + + tools = [] + tool_choice = None + + if params['разрешен_web_search']: + tools.append({ + "type": "web_search_preview", + "search_context_size": "medium", + "user_location": { + "type": "approximate", + "country": "RU" + } + }) + tool_choice = {"type": "web_search_preview"} + + response = client.responses.create( + model="gpt-4o-mini", + input=prompt, + tools=tools if tools else None, + tool_choice=tool_choice, + max_output_tokens=2000 + ) + return response.output_text + + +# Интерфейс Streamlit +st.title("Генератор логопедических занятий") + +# Левая часть с настройками +with st.sidebar: + st.header("Настройки занятия") + + # Обязательные поля + нарушение = st.text_input( + "Основное нарушение*", + placeholder="Пример: Дислалия (свистящие), ОНР II уровня" + ) + + возраст_ребенка = st.text_input( + "Возраст ребенка*", + placeholder="Пример: 5 лет, 6-7 лет" + ) + + цель_занятия = st.text_input( + "Цель занятия*", + placeholder="Пример: Автоматизация звука [Р] в слогах" + ) + + # Переключатель формата занятия + формат_занятия = st.radio( + "Формат занятия*", + options=["Индивидуальное", "Групповое"], + index=0, + horizontal=True + ) + + количество_детей = 1 # Значение по умолчанию + + if формат_занятия == "Групповое": + количество_детей = st.number_input( + "Количество детей в группе*", + min_value=2, + max_value=10, + value=3 + ) + + # Дополнительные параметры + тематика = st.text_input( + "Тематика занятия", + placeholder="Пример: Животные, Транспорт" + ) + + особые_условия = st.text_input( + "Особые условия", + placeholder="Пример: гиперактивность, РАС" + ) + + # Инвентарь + st.subheader("Инвентарь") + инвентарь_чекбоксы = { + "Зеркало": st.checkbox("Зеркало"), + "Карточки": st.checkbox("Карточки"), + "Игрушки": st.checkbox("Игрушки") + } + дополнительный_инвентарь = st.text_input("Другое") + + инвентарь = ", ".join([k for k, v in инвентарь_чекбоксы.items() if v]) + if дополнительный_инвентарь: + инвентарь += f", {дополнительный_инвентарь}" + + # Другие настройки + длительность_занятия = st.slider( + "Длительность (минут)", + min_value=15, + max_value=60, + value=30, + step=5 + ) + + наличие_ДЗ = st.checkbox("Домашнее задание") + разрешен_web_search = st.checkbox("Поиск материалов в интернете") + + # Кнопка генерации + сгенерировать = st.button("Создать конспект") + +# Правая часть с результатом +if сгенерировать: + if not нарушение or not возраст_ребенка or not цель_занятия: + st.error("Заполните обязательные поля!") + else: + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "тематика": тематика, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "инвентарь": инвентарь, + "особые_условия": особые_условия, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search + } + + with st.spinner("Создаем конспект..."): + try: + результат = generate_lesson_plan(params) + st.success("Готово!") + st.markdown(результат) + except Exception as e: + st.error(f"Ошибка: {e}") \ No newline at end of file diff --git a/app/data/attributes_template.json b/app/data/attributes_template.json new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/app/data/existing_files.json b/app/data/existing_files.json new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/app/data/files_management.py b/app/data/files_management.py new file mode 100644 index 0000000000000000000000000000000000000000..f0c134e6105b2370fe2878b19c0943dc2bc220ba --- /dev/null +++ b/app/data/files_management.py @@ -0,0 +1,219 @@ +import os +import json +import hashlib +import logging +from openai import OpenAI +from dotenv import load_dotenv + +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +load_dotenv() +client = OpenAI(api_key=os.getenv("API_KEY_openai")) + +# ==== Управление этапами через флаги ==== +RUN_CREATE_VECTOR_STORE = False +RUN_UPLOAD_FILES = False +RUN_GENERATE_TEMPLATE = True +RUN_UPDATE_ATTRIBUTES = False +RUN_DELETE_FILE = False + +# ==== Параметры ==== +VECTOR_STORE_NAME = "Logoped_KB" +FILES_DIR = "materials/vector_store" +ATTRIBUTES_FILE = "attributes_template.json" +VS_ID = os.getenv("VECTOR_STORE_ID") +FILE_NAME_DELETE = "" + + + +# 1. Создание векторного хранилища +def create_vector_store(name: str) -> str: + """Создает новое векторное хранилище, если его еще нет, и возвращает его ID""" + + # Получаем список всех векторных хранилищ + existing_stores = client.vector_stores.list() + + # Проверяем, существует ли хранилище с таким же именем + for store in existing_stores: + if store.name == name: + logger.info(f"Хранилище с именем '{name}' уже существует, его ID: {store.id}") + return store.id # Возвращаем существующий ID хранилища + + # Если хранилище не найдено, создаем новое + vector_store = client.vector_stores.create(name=name) + logger.info(f"Создано хранилище: {vector_store.id}") + return vector_store.id + + +# 2. Загрузка файлов с проверкой дубликатов +def upload_files(vector_store_id: str, files_dir: str): + """ + Загружает новые файлы в хранилище, пропуская уже существующие + Возвращает список file_id новых файлов + """ + existing = load_existing_files() + new_files = [] + + for filename in os.listdir(files_dir): + file_path = os.path.join(files_dir, filename) + file_hash = calculate_hash(file_path) + + # Если файл с таким именем и незменённым хэшем уже существует – пропускаем + if filename in existing and existing[filename]['hash'] == file_hash: + logger.info(f"Файл {filename} уже загружен, пропуск") + continue + + try: + with open(file_path, "rb") as f: + # Загрузка файла в OpenAI (файл попадет в список "files") + uploaded_file = client.files.create(file=f, purpose="assistants") + + # Добавление файла в векторное хранилище с (пока пустыми) атрибутами + # (Если атрибуты вы укажете в update_attributes позже, + # здесь можно передать {} или заранее известные значения.) + client.vector_stores.files.create( + vector_store_id=vector_store_id, + file_id=uploaded_file.id, + attributes={} # атрибуты можно обновить позже + ) + + # Сохраняем сведения о файле локально + existing[filename] = { + 'file_id': uploaded_file.id, + 'hash': file_hash, + 'attributes': {} # и здесь храните текущие атрибуты + } + new_files.append(uploaded_file.id) + + except Exception as e: + logger.error(f"Ошибка загрузки {filename}: {str(e)}") + + save_existing_files(existing) + return new_files + + +# 3. Генерация шаблона метаданных +def generate_attributes_template(vector_store_id: str): + """Создает JSON-файл с текущими метаданными для ручного редактирования""" + files_info = load_existing_files() + vector_files = client.vector_stores.files.list(vector_store_id=vector_store_id) + + template = {} + for v_file in vector_files: + filename = next((k for k, v in files_info.items() if v['file_id'] == v_file.id), None) + if filename: + template[v_file.id] = { + "filename": filename, + "attributes": files_info[filename].get('attributes', {}) + } + + with open("attributes_template.json", "w") as f: + json.dump(template, f, indent=2, ensure_ascii=False) + logger.info("Шаблон метаданных сгенерирован") + + +# 4. Обновление метаданных +def update_attributes(vector_store_id: str, attributes_file: str): + """Обновляет метаданные файлов на основе редактированного шаблона""" + with open(attributes_file, "r") as f: + attributes = json.load(f) + + existing = load_existing_files() + updated = 0 + + for file_id, data in attributes.items(): + filename = data['filename'] + if filename not in existing: + continue + + try: + # Обновляем метаданные через API + client.vector_stores.files.update( + vector_store_id=vector_store_id, + file_id=file_id, + attributes=data['attributes'] + ) + + # Обновляем локальную копию + existing[filename]['attributes'] = data['attributes'] + updated += 1 + + except Exception as e: + logger.error(f"Ошибка обновления {filename}: {str(e)}") + + save_existing_files(existing) + logger.info(f"Обновлено {updated} записей") + + +# Вспомогательные функции +def calculate_hash(file_path: str) -> str: + """Вычисляет SHA-256 хеш файла для отслеживания изменений""" + sha = hashlib.sha256() + with open(file_path, "rb") as f: + while True: + data = f.read(65536) + if not data: + break + sha.update(data) + return sha.hexdigest() + + +def load_existing_files() -> dict: + """Загружает информацию о ранее загруженных файлах""" + try: + with open("existing_files.json", "r") as f: + return json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + return {} + + +def save_existing_files(data: dict): + """Сохраняет информацию о загруженных файлах""" + with open("existing_files.json", "w") as f: + json.dump(data, f, indent=2) + + +def delete_file_by_filename(filename: str): + """ + Удаляет файл из OpenAI по имени (если он есть в existing_files.json) + """ + existing = load_existing_files() + if filename not in existing: + print(f"Файл '{filename}' не найден в existing_files.json") + return + + file_id = existing[filename]['file_id'] + vector_store_id = VS_ID + try: + client.vector_stores.files.delete( + vector_store_id=vector_store_id, + file_id=file_id + ) + print(f"Файл '{filename}' c ID {file_id} успешно удалён") + + # Удаляем из локального кэша + del existing[filename] + save_existing_files(existing) + except Exception as e: + print(f"Ошибка при удалении файла '{file_id}': {str(e)}") + +if __name__ == "__main__": + + # ==== Запуск этапов ==== + if RUN_CREATE_VECTOR_STORE: + VS_ID = create_vector_store(VECTOR_STORE_NAME) + + if RUN_UPLOAD_FILES: + new_files = upload_files(VS_ID, FILES_DIR) + else: + new_files = [] + + if RUN_GENERATE_TEMPLATE: + generate_attributes_template(VS_ID) + + if RUN_UPDATE_ATTRIBUTES: + update_attributes(VS_ID, ATTRIBUTES_FILE) + + if RUN_DELETE_FILE: + delete_file_by_filename(FILE_NAME_DELETE) \ No newline at end of file diff --git a/app/drawings.py b/app/drawings.py new file mode 100644 index 0000000000000000000000000000000000000000..2867eaa42f02181b40aa7d3c1603c2ffd741e167 --- /dev/null +++ b/app/drawings.py @@ -0,0 +1,128 @@ +drawings = [ + r""" + \ | / + .-'-. + ― ( ☀ ) ― + '-.-' + / | \ + ️ + + ___I_ + /\-_--\ + / \_-__\ + | [] | [] | + |____|____| + """, + + r""" + ################### + # .-----._ # + # | O O | # ← Глаза в зеркале + # | ▽ | # ← Рот + # '-----' "Р-р-р!" # + # || # + ################### + + |\_/| + / \ + /_~_~_~_\ + \o_o_o_o/ котик + || || + + """, + + r""" + ╔════════════════════════════════════════════╗ + ║ ║ + ║ ║ + ║ ║ + ║ РА РО РУ РЫ ║ + ║ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ║ + ║ │Р│ │Р│ │Р│ │Р│ ║ + ║ └─┘ └─┘ └─┘ └─┘ ║ + ║ ║ + ║ ( ) ← "Язычок!" ║ + ║ /___\ ║ + ║ | | ║ + ║ ║ + ║ ░░░░░░░░░ ║ + ║ ░ ░ ← "Это море!" ║ + ║ ░ >< ░ ║ + ║ ░░░░░░░░░ ║ + ║ ║ + ║ O O O ← "Шарики!" ║ + ║ \ | / ║ + ║ \|/ ║ + ║ "Фуух!" (дуем) ║ + ║ ║ + ║ АБВГДЕЁЖ ← "Азбука" ║ + ║ \_____/ ║ + ║ "Ура!" ║ + ║ ║ + ║ ♪~ ♪~ ♪~ ← "Пою звуки!" ║ + ║ ║ + ╚════════════════════════════════════════════╝ + """, + + r""" + _________________________________________________ + / \ + | ( ) | + | /___\ ← "Язычок на горке!" | + | / \ | + | _/ \_ | + | | | | + | | ДОМ | ← "А это где звук живет!" | + | |_______| | + | | + | * * * | + | \ | / ← "Дуем на свечки!" | + | \ | / | + | "Оооо" | + | | + | ♪ ♪ ♪ | + | "Пою гласные!" | + \_________________________________________________/ \ + """, + r""" + _________________________________________________ + / \ + | РА РО РУ | + | ┌──┐ ┌──┐ ┌──┐ | + | │★ │ │★ │ │★ │ ← "Звездные слоги" | + | └──┘ └──┘ └──┘ | + | | + | .-. | + | ( О ) ← "Луна!" | + | `-' | + | / \ | + | / \ | + | "А-а-а-а!" (кричит в космос) | + | | + | ∞∞∞∞∞∞∞∞∞ | + | "Ракета летит!" | + \_________________________________________________/ \ + """, + r""" + _________________________________________________ + / \ + | .-. .-. | + | ( Р ) ( Ш ) ← "Эти звуки дружат!" | + | `-' `-' | + | | | | + | / \ / \ | + | "Р-р-р" "Ш-ш-ш" | + | | + | O O O | + | \ | / ← Шарики для дыхания | + | \ | / | + | "Фууух!" | + | | + | ░░░░░░░░░░░░░░░ | + | ░ ░ ← "А это море!" | + | ░ ><((((°> ░ | + | ░ ░ | + | ░░░░░░░░░░░░░░░ | + \_________________________________________________/ + """, + ] diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000000000000000000000000000000000000..5292cc24039a6419b53e3d7a7b48c34c19ef3677 --- /dev/null +++ b/app/main.py @@ -0,0 +1,347 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI +import gradio as gr +import time +from datetime import datetime +import calendar +from docx import Document +import tempfile +import random # для случайного выбора цитаты +from app.quotes import quotes +import logging + +# Настройка логирования +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[logging.StreamHandler()] +) # Вывод в консоль + +# Загрузка переменных окружения +load_dotenv() +api_key = os.getenv("API_KEY_openai") +client = OpenAI(api_key=api_key) + +# API_KEY = os.getenv("API_KEY") +# BASE_URL = os.getenv("BASE_URL") +# client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + + +# Функция генерации плана занятия +def generate_lesson_plan_interface( + нарушение, возраст_ребенка, особые_условия, + формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия, + инвентарь, наличие_ДЗ, разрешен_web_search, текущий_месяц=None +): + # Логика работы с источниками - если есть, передаем + #источники = [web_sources] if разрешен_web_search and web_sources else [] + + + # Логика определения месяца + if not текущий_месяц: + текущий_месяц = calendar.month_name[datetime.now().month] + + # все параметры + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "тема": тема, + "особые_условия": особые_условия, + "инвентарь": инвентарь, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search, + #"источники": источники, + "текущий_месяц": текущий_месяц + } + + if формат_занятия == "Индивидуальное": + params["количество_детей"] = 1 + + # Формируем основной промпт + base_prompt = f""" + Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями. + На основе параметров занятия составь план занятия, который должен включать: + 1. **Тема занятия, цель и задачи** (не более 3-х) + 2. **Необходимый инвентарь** + 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и примерами""" + + # Добавляем секцию для веб-поиска только если разрешено + web_search_section = "" + if разрешен_web_search: + web_search_section = """ + **ВНИМАНИЕ:** Используй актуальные данные из интернета: + - Найди примеры релевантных упражнений и интегрируй их в этапы + - При необходимости адаптируй упражнения по параметрам ребенка и занятия + - Для каждого заимствованного упражнения укажи источник (ссылку)""" + + # Продолжение базового промпта + continuation = f""" + 4. **Домашнее задание** (только если параметр "наличие домашнего задания" = True) + 5. **Рекомендации по особым условиям** (если указаны) + + Параметры занятия: + - **Основное нарушение:** {нарушение} + - **Возраст:** {возраст_ребенка} + - **Цель занятия:** {цель_занятия} + - **Тема:** {тема or "не указано - определи самостоятельно"} + - **Формат:** {формат_занятия} ({количество_детей} детей) + - **Инвентарь:** {инвентарь or "не указан - предложи варианты"} + - **Особые условия:** {особые_условия or "отсутствуют"} + - **Длительность:** {длительность_занятия} минут + - **Месяц года:** {текущий_месяц} + + Стиль оформления: + - Четкая структура с нумерованными пунктами + - Минимум общих фраз, максимум конкретики + - Возрастосообразные примеры и материалы""" + + # Собираем полный промпт + full_prompt = base_prompt + web_search_section + continuation + + tools = [] + tool_choice = None + + if params['разрешен_web_search']: + tools.append({ + "type": "web_search_preview", + "search_context_size": "medium", + "user_location": {"type": "approximate", "country": "RU"} + }) + tool_choice = {"type": "web_search_preview"} + + response = client.responses.create( + model="gpt-4o-mini", + input=full_prompt, + temperature=0.5, + tools=tools if tools else None, + tool_choice=tool_choice, + max_output_tokens=2000, + stream=True + ) + + +####### БЕЗ СТРИМИНГА +# return response.output_text # Основной вывод без изменений + +# АННОТАЦИИ В ЛОГ +# if params['разрешен_web_search']: # Только логируем аннотации при веб-поиске +# try: +# content_block = response.output[1].content[0] +# # logging.info(f"=== ПОЛНЫЙ КОНТЕНТ БЛОКА ===") +# # logging.info(f"Тип: {content_block.type}") +# # logging.info(f"Текст: {content_block.text[:200]}...") # Первые 200 символов текста +# logging.info(f"Аннотации: {content_block.annotations}") +# # logging.info(f"Сырые данные: {vars(content_block)}") # Вся техническая информация +# except (IndexError, AttributeError) as e: +# logging.warning(f"Не удалось получить аннотации: {str(e)}") + +####### СТРИМИНГ + try: + for event in response: + if event.type == 'response.output_text.delta': + yield event.delta + elif event.type == 'response.completed': + break + except Exception as e: + yield f"Ошибка: {str(e)}" + + +############# COMPLETIONS (РАБОТАЕТ БЕЗ TOOLS) +# response = client.chat.completions.create( +# model="gpt-4o-mini", +# messages=[ +# {"role": "system", "content": "Ты — эксперт в области логопедии."}, +# {"role": "user", "content": prompt} +# ], +# max_tokens=2000, +# stream=True # Включаем потоковый режим +# ) + +# for chunk in response: +# if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content: +# yield chunk.choices[0].delta.content + + + +### css привязывать именно так +css_path = os.path.join(os.path.dirname(__file__), "styles.css") +theme='earneleh/paris' +#, css_paths=css_path +# Интерфейс Gradio +with gr.Blocks(theme=theme) as demo: + gr.Markdown("## Логопедический конспект", elem_classes=["main-title"]) + quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"]) + + with gr.Row(): + # Первый блок настройки (Ребенок) + with gr.Column(elem_classes=["left-col"], scale=1): + gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"]) + нарушение = gr.Textbox(label="Основное нарушение*", placeholder="Пример: Дислалия (свистящие), ОНР II уровня") + возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет") + особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС") + + gr.Markdown("### 📄 Занятие", elem_classes=["block-title"]) + формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное") + количество_детей = gr.Slider( + label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False + ) + def toggle_group_slider(selected_format): + return gr.update(visible=(selected_format == "Групповое")) + + + формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей) + + цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах") + тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт") + длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5) + инвентарь = gr.Textbox(label="Инвентарь (через запятую)", + placeholder="Пример: Зеркало, Карточки, Куклы") + + дз = gr.Checkbox(label="Домашнее задание") + # Выделенный блок для профессиональных ресурсов + gr.Markdown("---") # Разделительная линия + # gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"]) + + with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок + web = gr.Checkbox( + label="📚 Поиск упражнений в логопедических базах", + info="Упражнения подбираются из проверенных источников со ссылками", + interactive=True + ) + + #web_sources = gr.Textbox( + # label="Уточнить источники (необязательно, через запятую):", + # placeholder="Например: Logopedy.ru, Maam.ru, ...", + # visible=False, + #) + + # Логика отображения поля источников + #web.change( + # fn=lambda x: gr.update(visible=x), + # inputs=web, + # outputs=web_sources + #) + + # Кнопка создания конспекта + btn = gr.Button("Создать конспект", variant="primary") + + # Правая колонка — результат (output) + with gr.Column(elem_classes=["right-col"], scale=2): # Правая колонка — результат + gr.Markdown("### План урока", elem_classes=["block-title"]) + output = gr.Markdown( + "Здесь появится план урока после генерации.", + elem_id="plan-output" + ) + + download_btn = gr.DownloadButton( + label="⬇️ Скачать .docx", + visible=False + ) + output = gr.Markdown("") # Поле для вывода конспекта + + # Ввод параметров + all_inputs = [ + нарушение, возраст, особые_условия, + формат, количество_детей, цель, тема, длительность, + инвентарь, дз, web #web_sources + ] + + + # Функция для генерации docx файла + def generate_docx(text: str): + doc = Document() + for line in text.split("\n"): + doc.add_paragraph(line) + tmp_dir = tempfile.gettempdir() + file_path = os.path.join(tmp_dir, "Конспект_занятия.docx") + doc.save(file_path) + return file_path + +################### СТРИМИНГ + def on_submit_with_spinner(*args): + # Проверка обязательных полей (остается без изменений) + if not args[0] or not args[1] or not args[5]: + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), + gr.update(visible=False) + ) + return + + # Блокируем интерфейс + yield ( + *[gr.update(interactive=False) for _ in all_inputs], + gr.update(value="⏳ Конспект создается..."), + gr.update(visible=False) + ) + + full_response = [] + try: + for chunk in generate_lesson_plan_interface(*args): + full_response.append(chunk) + yield ( + *[gr.update(interactive=False) for _ in all_inputs], + gr.update(value="".join(full_response)), + gr.update(visible=False) + ) + + # После завершения стрима + file_path = generate_docx("".join(full_response)) + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value="".join(full_response)), + gr.update(visible=True, value=file_path) + ) + + except Exception as e: + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value=f"❌ Ошибка: {str(e)}"), + gr.update(visible=False) + ) + +################## БЕЗ СТРИМИНГА +# def on_submit_with_spinner(*args): +# if not args[0] or not args[1] or not args[5]: +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), +# gr.update(visible=False) +# ) + + # Блокируем интерфейс +# try: +# response_text = generate_lesson_plan_interface(*args) +# file_path = generate_docx(response_text) +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value=response_text), +# gr.update(visible=True, value=file_path) +# ) + +# except Exception as e: +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value=f"❌ Ошибка: {str(e)}"), +# gr.update(visible=False) +# ) + + + # Привязываем обработчики + btn.click( + fn=on_submit_with_spinner, + inputs=all_inputs, + outputs=[*all_inputs, output, download_btn] # <- скобку если не стримминг + ).then( + lambda: None, + inputs=[], + outputs=[] + ) + +if __name__ == "__main__": + demo.launch(share=True) \ No newline at end of file diff --git a/app/main_completions.py b/app/main_completions.py new file mode 100644 index 0000000000000000000000000000000000000000..194a05958f74bc0e6fce63d50c71904b957eee88 --- /dev/null +++ b/app/main_completions.py @@ -0,0 +1,191 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI +import streamlit as st + +# Загрузка переменных окружения +load_dotenv() +#api_key = os.getenv("API_KEY_openai") +API_KEY = os.getenv("API_KEY") +BASE_URL = os.getenv("BASE_URL") + + +def generate_lesson_plan(params): +# client = OpenAI(api_key=api_key) + client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + prompt = f""" + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры: + - **Основное нарушение:** {params['нарушение']} + - **Возраст ребенка:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тематика:** {params['тематика']} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Инвентарь:** {params['инвентарь']} + - **Особые условия:** {params['особые_условия']} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Разрешен web search:** {params['разрешен_web_search']} + + На основе этих параметров составь конспект занятия, который должен включать: + 1. **Тема занятия, цель и задачи** + 2. **Необходимые материалы** + 3. **Ход занятия**. Разбей занятие на логически последовательные этапы. + В каждый этап встрой конкретные упражнения с достаточным количеством примеров. + ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников. + Обязательно интегрируй ссылки на найденные источники в текст ответа. + 5. **Домашнее задание** (если параметр "наличие домашнего задания: True") + 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия") + """ + +# tools = [] +# tool_choice = None + +# if params['разрешен_web_search']: +# tools.append({ +# "type": "web_search_preview", +# "search_context_size": "medium", +# "user_location": { +# "type": "approximate", +# "country": "RU" +# } +# }) +# tool_choice = {"type": "web_search_preview"} + +# response = client.responses.create( +# model="gpt-4o-mini", +# input=prompt, +# tools=tools if tools else None, +# tool_choice=tool_choice, +# max_output_tokens=2000 +# ) +# return response.output_text +# + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": "Ты — эксперт в области логопедии."}, + {"role": "user", "content": prompt} + ], + max_tokens=2000 + ) + return response.choices[0].message.content + + +# Интерфейс Streamlit +st.title("Генератор логопедических занятий") + +# Инициализация флагов в session_state +if "loading" not in st.session_state: + st.session_state["loading"] = False +if "lesson_plan" not in st.session_state: + st.session_state["lesson_plan"] = "" + +# Используем обычный блок в сайдбаре для динамических виджетов +with st.sidebar: + st.header("Настройки занятия") + + нарушение = st.text_input( + "Основное нарушение*", + placeholder="Пример: Дислалия (свистящие), ОНР II уровня", + disabled=st.session_state["loading"] + ) + возраст_ребенка = st.text_input( + "Возраст ребенка*", + placeholder="Пример: 5 лет, 6-7 лет", + disabled=st.session_state["loading"] + ) + цель_занятия = st.text_input( + "Цель занятия*", + placeholder="Пример: Автоматизация звука [Р] в слогах", + disabled=st.session_state["loading"] + ) + + формат_занятия = st.radio( + "Формат занятия*", + options=["Индивидуальное", "Групповое"], + index=0, + horizontal=True, + disabled=st.session_state["loading"] + ) + + # Это поле появляется динамически, когда выбрано "Групповое" + if формат_занятия == "Групповое": + количество_детей = st.number_input( + "Количество детей в группе*", + min_value=2, + max_value=10, + value=3, + disabled=st.session_state["loading"] + ) + else: + количество_детей = 1 + + тематика = st.text_input( + "Тематика занятия", + placeholder="Пример: Животные, Транспорт", + disabled=st.session_state["loading"] + ) + особые_условия = st.text_input( + "Особые условия", + placeholder="Пример: гиперактивность, РАС", + disabled=st.session_state["loading"] + ) + + st.subheader("Инвентарь") + инвентарь_чекбоксы = { + "Зеркало": st.checkbox("Зеркало", disabled=st.session_state["loading"]), + "Карточки": st.checkbox("Карточки", disabled=st.session_state["loading"]), + "Игрушки": st.checkbox("Игрушки", disabled=st.session_state["loading"]) + } + дополнительный_инвентарь = st.text_input("Другое", disabled=st.session_state["loading"]) + инвентарь = ", ".join([k for k, v in инвентарь_чекбоксы.items() if v]) + if дополнительный_инвентарь: + инвентарь += f", {дополнительный_инвентарь}" + инвентарь = инвентарь.strip(", ") + + длительность_занятия = st.slider( + "Длительность (минут)", + min_value=15, + max_value=60, + value=30, + step=5, + disabled=st.session_state["loading"] + ) + наличие_ДЗ = st.checkbox("Домашнее задание", disabled=st.session_state["loading"]) + разрешен_web_search = st.checkbox("Поиск материалов в интернете", disabled=st.session_state["loading"]) + + # Кнопка отправки, блокируется если идет генерация + submit = st.button("Создать конспект", disabled=st.session_state["loading"], key="submit_btn") + +# Если кнопка нажата, запускаем генерацию +if submit: + if not нарушение or not возраст_ребенка or not цель_занятия: + st.sidebar.error("Заполните обязательные поля!") + else: + st.session_state["loading"] = True # Блокируем виджеты + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "тематика": тематика, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "инвентарь": инвентарь, + "особые_условия": особые_условия, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search + } + with st.spinner("Создаем конспект..."): + try: + результат = generate_lesson_plan(params) + st.session_state["lesson_plan"] = результат + st.success("Готово!") + except Exception as e: + st.error(f"Ошибка: {e}") + finally: + st.session_state["loading"] = False + +# Отображаем ранее сгенерированный конспект, если он есть и генерация не идёт +if st.session_state.get("lesson_plan", "") and not st.session_state["loading"]: + st.markdown(st.session_state["lesson_plan"]) \ No newline at end of file diff --git a/app/main_gradio.py b/app/main_gradio.py new file mode 100644 index 0000000000000000000000000000000000000000..b400fca89d8f7fc775831adf73c6698ae04291e0 --- /dev/null +++ b/app/main_gradio.py @@ -0,0 +1,183 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI +import gradio as gr +import time + +# Загрузка переменных окружения +load_dotenv() +API_KEY = os.getenv("API_KEY") +BASE_URL = os.getenv("BASE_URL") +#client = OpenAI(api_key=api_key) +client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + + +# Функция генерации плана занятия +def generate_lesson_plan_interface( + нарушение, возраст_ребенка, цель_занятия, формат_занятия, + количество_детей, тематика, особые_условия, + зеркало, карточки, игрушки, дополнительный_инвентарь, + длительность_занятия, наличие_ДЗ, разрешен_web_search +): + инвентарь = [] + if зеркало: инвентарь.append("Зеркало") + if карточки: инвентарь.append("Карточки") + if игрушки: инвентарь.append("Игрушки") + if дополнительный_инвентарь: инвентарь.append(дополнительный_инвентарь) + инвентарь = ", ".join(инвентарь) + + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "тематика": тематика, + "особые_условия": особые_условия, + "инвентарь": инвентарь, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search + } + + prompt = f""" + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры: + - **Основное нарушение:** {params['нарушение']} + - **Возраст ребенка:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тематика:** {params['тематика']} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Инвентарь:** {params['инвентарь']} + - **Особые условия:** {params['особые_условия']} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Разрешен web search:** {params['разрешен_web_search']} + + На основе этих параметров составь конспект занятия, который должен включать: + 1. **Тема занятия, цель и задачи** + 2. **Необходимые материалы** + 3. **Ход занятия**. Разбей занятие на логически последовательные этапы. + В каждый этап встрой конкретные упражнения с достаточным количеством примеров. + ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников. + Обязательно интегрируй ссылки на найденные источники в текст ответа. + 5. **Домашнее задание** (если параметр "наличие домашнего задания: True") + 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия") + """ + + # tools = [] + # tool_choice = None + + # if params['разрешен_web_search']: + # tools.append({ + # "type": "web_search_preview", + # "search_context_size": "medium", + # "user_location": { + # "type": "approximate", + # "country": "RU" + # } + # }) + # tool_choice = {"type": "web_search_preview"} + + # response = client.responses.create( + # model="gpt-4o-mini", + # input=prompt, + # tools=tools if tools else None, + # tool_choice=tool_choice, + # max_output_tokens=2000 + # ) + # return response.output_text + # + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": "Ты — эксперт в области логопедии."}, + {"role": "user", "content": prompt} + ], + max_tokens=2000 + ) + return response.choices[0].message.content + +# Интерфейс Gradio +with gr.Blocks() as demo: + gr.Markdown("## 🧠 Генератор логопедических занятий") + + with gr.Row(): + нарушение = gr.Textbox(label="Основное нарушение*", placeholder="Пример: Дислалия (свистящие), ОНР II уровня") + возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет") + цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах") + + тематика = gr.Textbox(label="Тематика", placeholder="Пример: Животные, Транспорт") + особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС") + + формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия*", value="Индивидуальное") + количество_детей = gr.Slider( + label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False + ) + + def toggle_group_slider(selected_format): + return gr.update(visible=(selected_format == "Групповое")) + + + формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей) + + with gr.Group(): + gr.Markdown("### Инвентарь") + зеркало = gr.Checkbox(label="Зеркало") + карточки = gr.Checkbox(label="Карточки") + игрушки = gr.Checkbox(label="Игрушки") + дополнительный = gr.Textbox(label="Дополнительно", placeholder="Пример: мячи, планшеты") + + длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5) + дз = gr.Checkbox(label="Домашнее задание") + web = gr.Checkbox(label="Разрешить поиск в интернете") + + btn = gr.Button("Создать конспект") + output = gr.Markdown() + + def on_submit(*args): + return generate_lesson_plan_interface(*args) + + # Элементы, которые будем блокировать во время генерации + all_inputs = [ + нарушение, возраст, цель, формат, количество_детей, + тематика, особые_условия, зеркало, карточки, игрушки, + дополнительный, длительность, дз, web, btn + ] + + def on_submit_with_spinner( + нарушение, возраст, цель, формат, количество_детей, + тематика, особые_условия, зеркало, карточки, игрушки, + дополнительный, длительность, дз, web + ): + # Блокируем все элементы и выводим "Генерация..." + for elem in all_inputs: + elem.interactive = False + yield gr.update(value="⏳ Генерация конспекта...") + + # Генерация + result = generate_lesson_plan_interface( + нарушение, возраст, цель, формат, количество_детей, + тематика, особые_условия, зеркало, карточки, игрушки, + дополнительный, длительность, дз, web + ) + + # Выводим результат + yield gr.update(value=result) + + # Разблокируем элементы + for elem in all_inputs: + elem.interactive = True + + btn.click( + fn=on_submit_with_spinner, + inputs=[ + нарушение, возраст, цель, формат, количество_детей, + тематика, особые_условия, зеркало, карточки, игрушки, + дополнительный, длительность, дз, web + ], + outputs=output, + show_progress=True + ) + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/app/main_gradio_nostream.py b/app/main_gradio_nostream.py new file mode 100644 index 0000000000000000000000000000000000000000..08e73f91a0dece48004b8963ec043bdd0e71cd08 --- /dev/null +++ b/app/main_gradio_nostream.py @@ -0,0 +1,343 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI +import gradio as gr +import time +from datetime import datetime +import calendar +from docx import Document +import tempfile +import random # для случайного выбора цитаты +from app.quotes import quotes +import logging + +# Настройка логирования +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[logging.StreamHandler()] +) # Вывод в консоль + +# Загрузка переменных окружения +load_dotenv() +api_key = os.getenv("API_KEY_openai") +client = OpenAI(api_key=api_key) + +# API_KEY = os.getenv("API_KEY") +# BASE_URL = os.getenv("BASE_URL") +# client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + + +# Функция генерации плана занятия +def generate_lesson_plan_interface( + нарушение, возраст_ребенка, особые_условия, + формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия, + инвентарь, наличие_ДЗ, разрешен_web_search, текущий_месяц=None +): + # Логика работы с источниками - если есть, передаем + #источники = [web_sources] if разрешен_web_search and web_sources else [] + + + # Логика определения месяца + if not текущий_месяц: + текущий_месяц = calendar.month_name[datetime.now().month] + + # все параметры + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "тема": тема, + "особые_условия": особые_условия, + "инвентарь": инвентарь, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search, + #"источники": источники, + "текущий_месяц": текущий_месяц + } + + if формат_занятия == "Индивидуальное": + params["количество_детей"] = 1 + + # Формируем основной промпт + base_prompt = f""" + Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями. + На основе параметров занятия составь план занятия, который должен включать: + 1. **Тема занятия, цель и задачи** (не более 3-х) + 2. **Необходимый инвентарь** + 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и примерами""" + + # Добавляем секцию для веб-поиска только если разрешено + web_search_section = "" + if разрешен_web_search: + web_search_section = """ + **ВНИМАНИЕ:** Используй актуальные данные из интернета: + - Найди примеры релевантных упражнений и интегрируй их в этапы + - При необходимости адаптируй упражнения по параметрам ребенка и занятия + - Для каждого заимствованного упражнения укажи источник (ссылку)""" + + # Продолжение базового промпта + continuation = f""" + 4. **Домашнее задание** (только если параметр "наличие домашнего задания" = True) + 5. **Рекомендации по особым условиям** (если указаны) + + Параметры занятия: + - **Основное нарушение:** {нарушение} + - **Возраст:** {возраст_ребенка} + - **Цель занятия:** {цель_занятия} + - **Тема:** {тема or "не указано - определи самостоятельно"} + - **Формат:** {формат_занятия} ({количество_детей} детей) + - **Инвентарь:** {инвентарь or "не указан - предложи варианты"} + - **Особые условия:** {особые_условия or "отсутствуют"} + - **Длительность:** {длительность_занятия} минут + - **Месяц года:** {текущий_месяц} + + Стиль оформления: + - Четкая структура с нумерованными пунктами + - Минимум общих фраз, максимум конкретики + - Возрастосообразные примеры и материалы""" + + # Собираем полный промпт + full_prompt = base_prompt + web_search_section + continuation + + tools = [] + tool_choice = None + + if params['разрешен_web_search']: + tools.append({ + "type": "web_search_preview", + "search_context_size": "medium", + "user_location": {"type": "approximate", "country": "RU"} + }) + tool_choice = {"type": "web_search_preview"} + + response = client.responses.create( + model="gpt-4o-mini", + input=full_prompt, + temperature=0.5, + tools=tools if tools else None, + tool_choice=tool_choice, + max_output_tokens=2000, + stream=False + ) + + +####### БЕЗ СТРИМИНГА + return response.output_text # Основной вывод без изменений + +# АННОТАЦИИ В ЛОГ +# if params['разрешен_web_search']: # Только логируем аннотации при веб-поиске +# try: +# content_block = response.output[1].content[0] +# # logging.info(f"=== ПОЛНЫЙ КОНТЕНТ БЛОКА ===") +# # logging.info(f"Тип: {content_block.type}") +# # logging.info(f"Текст: {content_block.text[:200]}...") # Первые 200 символов текста +# logging.info(f"Аннотации: {content_block.annotations}") +# # logging.info(f"Сырые данные: {vars(content_block)}") # Вся техническая информация +# except (IndexError, AttributeError) as e: +# logging.warning(f"Не удалось получить аннотации: {str(e)}") + +####### СТРИМИНГ +# try: +# for event in response: +# if event.type == 'response.output_text.delta': +# yield event.delta +# elif event.type == 'response.completed': +# break +# except Exception as e: +# yield f"Ошибка: {str(e)}" + + +############# COMPLETIONS (РАБОТАЕТ БЕЗ TOOLS) +# response = client.chat.completions.create( +# model="gpt-4o-mini", +# messages=[ +# {"role": "system", "content": "Ты — эксперт в области логопедии."}, +# {"role": "user", "content": prompt} +# ], +# max_tokens=2000, +# stream=True # Включаем потоковый режим +# ) + +# for chunk in response: +# if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content: +# yield chunk.choices[0].delta.content + +current_dir = os.path.dirname(__file__) +css_path = os.path.join(current_dir, "styles.css") + +# Интерфейс Gradio +with gr.Blocks(css_paths=css_path) as demo: + gr.Markdown("## Логопедический конспект", elem_classes=["main-title"]) + gr.HTML("") + + with gr.Row(): + # Первый блок настройки (Ребенок) + with gr.Column(scale=1): + quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"]) + + gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"]) + нарушение = gr.Textbox(label="Основное нарушение*", + placeholder="Пример: Дислалия (свистящие), ОНР II уровня") + возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет") + особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС") + + gr.Markdown("### 📄 Занятие", elem_classes=["block-title"]) + формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное") + количество_детей = gr.Slider( + label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False + ) + + + def toggle_group_slider(selected_format): + return gr.update(visible=(selected_format == "Групповое")) + + + формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей) + + цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах") + тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт") + длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5) + инвентарь = gr.Textbox(label="Инвентарь (через запятую)", + placeholder="Пример: Зеркало, Карточки, Куклы") + + дз = gr.Checkbox(label="Домашнее задание") + # Выделенный блок для профессиональных ресурсов + gr.Markdown("---") # Горизонтальная разделительная линия + # gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"]) + + with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок + web = gr.Checkbox( + label="📚 Поиск упражнений в логопедических базах", + info="Упражнения подбираются из проверенных источников со ссылками", + interactive=True + ) + + #web_sources = gr.Textbox( + # label="Уточнить источники (необязательно, через запятую):", + # placeholder="Например: Logopedy.ru, Maam.ru, ...", + # visible=False, + #) + + # Логика отображения поля источников + #web.change( + # fn=lambda x: gr.update(visible=x), + # inputs=web, + # outputs=web_sources + #) + + # Кнопка создания конспекта + btn = gr.Button("Создать конспект", variant="primary") + + # Правая колонка — результат (output) + with gr.Column(scale=2): # Правая колонка — результат + download_btn = gr.DownloadButton( + label="⬇️ Скачать .docx", + visible=False + ) + output = gr.Markdown("") # Поле для вывода конспекта + + # Ввод параметров + all_inputs = [ + нарушение, возраст, особые_условия, + формат, количество_детей, цель, тема, длительность, + инвентарь, дз, web #web_sources + ] + + + # Функция для генерации docx файла + def generate_docx(text: str): + doc = Document() + for line in text.split("\n"): + doc.add_paragraph(line) + tmp_dir = tempfile.gettempdir() + file_path = os.path.join(tmp_dir, "Конспект_занятия.docx") + doc.save(file_path) + return file_path + +################### СТРИМИНГ +# def on_submit_with_spinner(*args): +# # Проверка обязательных полей (остается без изменений) +# if not args[0] or not args[1] or not args[5]: +# yield ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), +# gr.update(visible=False) +# ) +# return + + # Блокируем интерфейс +# yield ( +# *[gr.update(interactive=False) for _ in all_inputs], +# gr.update(value="⏳ Конспект создается..."), +# gr.update(visible=False) +# ) + +# full_response = [] +# try: +# for chunk in generate_lesson_plan_interface(*args): +# full_response.append(chunk) +# yield ( +# *[gr.update(interactive=False) for _ in all_inputs], +# gr.update(value="".join(full_response)), +# gr.update(visible=False) +# ) + + # После завершения стрима +# file_path = generate_docx("".join(full_response)) +# yield ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value="".join(full_response)), +# gr.update(visible=True, value=file_path) +# ) + +# except Exception as e: +# yield ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value=f"❌ Ошибка: {str(e)}"), +# gr.update(visible=False) +# ) + +################## БЕЗ СТРИМИНГА + def on_submit_with_spinner(*args): + if not args[0] or not args[1] or not args[5]: + return ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), + gr.update(visible=False) + ) + + # Блокируем интерфейс + try: + response_text = generate_lesson_plan_interface(*args) + file_path = generate_docx(response_text) + return ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value=response_text), + gr.update(visible=True, value=file_path) + ) + + except Exception as e: + return ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value=f"❌ Ошибка: {str(e)}"), + gr.update(visible=False) + ) + + + # Привязываем обработчики + btn.click( + fn=on_submit_with_spinner, + inputs=all_inputs, + outputs=[*all_inputs, output, download_btn]) # <- скобку если не стримминг +# ).then( +# lambda: None, +# inputs=[], +# outputs=[] +# ) + +if __name__ == "__main__": + demo.launch(share=True) \ No newline at end of file diff --git a/app/main_gradio_test.py b/app/main_gradio_test.py new file mode 100644 index 0000000000000000000000000000000000000000..2044c93bd90097aca20965a092abea6fa88484f0 --- /dev/null +++ b/app/main_gradio_test.py @@ -0,0 +1,379 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI +import gradio as gr +import time +from datetime import datetime +import calendar +from docx import Document +import tempfile + +import random +from app.quotes import quotes +from app.drawings import drawings +import logging + +# Настройка логирования +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[logging.StreamHandler()] +) # Вывод в консоль + +# Загрузка переменных окружения +load_dotenv() +api_key = os.getenv("API_KEY_openai") +client = OpenAI(api_key=api_key) + +# API_KEY = os.getenv("API_KEY") +# BASE_URL = os.getenv("BASE_URL") +# client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + + +# Функция генерации плана занятия +def generate_lesson_plan_interface( + нарушение, возраст_ребенка, особые_условия, + формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия, + инвентарь, наличие_ДЗ, разрешен_web_search, текущий_месяц=None +): + # Логика работы с источниками - если есть, передаем + #источники = [web_sources] if разрешен_web_search and web_sources else [] + + + # Логика определения месяца + if not текущий_месяц: + текущий_месяц = calendar.month_name[datetime.now().month] + + # все параметры + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "тема": тема, + "особые_условия": особые_условия, + "инвентарь": инвентарь, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search, + #"источники": источники, + "текущий_месяц": текущий_месяц + } + + if формат_занятия == "Индивидуальное": + количество_детей = 1 # Изменяем значение переменной + params["количество_детей"] = 1 # И обновляем словарь параметров + + # Формируем основной промпт + base_prompt = f""" + Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями. + На основе параметров занятия составь план занятия, который должен включать: + 1. **Тема занятия, цель и задачи** (не более 3-х) + 2. **Необходимый инвентарь** + 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и примерами""" + + # Добавляем секцию для веб-поиска только если разрешено + web_search_section = "" + if разрешен_web_search: + web_search_section = """ + **ВНИМАНИЕ:** Используй актуальные данные из интернета: + - Найди примеры релевантных упражнений и интегрируй их в этапы + - При необходимости адаптируй упражнения по параметрам ребенка и занятия + - Для каждого заимствованного упражнения укажи источник (ссылку)""" + + # Продолжение базового промпта + continuation = f""" + 4. **Домашнее задание** (только если параметр "наличие домашнего задания" = True) + 5. **Рекомендации по особым условиям** (если указаны) + + Параметры занятия: + - **Основное нарушение:** {нарушение} + - **Возраст:** {возраст_ребенка} + - **Цель занятия:** {цель_занятия} + - **Тема:** {тема or "не указано - определи самостоятельно"} + - **Формат:** {формат_занятия} ({количество_детей} детей) + - **Инвентарь:** {инвентарь or "не указан - предложи варианты"} + - **Особые условия:** {особые_условия or "отсутствуют"} + - **Длительность:** {длительность_занятия} минут + - **Месяц года:** {текущий_месяц} + + Стиль оформления: + - Четкая структура с нумерованными пунктами + - Минимум общих фраз, максимум конкретики + - Возрастосообразные примеры и материалы""" + + # Собираем полный промпт + full_prompt = base_prompt + web_search_section + continuation + + tools = [] + tool_choice = None + + if params['разрешен_web_search']: + tools.append({ + "type": "web_search_preview", + "search_context_size": "medium", + "user_location": {"type": "approximate", "country": "RU"} + }) + tool_choice = {"type": "web_search_preview"} + + response = client.responses.create( + model="gpt-4o-mini", + input=full_prompt, + temperature=0.5, + tools=tools if tools else None, + tool_choice=tool_choice, + max_output_tokens=2000, + stream=True + ) + + +####### БЕЗ СТРИМИНГА +# return response.output_text # Основной вывод без изменений + +# АННОТАЦИИ В ЛОГ +# if params['разрешен_web_search']: # Только логируем аннотации при веб-поиске +# try: +# content_block = response.output[1].content[0] +# # logging.info(f"=== ПОЛНЫЙ КОНТЕНТ БЛОКА ===") +# # logging.info(f"Тип: {content_block.type}") +# # logging.info(f"Текст: {content_block.text[:200]}...") # Первые 200 символов текста +# logging.info(f"Аннотации: {content_block.annotations}") +# # logging.info(f"Сырые данные: {vars(content_block)}") # Вся техническая информация +# except (IndexError, AttributeError) as e: +# logging.warning(f"Не удалось получить аннотации: {str(e)}") + +####### СТРИМИНГ + try: + for event in response: + if event.type == 'response.output_text.delta': + yield event.delta + elif event.type == 'response.completed': + break + except Exception as e: + yield f"Ошибка: {str(e)}" + + +############# COMPLETIONS (РАБОТАЕТ БЕЗ TOOLS) +# response = client.chat.completions.create( +# model="gpt-4o-mini", +# messages=[ +# {"role": "system", "content": "Ты — эксперт в области логопедии."}, +# {"role": "user", "content": prompt} +# ], +# max_tokens=2000, +# stream=True # Включаем потоковый режим +# ) + +# for chunk in response: +# if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content: +# yield chunk.choices[0].delta.content + + +# Случайный рисунок в блокноте +drawing = random.choice(drawings) +# Текст с подсказкой и рисунком в блокноте +hint_text = f"""Здесь появится план занятия — заполните вводные и нажмите кнопку **Создать конспект** + +
+{drawing}
+
+""" +### css привязывать именно так + +#theme='earneleh/paris' + + + +theme = gr.themes.Base( + secondary_hue="rose", + neutral_hue="stone", +).set( + body_background_fill='*primary_50', + body_text_color='*primary_900', + body_text_size='*text_lg', + body_text_color_subdued='*primary_700', + body_text_weight='600', +) + +css_path = os.path.join(os.path.dirname(__file__), "styles.css") +# +# Интерфейс Gradio +with gr.Blocks(theme=theme, css_paths=css_path) as demo: + gr.Markdown("# Логопедический конспект", elem_classes=["main-title"]) + quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"]) + + with gr.Row(): + # Левый блок (все поля) + with gr.Column(elem_classes=["left-col"], scale=1): + # Блок 1: Ребёнок (заголовок + поля) + with gr.Column(variant="panel"): # <<< Обертка для первого блока + gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"]) + нарушение = gr.Textbox(label="Основное нарушение*", placeholder="Пример: Дислалия (свистящие), ОНР II уровня") + возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет") + особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС") + + + + # Блок 2: Занятие (заголовок + поля) + with gr.Column(variant="panel"): # <<< Обертка для второго блока + gr.Markdown("### 📄 Занятие", elem_classes=["block-title"]) + формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное") + количество_детей = gr.Slider(label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False) + def toggle_group_slider(selected_format): + if selected_format == "Индивидуальное": + return gr.update(visible=False, value=1) # Скрыть слайдер и установить значение 1 + else: + return gr.update(visible=True, value=2) # Показать слайдер и установить значение 2 + # Привязываем изменение формата к функции + формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей) + + цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах") + тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт") + длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5) + инвентарь = gr.Textbox(label="Инвентарь (через запятую)", placeholder="Пример: Зеркало, Карточки, Куклы") + дз = gr.Checkbox(label="Домашнее задание") + # Выделенный блок для профессиональных ресурсов + gr.Markdown("---") # Разделительная линия + # gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"]) + + with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок + web = gr.Checkbox( + label="📚 Поиск упражнений в логопедических базах", + info="Упражнения подбираются из проверенных источников со ссылками", + interactive=True + ) + + #web_sources = gr.Textbox( + # label="Уточнить источники (необязательно, через запятую):", + # placeholder="Например: Logopedy.ru, Maam.ru, ...", + # visible=False, + #) + + # Логика отображения поля источников + #web.change( + # fn=lambda x: gr.update(visible=x), + # inputs=web, + # outputs=web_sources + #) + + # Кнопка создания конспекта + btn = gr.Button("Создать конспект", variant="primary") + + # Правая колонка — результат (output) + with gr.Column(elem_classes=["right-col"], scale=2): + # Общий блок-панель для правой колонки + with gr.Column(variant="panel"): # <<< Главная панель + gr.Markdown("### Конспект", elem_classes=["block-title"]) # Заголовок ВНУТРИ панели + # Блок с выводом конспекта + output = gr.Markdown( + hint_text, + elem_id="plan-output" + ) + # Кнопка скачивания (оставляем внутри панели) + download_btn = gr.DownloadButton( + label="⬇️ Скачать .docx", + visible=False + ) + + # Ввод параметров + all_inputs = [ + нарушение, возраст, особые_условия, + формат, количество_детей, цель, тема, длительность, + инвентарь, дз, web #web_sources + ] + + + # Функция для генерации docx файла + def generate_docx(text: str): + doc = Document() + for line in text.split("\n"): + doc.add_paragraph(line) + tmp_dir = tempfile.gettempdir() + file_path = os.path.join(tmp_dir, "Конспект_занятия.docx") + doc.save(file_path) + return file_path + +################### СТРИМИНГ + def on_submit_with_spinner(*args): + # Проверка обязательных полей (остается без изменений) + if not args[0] or not args[1] or not args[5]: + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), + gr.update(visible=False) + ) + return + + # Блокируем интерфейс + yield ( + *[gr.update(interactive=False) for _ in all_inputs], + gr.update(value="⏳ Конспект создается..."), + gr.update(visible=False) + ) + + full_response = [] + try: + for chunk in generate_lesson_plan_interface(*args): + full_response.append(chunk) + yield ( + *[gr.update(interactive=False) for _ in all_inputs], + gr.update(value="".join(full_response)), + gr.update(visible=False) + ) + + # После завершения стрима + file_path = generate_docx("".join(full_response)) + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value="".join(full_response)), + gr.update(visible=True, value=file_path) + ) + + except Exception as e: + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value=f"❌ Ошибка: {str(e)}"), + gr.update(visible=False) + ) + +################## БЕЗ СТРИМИНГА +# def on_submit_with_spinner(*args): +# if not args[0] or not args[1] or not args[5]: +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), +# gr.update(visible=False) +# ) + + # Блокируем интерфейс +# try: +# response_text = generate_lesson_plan_interface(*args) +# file_path = generate_docx(response_text) +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value=response_text), +# gr.update(visible=True, value=file_path) +# ) + +# except Exception as e: +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value=f"❌ Ошибка: {str(e)}"), +# gr.update(visible=False) +# ) + + + # Привязываем обработчики + btn.click( + fn=on_submit_with_spinner, + inputs=all_inputs, + outputs=[*all_inputs, output, download_btn] # <- скобку если не стримминг + ).then( + lambda: None, + inputs=[], + outputs=[] + ) + +if __name__ == "__main__": + demo.launch(share=True) \ No newline at end of file diff --git a/app/main_gradio_test_FS.py b/app/main_gradio_test_FS.py new file mode 100644 index 0000000000000000000000000000000000000000..308eb8e4bcc0929dad3567ce4cb8ab11c2b2461a --- /dev/null +++ b/app/main_gradio_test_FS.py @@ -0,0 +1,386 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI +import gradio as gr +import time +from datetime import datetime +import calendar +from docx import Document +import tempfile + +import random +from app.quotes import quotes +from app.drawings import drawings +import logging + +# Настройка логирования +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[logging.StreamHandler()] # Вывод в консоль +) + +# Загрузка переменных окружения +load_dotenv() +api_key = os.getenv("API_KEY_openai") +client = OpenAI(api_key=api_key) +VS_ID = os.getenv("VECTOR_STORE_ID") +# API_KEY = os.getenv("API_KEY") +# BASE_URL = os.getenv("BASE_URL") +# client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + + +# Функция генерации плана занятия +def generate_lesson_plan_interface( + нарушение, возраст_ребенка, особые_условия, + формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия, + инвентарь, наличие_ДЗ, разрешен_file_search, текущий_месяц=None +): + # Логика работы с источниками - если есть, передаем + #источники = [web_sources] if разрешен_web_search and web_sources else [] + + + # Логика определения месяца + if not текущий_месяц: + текущий_месяц = calendar.month_name[datetime.now().month] + + # все параметры + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "тема": тема, + "особые_условия": особые_условия, + "инвентарь": инвентарь, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_file_search": разрешен_file_search, + #"источники": источники, + "текущий_месяц": текущий_месяц + } + + if формат_занятия == "Индивидуальное": + количество_детей = 1 # Изменяем значение переменной + params["количество_детей"] = 1 # И обновляем словарь параметров + + # Формируем основной промпт + base_prompt = f""" + Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями. + На основе параметров занятия составь план занятия, который должен включать: + 1. **Тема занятия, цель и задачи** (не более 3-х) + 2. **Необходимый инвентарь** + 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и примерами""" + + + file_search_section = "" + if разрешен_file_search: + file_search_section = """ + **Используй данные из базы: + - Найди примеры релевантных упражнений и интегрируй их в этапы + - При необходимости адаптируй упражнения по параметрам ребенка и занятия + - Укажи ссылки на источники + """ + + # Продолжение базового промпта + continuation = f""" + 4. **Домашнее задание** (только если параметр "наличие домашнего задания" = True) + 5. **Рекомендации по особым условиям** (только если указаны) + + Параметры занятия: + - **Основное нарушение:** {нарушение} + - **Возраст:** {возраст_ребенка} + - **Цель занятия:** {цель_занятия} + - **Тема:** {тема or "не указано - определи самостоятельно"} + - **Формат:** {формат_занятия} ({количество_детей} детей) + - **Инвентарь:** {инвентарь or "не указан - предложи варианты"} + - **Особые условия:** {особые_условия or "отсутствуют"} + - **Длительность:** {длительность_занятия} минут + - **Месяц года:** {текущий_месяц} + + Стиль оформления: + - Четкая структура с нумерованными пунктами + - Минимум общих фраз, максимум конкретики + - Возрастосообразные примеры и материалы""" + + # Собираем полный промпт + full_prompt = base_prompt + file_search_section + continuation + + tools = [] + tool_choice = None + #FILE SEARCH + if params['разрешен_file_search']: + tools.append({ + "type": "file_search", + "vector_store_ids": [VS_ID], + "max_num_results": 5 + }) + #WEB SEARCH +# if params['разрешен_web_search']: +# tools.append({ +# "type": "web_search_preview", +# "search_context_size": "medium", +# "user_location": {"type": "approximate", "country": "RU"} +# }) +# tool_choice = {"type": "web_search_preview"} + + response = client.responses.create( + model=os.getenv("MODEL"), + input=full_prompt, + temperature=0.7, + tools=tools if tools else None, + tool_choice=tool_choice, + max_output_tokens=2000, + stream=True + ) + + +####### БЕЗ СТРИМИНГА +# return response.output_text # Основной вывод без изменений + +# АННОТАЦИИ В ЛОГ +# if params['разрешен_web_search']: # Только логируем аннотации при веб-поиске +# try: +# content_block = response.output[1].content[0] +# # logging.info(f"=== ПОЛНЫЙ КОНТЕНТ БЛОКА ===") +# # logging.info(f"Тип: {content_block.type}") +# # logging.info(f"Текст: {content_block.text[:200]}...") # Первые 200 символов текста +# logging.info(f"Аннотации: {content_block.annotations}") +# # logging.info(f"Сырые данные: {vars(content_block)}") # Вся техническая информация +# except (IndexError, AttributeError) as e: +# logging.warning(f"Не удалось получить аннотации: {str(e)}") + +####### СТРИМИНГ + try: + for event in response: + if event.type == 'response.output_text.delta': + yield event.delta + elif event.type == 'response.completed': + break + except Exception as e: + yield f"Ошибка: {str(e)}" + + +############# COMPLETIONS (РАБОТАЕТ БЕЗ TOOLS) +# response = client.chat.completions.create( +# model="gpt-4o-mini", +# messages=[ +# {"role": "system", "content": "Ты — эксперт в области логопедии."}, +# {"role": "user", "content": prompt} +# ], +# max_tokens=2000, +# stream=True # Включаем потоковый режим +# ) + +# for chunk in response: +# if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content: +# yield chunk.choices[0].delta.content + + +# Случайный рисунок в блокноте +drawing = random.choice(drawings) +# Текст с подсказкой и рисунком в блокноте +hint_text = f"""Здесь появится план занятия — заполните вводные и нажмите кнопку **Создать конспект** + +
+{drawing}
+
+""" + +########## ИНТЕРФЕЙС +#ТЕМА И СТИЛИ + +### css привязывать именно так +#theme='earneleh/paris' +theme = gr.themes.Base( + secondary_hue="rose", + neutral_hue="stone", +).set( + body_background_fill='*primary_50', + body_text_color='*primary_900', + body_text_size='*text_lg', + body_text_color_subdued='*primary_700', + body_text_weight='600', +) +css_path = os.path.join(os.path.dirname(__file__), "styles.css") + +# ИНТЕРФЕЙС +with gr.Blocks(theme=theme, css_paths=css_path) as demo: + gr.Markdown("# Логопедический конспект", elem_classes=["main-title"]) + quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"]) + + with gr.Row(): + # Левый блок (все поля) + with gr.Column(elem_classes=["left-col"], scale=1): + # Блок 1: Ребёнок (заголовок + поля) + with gr.Column(variant="panel"): # <<< Обертка для первого блока + gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"]) + нарушение = gr.Textbox(label="Основное нарушение*", placeholder="Пример: Дислалия (свистящие), ОНР II уровня") + возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет") + особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС") + + + + # Блок 2: Занятие (заголовок + поля) + with gr.Column(variant="panel"): # <<< Обертка для второго блока + gr.Markdown("### 📄 Занятие", elem_classes=["block-title"]) + формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное") + количество_детей = gr.Slider(label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False) + def toggle_group_slider(selected_format): + if selected_format == "Индивидуальное": + return gr.update(visible=False, value=1) # Скрыть слайдер и установить значение 1 + else: + return gr.update(visible=True, value=2) # Показать слайдер и установить значение 2 + # Привязываем изменение формата к функции + формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей) + + цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах") + тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт") + длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5) + инвентарь = gr.Textbox(label="Инвентарь (через запятую)", placeholder="Пример: Зеркало, Карточки, Куклы") + дз = gr.Checkbox(label="Домашнее задание") + # Выделенный блок для профессиональных ресурсов + gr.Markdown("---") # Разделительная линия + # gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"]) + + with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок + file_search = gr.Checkbox( + label="📚 Поиск упражнений в логопедических базах", + info="Упражнения подбираются из проверенных источников со ссылками (FS)", + interactive=True + ) + + #web_sources = gr.Textbox( + # label="Уточнить источники (необязательно, через запятую):", + # placeholder="Например: Logopedy.ru, Maam.ru, ...", + # visible=False, + #) + + # Логика отображения поля источников + #web.change( + # fn=lambda x: gr.update(visible=x), + # inputs=web, + # outputs=web_sources + #) + + # Кнопка создания конспекта + btn = gr.Button("Создать конспект", variant="primary") + + # Правая колонка — результат (output) + with gr.Column(elem_classes=["right-col"], scale=2): + # Общий блок-панель для правой колонки + with gr.Column(variant="panel"): # <<< Главная панель + gr.Markdown("### Конспект", elem_classes=["block-title"]) # Заголовок ВНУТРИ панели + # Блок с выводом конспекта + output = gr.Markdown( + hint_text, + elem_id="plan-output" + ) + # Кнопка скачивания (оставляем внутри панели) + download_btn = gr.DownloadButton( + label="⬇️ Скачать .docx", + visible=False + ) + + # Ввод параметров + all_inputs = [ + нарушение, возраст, особые_условия, + формат, количество_детей, цель, тема, длительность, + инвентарь, дз, file_search #web_sources + ] + + + # Функция для генерации docx файла + def generate_docx(text: str): + doc = Document() + for line in text.split("\n"): + doc.add_paragraph(line) + tmp_dir = tempfile.gettempdir() + file_path = os.path.join(tmp_dir, "Конспект_занятия.docx") + doc.save(file_path) + return file_path + +################### СТРИМИНГ + def on_submit_with_spinner(*args): + # Проверка обязательных полей (остается без изменений) + if not args[0] or not args[1] or not args[5]: + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), + gr.update(visible=False) + ) + return + + # Блокируем интерфейс + yield ( + *[gr.update(interactive=False) for _ in all_inputs], + gr.update(value="⏳ Конспект создается..."), + gr.update(visible=False) + ) + + full_response = [] + try: + for chunk in generate_lesson_plan_interface(*args): + full_response.append(chunk) + yield ( + *[gr.update(interactive=False) for _ in all_inputs], + gr.update(value="".join(full_response)), + gr.update(visible=False) + ) + + # После завершения стрима + file_path = generate_docx("".join(full_response)) + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value="".join(full_response)), + gr.update(visible=True, value=file_path) + ) + + except Exception as e: + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value=f"❌ Ошибка: {str(e)}"), + gr.update(visible=False) + ) + +################## БЕЗ СТРИМИНГА +# def on_submit_with_spinner(*args): +# if not args[0] or not args[1] or not args[5]: +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), +# gr.update(visible=False) +# ) + + # Блокируем интерфейс +# try: +# response_text = generate_lesson_plan_interface(*args) +# file_path = generate_docx(response_text) +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value=response_text), +# gr.update(visible=True, value=file_path) +# ) + +# except Exception as e: +# return ( +# *[gr.update(interactive=True) for _ in all_inputs], +# gr.update(value=f"❌ Ошибка: {str(e)}"), +# gr.update(visible=False) +# ) + + + # Привязываем обработчики + btn.click( + fn=on_submit_with_spinner, + inputs=all_inputs, + outputs=[*all_inputs, output, download_btn] # <- скобку если не стримминг + ).then( + lambda: None, + inputs=[], + outputs=[] + ) + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/app/misc/.gradio/certificate.pem b/app/misc/.gradio/certificate.pem new file mode 100644 index 0000000000000000000000000000000000000000..b85c8037f6b60976b2546fdbae88312c5246d9a3 --- /dev/null +++ b/app/misc/.gradio/certificate.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- diff --git a/app/misc/Stable.py b/app/misc/Stable.py new file mode 100644 index 0000000000000000000000000000000000000000..642ae72b689216b938f6a9fe9856988ec544c593 --- /dev/null +++ b/app/misc/Stable.py @@ -0,0 +1,168 @@ +import os +import asyncio +from dotenv import load_dotenv +from openai import OpenAI +import streamlit as st + +# Загрузка переменных окружения +load_dotenv() +api_key = os.getenv("API_KEY_openai") + + +def generate_lesson_plan(params): + client = OpenAI(api_key=api_key) + + prompt = f""" + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры: + - **Основное нарушение:** {params['нарушение']} + - **Возраст ребенка:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тематика:** {params['тематика']} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Инвентарь:** {params['инвентарь']} + - **Особые условия:** {params['особые_условия']} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Разрешен web search:** {params['разрешен_web_search']} + + На основе этих параметров составь конспект занятия, который должен включать: + 1. **Тема занятия, цель и задачи** + 2. **Необходимые материалы** + 3. **Ход занятия**. Разбей занятие на логически последовательные этапы. + В каждый этап встрой конкретные упражнения с достаточным количеством примеров. + ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников. + Обязательно интегрируй ссылки на найденные источники в текст ответа. + 5. **Домашнее задание** (если параметр "наличие домашнего задания: True") + 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия") + """ + + tools = [] + tool_choice = None + + if params['разрешен_web_search']: + tools.append({ + "type": "web_search_preview", + "search_context_size": "medium", + "user_location": { + "type": "approximate", + "country": "RU" + } + }) + tool_choice = {"type": "web_search_preview"} + + response = client.responses.create( + model="gpt-4o-mini", + input=prompt, + tools=tools if tools else None, + tool_choice=tool_choice, + max_output_tokens=2000 + ) + return response.output_text + + +# Интерфейс Streamlit +st.title("Генератор логопедических занятий") + +# Левая часть с настройками +with st.sidebar: + st.header("Настройки занятия") + + # Обязательные поля + нарушение = st.text_input( + "Основное нарушение*", + placeholder="Пример: Дислалия (свистящие), ОНР II уровня" + ) + + возраст_ребенка = st.text_input( + "Возраст ребенка*", + placeholder="Пример: 5 лет, 6-7 лет" + ) + + цель_занятия = st.text_input( + "Цель занятия*", + placeholder="Пример: Автоматизация звука [Р] в слогах" + ) + + # Переключатель формата занятия + формат_занятия = st.radio( + "Формат занятия*", + options=["Индивидуальное", "Групповое"], + index=0, + horizontal=True + ) + + количество_детей = 1 # Значение по умолчанию + + if формат_занятия == "Групповое": + количество_детей = st.number_input( + "Количество детей в группе*", + min_value=2, + max_value=10, + value=3 + ) + + # Дополнительные параметры + тематика = st.text_input( + "Тематика занятия", + placeholder="Пример: Животные, Транспорт" + ) + + особые_условия = st.text_input( + "Особые условия", + placeholder="Пример: гиперактивность, РАС" + ) + + # Инвентарь + st.subheader("Инвентарь") + инвентарь_чекбоксы = { + "Зеркало": st.checkbox("Зеркало"), + "Карточки": st.checkbox("Карточки"), + "Игрушки": st.checkbox("Игрушки") + } + дополнительный_инвентарь = st.text_input("Другое") + + инвентарь = ", ".join([k for k, v in инвентарь_чекбоксы.items() if v]) + if дополнительный_инвентарь: + инвентарь += f", {дополнительный_инвентарь}" + + # Другие настройки + длительность_занятия = st.slider( + "Длительность (минут)", + min_value=15, + max_value=60, + value=30, + step=5 + ) + + наличие_ДЗ = st.checkbox("Домашнее задание") + разрешен_web_search = st.checkbox("Поиск материалов в интернете") + + # Кнопка генерации + сгенерировать = st.button("Создать конспект") + +# Правая часть с результатом +if сгенерировать: + if not нарушение or not возраст_ребенка or not цель_занятия: + st.error("Заполните обязательные поля!") + else: + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "тематика": тематика, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "инвентарь": инвентарь, + "особые_условия": особые_условия, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search + } + + with st.spinner("Создаем конспект..."): + try: + результат = generate_lesson_plan(params) + st.success("Готово!") + st.markdown(результат) + except Exception as e: + st.error(f"Ошибка: {e}") \ No newline at end of file diff --git a/app/misc/main_completions.py b/app/misc/main_completions.py new file mode 100644 index 0000000000000000000000000000000000000000..194a05958f74bc0e6fce63d50c71904b957eee88 --- /dev/null +++ b/app/misc/main_completions.py @@ -0,0 +1,191 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI +import streamlit as st + +# Загрузка переменных окружения +load_dotenv() +#api_key = os.getenv("API_KEY_openai") +API_KEY = os.getenv("API_KEY") +BASE_URL = os.getenv("BASE_URL") + + +def generate_lesson_plan(params): +# client = OpenAI(api_key=api_key) + client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + prompt = f""" + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры: + - **Основное нарушение:** {params['нарушение']} + - **Возраст ребенка:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тематика:** {params['тематика']} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Инвентарь:** {params['инвентарь']} + - **Особые условия:** {params['особые_условия']} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Разрешен web search:** {params['разрешен_web_search']} + + На основе этих параметров составь конспект занятия, который должен включать: + 1. **Тема занятия, цель и задачи** + 2. **Необходимые материалы** + 3. **Ход занятия**. Разбей занятие на логически последовательные этапы. + В каждый этап встрой конкретные упражнения с достаточным количеством примеров. + ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников. + Обязательно интегрируй ссылки на найденные источники в текст ответа. + 5. **Домашнее задание** (если параметр "наличие домашнего задания: True") + 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия") + """ + +# tools = [] +# tool_choice = None + +# if params['разрешен_web_search']: +# tools.append({ +# "type": "web_search_preview", +# "search_context_size": "medium", +# "user_location": { +# "type": "approximate", +# "country": "RU" +# } +# }) +# tool_choice = {"type": "web_search_preview"} + +# response = client.responses.create( +# model="gpt-4o-mini", +# input=prompt, +# tools=tools if tools else None, +# tool_choice=tool_choice, +# max_output_tokens=2000 +# ) +# return response.output_text +# + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": "Ты — эксперт в области логопедии."}, + {"role": "user", "content": prompt} + ], + max_tokens=2000 + ) + return response.choices[0].message.content + + +# Интерфейс Streamlit +st.title("Генератор логопедических занятий") + +# Инициализация флагов в session_state +if "loading" not in st.session_state: + st.session_state["loading"] = False +if "lesson_plan" not in st.session_state: + st.session_state["lesson_plan"] = "" + +# Используем обычный блок в сайдбаре для динамических виджетов +with st.sidebar: + st.header("Настройки занятия") + + нарушение = st.text_input( + "Основное нарушение*", + placeholder="Пример: Дислалия (свистящие), ОНР II уровня", + disabled=st.session_state["loading"] + ) + возраст_ребенка = st.text_input( + "Возраст ребенка*", + placeholder="Пример: 5 лет, 6-7 лет", + disabled=st.session_state["loading"] + ) + цель_занятия = st.text_input( + "Цель занятия*", + placeholder="Пример: Автоматизация звука [Р] в слогах", + disabled=st.session_state["loading"] + ) + + формат_занятия = st.radio( + "Формат занятия*", + options=["Индивидуальное", "Групповое"], + index=0, + horizontal=True, + disabled=st.session_state["loading"] + ) + + # Это поле появляется динамически, когда выбрано "Групповое" + if формат_занятия == "Групповое": + количество_детей = st.number_input( + "Количество детей в группе*", + min_value=2, + max_value=10, + value=3, + disabled=st.session_state["loading"] + ) + else: + количество_детей = 1 + + тематика = st.text_input( + "Тематика занятия", + placeholder="Пример: Животные, Транспорт", + disabled=st.session_state["loading"] + ) + особые_условия = st.text_input( + "Особые условия", + placeholder="Пример: гиперактивность, РАС", + disabled=st.session_state["loading"] + ) + + st.subheader("Инвентарь") + инвентарь_чекбоксы = { + "Зеркало": st.checkbox("Зеркало", disabled=st.session_state["loading"]), + "Карточки": st.checkbox("Карточки", disabled=st.session_state["loading"]), + "Игрушки": st.checkbox("Игрушки", disabled=st.session_state["loading"]) + } + дополнительный_инвентарь = st.text_input("Другое", disabled=st.session_state["loading"]) + инвентарь = ", ".join([k for k, v in инвентарь_чекбоксы.items() if v]) + if дополнительный_инвентарь: + инвентарь += f", {дополнительный_инвентарь}" + инвентарь = инвентарь.strip(", ") + + длительность_занятия = st.slider( + "Длительность (минут)", + min_value=15, + max_value=60, + value=30, + step=5, + disabled=st.session_state["loading"] + ) + наличие_ДЗ = st.checkbox("Домашнее задание", disabled=st.session_state["loading"]) + разрешен_web_search = st.checkbox("Поиск материалов в интернете", disabled=st.session_state["loading"]) + + # Кнопка отправки, блокируется если идет генерация + submit = st.button("Создать конспект", disabled=st.session_state["loading"], key="submit_btn") + +# Если кнопка нажата, запускаем генерацию +if submit: + if not нарушение or not возраст_ребенка or not цель_занятия: + st.sidebar.error("Заполните обязательные поля!") + else: + st.session_state["loading"] = True # Блокируем виджеты + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "тематика": тематика, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "инвентарь": инвентарь, + "особые_условия": особые_условия, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search + } + with st.spinner("Создаем конспект..."): + try: + результат = generate_lesson_plan(params) + st.session_state["lesson_plan"] = результат + st.success("Готово!") + except Exception as e: + st.error(f"Ошибка: {e}") + finally: + st.session_state["loading"] = False + +# Отображаем ранее сгенерированный конспект, если он есть и генерация не идёт +if st.session_state.get("lesson_plan", "") and not st.session_state["loading"]: + st.markdown(st.session_state["lesson_plan"]) \ No newline at end of file diff --git a/app/misc/main_greadio_noStream.py b/app/misc/main_greadio_noStream.py new file mode 100644 index 0000000000000000000000000000000000000000..016194c5a9418acd231b9bb62732a7d079cff8b5 --- /dev/null +++ b/app/misc/main_greadio_noStream.py @@ -0,0 +1,234 @@ +import os +from dotenv import load_dotenv +from openai import OpenAI +import gradio as gr +import time +from datetime import datetime +import calendar +from docx import Document +import tempfile +import random # для случайного выбора цитаты +from app.quotes import quotes +# Загрузка переменных окружения +load_dotenv() +api_key = os.getenv("API_KEY_openai") +client = OpenAI(api_key=api_key) + +#API_KEY = os.getenv("API_KEY") +#BASE_URL = os.getenv("BASE_URL") +#client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + + +# Функция генерации плана занятия +def generate_lesson_plan_interface( + нарушение, возраст_ребенка, особые_условия, + формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия, + инвентарь, наличие_ДЗ, разрешен_web_search, web_sources, текущий_месяц=None +): + + # Логика работы с источниками - если есть, передаем + источники = [web_sources] if разрешен_web_search else [] + + # Логика определения месяца + if not текущий_месяц: + текущий_месяц = datetime.now().month + + # все параметры + params = { + "нарушение": нарушение, + "возраст_ребенка": возраст_ребенка, + "цель_занятия": цель_занятия, + "формат_занятия": формат_занятия, + "количество_детей": количество_детей, + "тема": тема, + "особые_условия": особые_условия, + "инвентарь": инвентарь, + "длительность_занятия": длительность_занятия, + "наличие_ДЗ": наличие_ДЗ, + "разрешен_web_search": разрешен_web_search, + "источники": источники, + "текущий_месяц": текущий_месяц + } + + prompt = f""" + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры: + - **Основное нарушение:** {params['нарушение']} + - **Возраст ребенка:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Инвентарь:** {params['инвентарь'] or "не указано"} + - **Особые условия:** {params['особые_условия'] or "не указано"} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Разрешен web search:** {params['разрешен_web_search']} + - **Текущий месяц:** {params['текущий_месяц']} + + На основе этих параметров составь конспект занятия, который должен включать: + 1. **Тема занятия, цель и задачи** + 2. **Необходимые материалы** + 3. **Ход занятия**. Разбей занятие на логически последовательные этапы. + В каждый этап встрой конкретные упражнения с достаточным количеством примеров. + ВАЖНО: если разрешен web search: True, то выстрой занятие на базе упражнений из релевантных источников. + Обязательно приведи ссылки на источники источники в текст ответа). + 5. **Домашнее задание** (только если параметр "наличие домашнего задания: True") + 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия") + """ + + tools = [] + tool_choice = None + + if params['разрешен_web_search']: + tools.append({ + "type": "web_search_preview", + "search_context_size": "medium", + "user_location": { + "type": "approximate", + "country": "RU" + } + }) + tool_choice = {"type": "web_search_preview"} + + response = client.responses.create( + model="gpt-4o-mini", + input=prompt, + tools=tools if tools else None, + tool_choice=tool_choice, + max_output_tokens=2000 + ) + return response.output_text + +# response = client.chat.completions.create( +# model="gpt-4o-mini", +# messages=[ +# {"role": "system", "content": "Ты — эксперт в области логопедии."}, +# {"role": "user", "content": prompt} +# ], +# max_tokens=2000 +# ) +# return response.choices[0].message.content + +# Интерфейс Gradio +with gr.Blocks() as demo: + gr.Markdown("## 🧠 Генератор логопедических занятий") + + with gr.Row(): + # Первый блок настройки (Ребенок) + with gr.Column(scale=1): + quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"]) + + gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"]) + нарушение = gr.Textbox(label="Основное нарушение*", + placeholder="Пример: Дислалия (свистящие), ОНР II уровня") + возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет") + особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС") + + gr.Markdown("### 📄 Занятие", elem_classes=["block-title"]) + формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное") + количество_детей = gr.Slider( + label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False + ) + + def toggle_group_slider(selected_format): + return gr.update(visible=(selected_format == "Групповое")) + + + формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей) + + цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах") + тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт") + длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5) + инвентарь = gr.Textbox(label="Инвентарь (введите через запятую)", + placeholder="Пример: Зеркало, Карточки, Игрушки") + + дз = gr.Checkbox(label="Домашнее задание") + # Выделенный блок для профессиональных ресурсов + gr.Markdown("---") # Горизонтальная разделительная линия + #gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"]) + + with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок + web = gr.Checkbox( + label="📚 Поиск упражнений в логопедических базах", + info="Упражнения подбираются из проверенных источников со ссылками", + interactive=True + ) + + web_sources = gr.Textbox( + label="Уточнить источники (необязательно):", + placeholder="Например: Logopedy.ru, Maam.ru, ...", + visible=False, + ) + + # Логика отображения поля источников + web.change( + fn=lambda x: gr.update(visible=x), + inputs=web, + outputs=web_sources + ) + + # Кнопка создания конспекта + btn = gr.Button("Создать конспект", variant="primary") + + # Правая колонка — результат (output) + with gr.Column(scale=2): # Правая колонка — результат + download_btn = gr.DownloadButton( + label="⬇️ Скачать .docx", + visible=False + ) + output = gr.Markdown("") # Поле для вывода конспекта + + # Ввод параметров + all_inputs = [ + нарушение, возраст, особые_условия, + формат, количество_детей, цель, тема, длительность, + инвентарь, дз, web, web_sources + ] + + # Функция для генерации docx файла + def generate_docx(text: str): + doc = Document() + for line in text.split("\n"): + doc.add_paragraph(line) + tmp_dir = tempfile.gettempdir() + file_path = os.path.join(tmp_dir, "Конспект_занятия.docx") + doc.save(file_path) + return file_path + + + def on_submit_with_spinner(*args): + # Проверка обязательных полей + if not args[0] or not args[1] or not args[5]: + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"), + gr.update(visible=False) + ) + return + + # Показываем спиннер + yield ( + *[gr.update(interactive=False) for _ in all_inputs], + gr.update(value="⏳ Конспект создается..."), + gr.update(visible=False) + ) + + # Генерация конспекта + result = generate_lesson_plan_interface(*args) + file_path = generate_docx(result) + random_quote = random.choice(quotes) + yield ( + *[gr.update(interactive=True) for _ in all_inputs], + gr.update(value=result), + gr.update(visible=True, value=file_path), # Передаём путь к файлу прямо в DownloadButton + ) + + # Привязываем обработчики + btn.click( + fn=on_submit_with_spinner, + inputs=all_inputs, + outputs=[* all_inputs, output, download_btn] + ) + + +if __name__ == "__main__": + demo.launch(share=True) \ No newline at end of file diff --git a/app/misc/misc b/app/misc/misc new file mode 100644 index 0000000000000000000000000000000000000000..1fc7e6c4d3d5e555d42b36a42f2f4d495f7ae522 --- /dev/null +++ b/app/misc/misc @@ -0,0 +1,24 @@ + +def delete_file_by_name(filename: str): + """ + Удаляет файл из OpenAI по имени (если он есть в existing_files.json) + """ + existing = load_existing_files() + if filename not in existing: + print(f"Файл '{filename}' не найден в existing_files.json") + return + + file_id = existing[filename]['file_id'] + vector_store_id = VS_ID + try: + client.vector_stores.files.delete( + vector_store_id=vector_store_id, + file_id=file_id + ) + print(f"Файл '{filename}' (ID: {file_id}) успешно удалён") + + # Удаляем из локального кэша + del existing[filename] + save_existing_files(existing) + except Exception as e: + print(f"Ошибка при удалении файла '{filename}': {str(e)}") diff --git a/app/misc/test_API.py b/app/misc/test_API.py new file mode 100644 index 0000000000000000000000000000000000000000..e9afdfeef368dc2e02cd66769c8080a207dd967d --- /dev/null +++ b/app/misc/test_API.py @@ -0,0 +1,12 @@ +import requests + +API_KEY = "" + +headers = { + "Authorization": f"Bearer {API_KEY}" +} + +response = requests.get("https://api.openai.com/v1/models", headers=headers) + +print(response.status_code) # Должно быть 200, если ключ работает +print(response.json()) # Посмотрим, что вернет API diff --git a/app/misc/test_completions.py b/app/misc/test_completions.py new file mode 100644 index 0000000000000000000000000000000000000000..5f7305c8584c026849bcd18c4c28a397433dfafd --- /dev/null +++ b/app/misc/test_completions.py @@ -0,0 +1,22 @@ +import os +from openai import OpenAI +from dotenv import load_dotenv + +# 5. Загрузка переменных окружения +load_dotenv() +API_KEY = os.getenv("API_KEY") +BASE_URL = os.getenv("BASE_URL") + +# 6. Инициализация клиента OpenAI +client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + +# Отправка запроса к chat.completions +response = client.chat.completions.create( + model="gpt-4o-mini", # Можно заменить на другую доступную модель + messages=[ + {"role": "user", "content": "Найди свежие новости про искусственный интеллект и дай мне ссылки."} + ] +) + +# Вывод ответа в консоль +print(response.choices[0].message.content) diff --git a/app/misc/test_file_search.py b/app/misc/test_file_search.py new file mode 100644 index 0000000000000000000000000000000000000000..06f6c066a164a1c8c7a50b1ecc4eae1064d11826 --- /dev/null +++ b/app/misc/test_file_search.py @@ -0,0 +1,25 @@ +from openai import OpenAI +import os +import asyncio +from dotenv import load_dotenv + +load_dotenv() + +API_KEY = os.getenv("API_KEY_openai") +VS_ID = os.getenv("VECTOR_STORE_ID") +client = OpenAI(api_key=API_KEY) + +response = client.responses.create( + model="gpt-4o-mini", + input="Приведи из базы упражнения дляя автматизации звука Р. Указывай точные названия источников в [квадратных скобках]." + "Строго: если релевантные сведения в данных по запросу отсутствуют, сообщи об этом. Ответ давай в пределах {max_output_tokens} токенов", + tools=[{ + "type": "file_search", + "vector_store_ids": [VS_ID] + }], + max_output_tokens=1000, + include=["file_search_call.results"] +) + +print (response.output_text) + diff --git a/app/misc/test_proxy.py b/app/misc/test_proxy.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/app/misc/test_responses.py b/app/misc/test_responses.py new file mode 100644 index 0000000000000000000000000000000000000000..9dad9ebec07ae7e9510d06b78f2e01ff00e054b9 --- /dev/null +++ b/app/misc/test_responses.py @@ -0,0 +1,21 @@ +from openai import OpenAI +import os +from dotenv import load_dotenv + +API_KEY=os.getenv("API_KEY_openai") +load_dotenv() +client = OpenAI(api_key=API_KEY) +VS_ID = os.getenv("VECTOR_STORE_ID") +response = client.responses.create( + model="gpt-4o-mini", + input="Придумай упражнения для автоматизации звука Р", + tools=[{ + "type": "file_search", + "vector_store_ids": [VS_ID] + }] +) + +print(response.output_text) + + + diff --git a/app/misc/test_web_search.py b/app/misc/test_web_search.py new file mode 100644 index 0000000000000000000000000000000000000000..e95c837cf366f07418c545468682873de1eedf2c --- /dev/null +++ b/app/misc/test_web_search.py @@ -0,0 +1,35 @@ +import os # Импортируем модуль для работы с переменными окружения +import asyncio # Импортируем модуль для поддержки асинхронного программирования +from dotenv import load_dotenv # Импортируем функцию для загрузки переменных окружения из файла .env +from openai import OpenAI +# Загружаем переменные окружения из файла .env +load_dotenv() +api_key=os.getenv("API_KEY_openai") + +client = OpenAI(api_key=api_key) + +# Тестовый запрос +prompt = "Какие современные методы используются для развития фонематического слуха у детей? Приведи примеры и ссылки на источники." + +tools = [ + { + "type": "web_search_preview", + "search_context_size": "low", + "user_location": { + "type": "approximate", + "country": "RU" + } + } +] + +# Отправляем запрос к API +response = client.responses.create( + model="gpt-4o-mini", + input=prompt, + tools=tools, + max_output_tokens=1000 +) + +# Выводим результат в консоль +print("Ответ модели:") +print(response.output_text) diff --git a/app/prompt.txt b/app/prompt.txt new file mode 100644 index 0000000000000000000000000000000000000000..a2235989004343ad3f0834599d3bd6f792f26a7b --- /dev/null +++ b/app/prompt.txt @@ -0,0 +1,205 @@ +Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры: +- **Нарушение:** {нарушение} +- **Цель занятия:** {цель_занятия} +- **Месяц:** {месяц} +- **Инвентарь:** {инвентарь} +- **Количество детей:** {количество_детей} +- **Длительность занятия:** {длительность_занятия} +- **Наличие домашнего задания:** {наличие_ДЗ} +- **Загруженные материалы (если есть):** {загруженные_материалы} +- **Разрешен web search:** {разрешен_web_search} + +На основе этих параметров составь подробный конспект занятия, который должен включать: + +1. **Тема занятия, цель и задачи:** + - Четко сформулируй тему занятия. + - Определи основную цель и конкретные задачи, которые необходимо решить. + +2. **Необходимые материалы:** + - Перечисли все материалы и инвентарь, необходимые для проведения занятия (например, зеркала, лото, карточки и т.д.). + +3. **Ход занятия:** + - Разбей занятие на логически последовательные этапы: организационный момент, основная часть, речевые игры, артикуляционная разминка, заключительная часть. + +4. **Встроенные упражнения с достаточным количеством материала:** + - Приведи конкретные упражнения (например, списки слов, предложения, игры) с подробными примерами, соответствующими заявленным параметрам. + +5. **Использование загруженных файлов (если есть):** + - Если логопед загрузил файлы с логопедическими материалами, используй `file search` для поиска наиболее релевантных фрагментов, связанных с темой и целью занятия. + - Интегрируй найденные материалы в ход занятия, указывая, где и как они используются. + - Если подходящих материалов в файлах нет, сгенерируй примеры самостоятельно. + +6. **Подбор материалов из свободных источников (если разрешен web search):** + - Если разрешен веб-поиск ({разрешен_web_search} = "Да"), найди релевантные материалы (иллюстрации, видео, списки слов), которые будут интегрированы в ход занятия. + - Для каждого найденного материала укажи, на каком этапе занятия он используется и какую функцию выполняет (например, "Видео с артикуляционной гимнастикой", "Карточки с изображениями для упражнения по звуку [С]"). + - Если веб-поиск не разрешен или не удалось найти подходящие материалы, сгенерируй примеры самостоятельно. + +7. **Домашнее задание (если предусмотрено):** + - Если параметр {наличие_ДЗ} указывает на наличие домашнего задания, предложи конкретное домашнее задание, соответствующее теме занятия. + +**Важно:** Используй загруженные материалы и web search только если они доступны. В остальных случаях сгенерируй примеры самостоятельно. + + + prompt = f""" + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. + Ты получаешь запрос на разработку занятия со следующими параметрами: + - **Основное нарушение ребенка:** {params['нарушение']} + - **Возраст:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Доступный инвентарь:** {params['инвентарь'] or "не указано, на твое усмотрение"} + - **Особые условия:** {params['особые_условия'] or "отсутствуют"} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Поиск по интернету:** {params['разрешен_web_search']} + - **Текущий месяц :** {params['текущий_месяц']} (1=январь и т.д.) + + На основе этих параметров составь план занятия, который должен включать: + 1. **Тема занятия, цель и задачи (не более 3-х)** + 2. **Необходимый инвентарь** + 3. **Ход занятия**. Состоит из логически последовательных этапов. + В каждый этап встроены конкретные упражнения с достаточным количеством примеров. + СТРОГО: если **поиск по интернету: True**, то обязательно найди упражнения из релевантных источников. + (если параметр "наличие домашнего задания": True): 5. **Домашнее задание** + (если параметр "особые условия": True)6. **Рекомендации по особым условиям** (если указан параметр "Особые условия", иначе пропусти) + """ + + + + + + + + + + + + + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. + Ты получаешь запрос на разработку занятия со следующими параметрами: + - **Основное нарушение ребенка:** {params['нарушение']} + - **Возраст:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Доступный инвентарь:** {params['инвентарь'] or "не указано, на твое усмотрение"} + - **Особые условия:** {params['особые_условия'] or "отсутствуют"} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Поиск по интернету:** {params['разрешен_web_search']} + - **Текущий месяц :** {params['текущий_месяц']} (1=январь и т.д.) + + + На основе этих параметров составь план занятия, который должен включать: + 1. **Тема занятия, цель и задачи (не более 3-х)** + 2. **Необходимый инвентарь** + 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и достаточным количеством примеров. + 4. **Домашнее задание** (если параметр "наличие домашнего задания": True) + 5. **Рекомендации по особым условиям** (если параметр "особые условия" указан) + + **Особое условие:** + Если **поиск по интернету** равен **True**, то: + - Найди актуальные примеры упражнений и интегрируй их в соответствующие этапы занятия. + - Для каждого найденного упражнения укажи источник (ссылку). + + + + + + + + + + + + + +**Инструкции для веб-поиска (если разрешен):** +1. **Сформулируй поисковые запросы** на основе данных о ребенке: + - Учти: нарушение ({params['нарушение']}), возраст ({params['возраст_ребенка']}), цель ({params['цель_занятия']}). + - Пример запроса: "логопедические упражнения при дизартрии для детей 5 лет на развитие артикуляционной моторики". +2. **Используй только авторитетные источники**: + - Научные статьи, образовательные порталы (например, logoped.ru, defektolog.pro), методические пособия. + - Избегай блогов, форумов и непроверенных ресурсов. +3. **Адаптируй найденные упражнения**: + - Проверь соответствие инвентарю ({params['инвентарь']}) и длительности ({params['длительность_занятия']} мин). + - Упрости/усложни задания в зависимости от возраста. + - Если упражнение требует запрещенного инвентаря — предложи альтернативу. +4. **Укажи источники** в формате: [Название ресурса] (ссылка/автор) — например: "Упражнение «Веселый язычок» (методика Н.В. Нищевой)". + +**Структура плана занятия (допол + + + + + +{ + "нарушение": "дислалия", + "возраст_ребенка": "5 лет", + "цель_занятия": "автоматизация звука [Р] в словах и фразах", + "тема": "Путешествие в страну звука [Р]", + "формат_занятия": "индивидуальное", + "количество_детей": "1", + "инвентарь": "зеркало, картинки со звуком [Р], мяч, кубики с буквами", + "особые_условия": "гиперактивность", + "длительность_занятия": "30", + "наличие_ДЗ": "True", + "разрешен_web_search": "True", + "текущий_месяц": "4" +} + + + + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. + Ты получаешь запрос на разработку занятия со следующими параметрами: + - **Основное нарушение ребенка:** {params['нарушение']} + - **Возраст:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Доступный инвентарь:** {params['инвентарь'] or "не указано, на твое усмотрение"} + - **Особые условия:** {params['особые_условия'] or "не указаны"} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Поиск по интернету:** {params['разрешен_web_search']} + - **Текущий месяц года:** {params['текущий_месяц']} (1=январь и т.д.) + + На основе этих параметров составь план занятия, который должен включать: + 1. **Тема занятия, цель и задачи (не более 3-х)** + 2. **Необходимый инвентарь** + 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и достаточным количеством примеров. + **Особое условие:** + Если **поиск по интернету** равен **True**, то: + - Найди актуальные примеры упражнений и интегрируй их в соответствующие этапы занятия. + - При необходимости адаптируй найденные упражнения для ребенка/детей. + - Для каждого найденного упражнения укажи источник (ссылку). + 4. **Домашнее задание** (если параметр "наличие домашнего задания": True, иначе не упоминай в ответе) + 5. **Рекомендации по особым условиям** (если параметр "особые условия" указан, иначе не упоминай в ответе) + + + Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями. + На основе параметров занятия составь план занятия, который должен включать: + 1. **Тема занятия, цель и задачи (не более 3-х)** + 2. **Необходимый инвентарь** + 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и достаточным количеством примеров. + **Особое условие:** + Если **поиск по интернету** равен **True**, то: + - Найди примеры релевантных упражнений и интегрируй их в соответствующие этапы занятия. + - При необходимости адаптируй найденные упражнения для ребенка/детей. + - Для каждого найденного упражнения укажи источник (ссылку). + 4. **Домашнее задание** (если параметр "наличие домашнего задания": True, иначе не упоминай в ответе) + 5. **Рекомендации по особым условиям** (если параметр "особые условия" указан, иначе не упоминай в ответе) + + Параметры занятия: + - **Основное нарушение ребенка:** {params['нарушение']} + - **Возраст:** {params['возраст_ребенка']} + - **Цель занятия:** {params['цель_занятия']} + - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"} + - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей) + - **Доступный инвентарь:** {params['инвентарь'] or "не указано, на твое усмотрение"} + - **Особые условия:** {params['особые_условия'] or "не указаны"} + - **Длительность занятия:** {params['длительность_занятия']} минут + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Поиск по интернету:** {params['разрешен_web_search']} + - **Текущий месяц года:** {params['текущий_месяц']} (1=январь и т.д.) diff --git a/app/quotes.py b/app/quotes.py new file mode 100644 index 0000000000000000000000000000000000000000..621fee27d6344bf3fadae92f7e3ef5f4e6ec508f --- /dev/null +++ b/app/quotes.py @@ -0,0 +1,34 @@ +quotes = [ + "«Речь - не только средство общения, но и орудие мышления.»
Р.Е. Левина, 'Основы теории и практики логопедии' (1968)", + "«Логопед должен быть и врачом, и педагогом, и психологом.»
С.С. Ляпидевский, 'Невропатология' (1965)", + "«Речь - это зеркало души, отражающее её состояние.»
В.К. Орфинская, 'Методы работы при алалии' (1963)", + "«Коррекция речи - это создание условий для естественного развития.»
О.В. Правдина, 'Логопедия' (1973)", + "«Заикание - не болезнь, а привычка, которую можно изменить.»
Ф.А. Рау, 'Заикание и его преодоление' (1948)", + "«Логопед открывает ребёнку мир, где слова обретают смысл.»
Т.Б. Филичева, 'Логопедическая работа в специальном детском саду' (1987)", + "«Заикание - вызов, который можно преодолеть.»
В.И. Селивёрстов, 'Заикание у детей' (1994)", + "«Речевая патология - не тупик, а особый путь развития.»
Е.М. Мастюкова, 'Ребёнок с отклонениями в развитии' (1992)", + "«Дислексия - иной способ восприятия текста.»
О.Б. Иншакова, 'Альбом для логопеда' (2003)", + "«Ребёнок с нарушением речи - прежде всего ребёнок.»
Г.В. Чиркина, 'Коррекция нарушений речи' (2009)", + "«Логопедия - это наука о возможностях.»
Т.Г. Визель, 'Основы нейропсихологии' (2005)", + "«Каждое слово ребёнка - шаг в развитии.»
Н.С. Жукова, 'Логопедия. Преодоление ОНР' (1998)", + "«Игра - главный инструмент в работе с речью.»
Е.А. Стребелева, 'Коррекционно-педагогическая помощь' (2008)", + "«Речь формируется в диалоге, а не в упражнениях.»
А.В. Ястребова, 'Преодоление общего недоразвития речи' (1999)", + "«Мотивация - двигатель речевого развития.»
Л.Г. Парамонова, 'Логопедия для всех' (2002)", + "«Ошибки в речи - это ступени к правильному произношению.»
Т.А. Ткаченко, 'Если дошкольник плохо говорит' (2001)", + "«Ребёнок учится говорить, когда ему есть что сказать.»
С.Н. Шаховская, 'Логопедическая работа с детьми' (1985)", + "«Логопед не исправляет речь, а помогает ей развиваться.»
В.К. Воробьёва, 'Методика развития речи' (2006)", + "«Речевые нарушения - это сигналы, а не приговор.»
Р.И. Лалаева, 'Логопедическая работа в коррекционных классах' (1998)", + "«Главное в логопедии - видеть личность за дефектом.»
Б.М. Гриншпун, 'Дефектология и логопедия' (1983)", + + # Зарубежные + "«Речь - это мост между мыслью и действием.»
Чарльз Ван Райпер (1954)", + "«Заикание - это не то, что у тебя есть, это то, что ты делаешь.»
Оливер Бладстайн (1987)", + "«Логопедия - это не исправление ошибок, а раскрытие потенциала.»
Рэймонд Д. Кент (1994)", + "«Язык рождается из потребности человека соединиться с другим человеком.»
Роман Якобсон (1960)", + "«Говорить - значит быть человеком.»
Пол Брока (1861)", + "«Лучший способ помочь ребёнку заговорить - это слушать.»
Элейн Силлман", + "«Даже самая сложная речь начинается с одного слова.»
Барри Гутар (1982)", + "«Молчание — это не отсутствие речи, а её начало.»
Питер Рэминг (1985)", + "«Если вы видите в человеке то, чего он в себе не видит, — он этим становится.»
Виктор Франкл", + "«Ожидания взрослых напрямую влияют на прогресс ребёнка»
Пигмалилион в классе, Р. Розенталь и Л. Джекобсон (1968)" +] diff --git a/app/styles.css b/app/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..519f149e31c1406ff4b0b8a9804470c3de21f5f8 --- /dev/null +++ b/app/styles.css @@ -0,0 +1,53 @@ +/* Основной фон */ +/*.gradio-container { + background-color: #fef3c7; +} */ + +/* Заголовок */ +/* .main-title h2 { */ + text-align: left; + font-size: 2rem; + margin: 0; /* Убираем отступы вокруг заголовка */ + /*color: #003366;*/ + margin-bottom: -5px; !important /* Убираем отступ снизу заголовка */ + padding: 0; +} + + +/* Цитата */ +.quote-block { + text-align: left; + /*font-style: italic;*/ + padding: 0; + margin: -1px 0 0 0; /* верхний небольшой отступ, снизу чуть больше */ + /*max-width: 30em;*/ /* ограничиваем ширину, чтобы не тянулась на всю строку */ + line-height: 1.4em; +} + + +/* .left-col { */ +/* background-color: #CDE8FA; /* более тёмный голубой */ +/* padding: 20px; */ + /* Можно и цвет текста подкорректировать */ +/* color: #003366; */ +/* } */ + +.right-col { + background-color: #F0FAFF; /* посветлее */ + padding: 0px; + color: #fef3c7; +} + + /* Поле для конспекта */ +#plan-output { + border: 2px dashed #bbb; + border-radius: 12px; + background-color: ##f5f5f5; + padding: 20px; + min-height: 1100px; + font-style: italic; + color: #555; + box-shadow: 0 2px 6px rgba(0,0,0,0.05); +} + +/*#f5f5f5*/ \ No newline at end of file diff --git a/app/test.py b/app/test.py new file mode 100644 index 0000000000000000000000000000000000000000..5498be5d63748db612d1a08ef15c99972177f172 --- /dev/null +++ b/app/test.py @@ -0,0 +1,77 @@ +import os # Импортируем модуль для работы с переменными окружения +import asyncio # Импортируем модуль для поддержки асинхронного программирования +from dotenv import load_dotenv # Импортируем функцию для загрузки переменных окружения из файла .env +from openai import OpenAI +# Загружаем переменные окружения из файла .env +load_dotenv() +api_key=os.getenv("API_KEY_openai") +async def main(): + client = OpenAI(api_key=api_key) + # Пример параметров для запроса + params = { + "нарушение": "ОНР", # Тип речевого нарушения + "цель_занятия": "Развитие фонематического слуха", # Цель занятия + "месяц": "апрель", # Месяц проведения занятия + "инвентарь": "зеркало, карточки с изображениями", # Необходимый инвентарь + "количество_детей": 1, # Количество детей на занятии + "длительность_занятия": 30, # Длительность занятия в минутах + "наличие_ДЗ": False, # Наличие домашнего задания (True или False) + "загруженные_материалы": None, # Путь к загруженным материалам, если есть + "разрешен_web_search": True # Разрешение на использование веб-поиска + } + + # Формируем текст запроса (промпт) на основе предоставленных параметров + prompt = f""" + Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры: + - **Нарушение:** {params['нарушение']} + - **Цель занятия:** {params['цель_занятия']} + - **Месяц:** {params['месяц']} + - **Инвентарь:** {params['инвентарь']} + - **Количество детей:** {params['количество_детей']} + - **Длительность занятия:** {params['длительность_занятия']} + - **Наличие домашнего задания:** {params['наличие_ДЗ']} + - **Загруженные материалы (если есть):** {params['загруженные_материалы']} + - **Разрешен web search:** {params['разрешен_web_search']} + + На основе этих параметров составь конспект занятия, который должен включать: + 1. **Тема занятия, цель и задачи:** Четко сформулируй тему занятия, основную цель и конкретные задачи, которые необходимо решить. + 2. **Необходимые материалы:** Перечисли все материалы и инвентарь, необходимые для проведения занятия. + 3. **Ход занятия:** Разбей занятие на логически последовательные этапы. + 4. **Встроенные в ход занятия упражнения с достаточным количеством материала:** Приведи конкретные упражнения с подробными примерами. + ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников. Обязательно интегрируй ссылки на найденные источники в текст ответа. + 5. **Использование загруженных файлов (если есть):** Если логопед загрузил файлы с логопедическими материалами, используй их для поиска наиболее релевантных фрагментов и интегрируй их в ход занятия. + 6. **Домашнее задание (если True):** Если параметр 'наличие_ДЗ' указывает на наличие домашнего задания, предложи конкретное домашнее задание, соответствующее теме занятия. + """ + + # Определяем список инструментов, которые будут использоваться в запросе + tools = [] + tool_choice = None # По умолчанию tool_choice отсутствует + + if params['разрешен_web_search']: + tools.append({ + "type": "web_search_preview", + "search_context_size": "medium", + "user_location": { + "type": "approximate", + "country": "RU" + } + }) + tool_choice = {"type": "web_search_preview"} # Устанавливаем tool_choice только если web search включен + + if params['загруженные_материалы']: + tools.append({"type": "file_search"}) # Добавляем инструмент поиска по файлам, если материалы загружены + + # Отправляем запрос к API OpenAI с использованием эндпоинта responses + response = client.responses.create( + model="gpt-4o-mini", # Используемая модель + input=prompt, # Текст запроса + tools=tools if tools else None, # Включаем инструменты, если они были определены + tool_choice=tool_choice, # Передаем tool_choice только если он определен + max_output_tokens=1200 # Максимальное количество токенов в ответе + ) + + # Выводим результат в консоль + print(response.output_text) + +# Запускаем асинхронную функцию +asyncio.run(main()) \ No newline at end of file diff --git a/app/theme_builder.py b/app/theme_builder.py new file mode 100644 index 0000000000000000000000000000000000000000..cbe5ff8b90ed28bf71014a56f2bbcd4e5795cc61 --- /dev/null +++ b/app/theme_builder.py @@ -0,0 +1,3 @@ +import gradio as gr + +gr.themes.builder() \ No newline at end of file diff --git a/app/tooltip.js b/app/tooltip.js new file mode 100644 index 0000000000000000000000000000000000000000..0421f1d4a91b222b3e602b7f1b7e1e8f0dc05e13 --- /dev/null +++ b/app/tooltip.js @@ -0,0 +1,27 @@ +// app/tooltip.js +function initProCheckboxTooltip() { + const checkboxLabel = document.querySelector('#pro-checkbox .wrap .label-wrap'); + if (checkboxLabel) { + checkboxLabel.setAttribute( + 'data-tooltip', + 'Включает упражнения из: Logopedia.pro, Maam.ru и других проверенных источников' + ); + + if (!checkboxLabel.querySelector('.tooltip-icon')) { + const icon = document.createElement('span'); + icon.className = 'tooltip-icon'; + icon.textContent = ' ⓘ'; + icon.style.color = '#1976D2'; + checkboxLabel.querySelector('label').appendChild(icon); + } + } +} + +// Инициализация при загрузке +document.addEventListener('DOMContentLoaded', initProCheckboxTooltip); + +// Отслеживание динамических изменений DOM +new MutationObserver(initProCheckboxTooltip).observe(document.body, { + childList: true, + subtree: true +}); \ No newline at end of file diff --git a/docs/costs.md b/docs/costs.md new file mode 100644 index 0000000000000000000000000000000000000000..0527b89d1b75b9c922ad5b657b2cd03bf193e686 --- /dev/null +++ b/docs/costs.md @@ -0,0 +1,21 @@ +openai - gpt-4o-mini: + input 0.15/1M + output 0.60/1M + +web search: +gpt-4o-mini or gpt-4o-mini-search-preview: +low - 0.025$ +med - 0.0275$ +high - 0.03$ + - responses web_search - 0.03$ + - file search tool call - 0,0025$ + +image input + ~30000/1 страница ~ 0.005$ ??? + + + +aitunnel + gpt-4o-mini + image input 0.85р=0.01$ + текстовый запрос - от 0.01р~0.0001$ \ No newline at end of file diff --git a/docs/errors.md b/docs/errors.md new file mode 100644 index 0000000000000000000000000000000000000000..eb930c591a7cf0261f8ae99ad4adcf095d66b42f --- /dev/null +++ b/docs/errors.md @@ -0,0 +1,5 @@ +❌ Ошибка: Error code: 500 - {'error': {'message': 'An error occurred while processing your request. You can retry your request, or contact us through +our help center at help.openai.com if the error persists. +Please include the request ID req_91a6425c4b4c40ed774a0cb21c9b7cf5 in your message.', 'type': 'server_error', 'param': None, 'code': 'server_error'}} + + diff --git a/docs/file_search b/docs/file_search new file mode 100644 index 0000000000000000000000000000000000000000..4fb8ed7e2d96155ff16e9531ebdab4cb0bd1eff4 --- /dev/null +++ b/docs/file_search @@ -0,0 +1,352 @@ +RETRIEVAL +https://platform.openai.com/docs/guides/retrieval?attributes-filter-example=filename&vector-store-operations=create&vector-store-batch-operations=update + + +Attributes +Each vector_store.file can have associated attributes, a dictionary of values that can be referenced when performing semantic search with attribute filtering. +The dictionary can have at most 16 keys, with a limit of 256 characters each. + +Create vector store file with attributes +client.vector_stores.files.create( + vector_store_id="vs_123", + file_id="file_123", + attributes={ + "region": "US", + "category": "Marketing", + "date": 1672531200 # Jan 1, 2023 + } +) + + + + +Chunking +By default, max_chunk_size_tokens is set to 800 and chunk_overlap_tokens is set to 400, meaning every file is indexed by being split up into 800-token chunks, +with 400-token overlap between consecutive chunks. + +You can adjust this by setting chunking_strategy when adding files to the vector store. There are certain limitations to chunking_strategy: + +max_chunk_size_tokens must be between 100 and 4096 inclusive. +chunk_overlap_tokens must be non-negative and should not exceed max_chunk_size_tokens / 2. + + + + +############ Vector store operations +CREATE +client.vector_stores.create( + name="Support FAQ", + file_ids=["file_123"] +) + +UPDATE +client.vector_stores.update( + vector_store_id="vs_123", + name="Support FAQ Updated" +) + + +############ Vector store file operations +CREATE +client.vector_stores.files.create_and_poll( + vector_store_id="vs_123", + file_id="file_123" +) + + +BATCH CREATE +client.vector_stores.file_batches.create_and_poll( + vector_store_id="vs_123", + file_ids=["file_123", "file_456"] +) + +UPDATE +client.vector_stores.files.update( + vector_store_id="vs_123", + file_id="file_123", + attributes={"key": "value"} +) +LIST +client.vector_stores.files.list( + vector_store_id="vs_123" +) + +############# ATTRIBUTES +Each vector_store.file can have associated attributes, a dictionary of values that can be referenced when performing semantic search with attribute filtering. +The dictionary can have at most 16 keys, with a limit of 256 characters each. + +CREATE VS FILE WITH ATTRIBUTES +client.vector_stores.files.create( + vector_store_id="vs_123", + file_id="file_123", + attributes={ + "region": "US", + "category": "Marketing", + "date": 1672531200 # Jan 1, 2023 + } +) + +SEARCH QUERY +user_query = "What is the return policy?" + +results = client.vector_stores.search( + vector_store_id=vector_store.id, + query=user_query, +) + + +FILE SEARCH TOOL +from openai import OpenAI +client = OpenAI() + +response = client.responses.create( + model="gpt-4o-mini", + input="What is deep research by OpenAI?", + tools=[{ + "type": "file_search", + "vector_store_ids": [""] + }] +) +print(response) + +RESPONSE +{ + "output": [ + { + "type": "file_search_call", + "id": "fs_67c09ccea8c48191ade9367e3ba71515", + "status": "completed", + "queries": ["What is deep research?"], + "search_results": null + }, + { + "id": "msg_67c09cd3091c819185af2be5d13d87de", + "type": "message", + "role": "assistant", + "content": [ + { + "type": "output_text", + "text": "Deep research is a sophisticated capability that allows for extensive inquiry and synthesis of information across various domains. It is designed to conduct multi-step research tasks, gather data from multiple online sources, and provide comprehensive reports similar to what a research analyst would produce. This functionality is particularly useful in fields requiring detailed and accurate information...", + "annotations": [ + { + "type": "file_citation", + "index": 992, + "file_id": "file-2dtbBZdjtDKS8eqWxqbgDi", + "filename": "deep_research_blog.pdf" + }, + { + "type": "file_citation", + "index": 992, + "file_id": "file-2dtbBZdjtDKS8eqWxqbgDi", + "filename": "deep_research_blog.pdf" + }, + { + "type": "file_citation", + "index": 1176, + "file_id": "file-2dtbBZdjtDKS8eqWxqbgDi", + "filename": "deep_research_blog.pdf" + }, + { + "type": "file_citation", + "index": 1176, + "file_id": "file-2dtbBZdjtDKS8eqWxqbgDi", + "filename": "deep_research_blog.pdf" + } + ] + } + ] + } + ] +} + + +LIMIT RESULTS + "max_num_results": 2 +INCLUDE SEARCH RESULTS + include=["file_search_call.results"] + + +METADATA FILTERING + + "filters": { + "type": "eq", + "key": "type", + "value": "blog" + } + + + + + + + + + +Response(id='resp_67f6d96ee8408192a8849670feda558e0faa975217e529bc', +created_at=1744230766.0, error=None, incomplete_details=None, instructions=None, +metadata={}, model='gpt-4o-mini-2024-07-18', object='response', output=[ResponseFileSearchToolCall(id='fs_67f6d97084588192afdf4ebf9b3121cc0faa975217e529bc', +queries=['упражнения для автоматизации звука Р', 'упражнения для звука Р', 'автоматизация звука Р'], status='completed', type='file_search_call', results=None), + ResponseOutputMessage(id='msg_67f6d97428f0819299ea4a771e639b3f0faa975217e529bc', + content=[ResponseOutputText(annotations=[AnnotationFileCitation(file_id='file-PiEcinbLiZwssqrka7N2P3', + index=315, type='file_citation', filename='Komarova_avtomatizatsia_zvuka_R.pdf'), + AnnotationFileCitation(file_id='file-QX2xWtyQLiCrU9rcMa9SAH', index=475, type='file_citation', + filename='Komarova_avtomatizatsia_zvuka_R-_1_.pdf'), AnnotationFileCitation(file_id='file-QX2xWtyQLiCrU9rcMa9SAH', + index=664, type='file_citation', filename='Komarova_avtomatizatsia_zvuka_R-_1_.pdf'), + AnnotationFileCitation(file_id='file-PiEcinbLiZwssqrka7N2P3', index=828, type='file_citation', + filename='Komarova_avtomatizatsia_zvuka_R.pdf'), AnnotationFileCitation(file_id='file-PiEcinbLiZwssqrka7N2P3', + index=828, type='file_citation', filename='Komarova_avtomatizatsia_zvuka_R.pdf'), + AnnotationFileCitation(file_id='file-JGY4tskUsk4fpd9vJN7dxr', index=1030, type='file_citation', + filename='Avtomatizatsia_zvuki_M_M_N_N.doc')], text='Вот несколько упражнений для автоматизации звука «Р», + которые можно использовать с детьми в возрасте 4-7 лет:\n\n1. Лабиринт:\n - Ребенок произносит звук «Р», + называя картинки на странице, перемещая игрушку по лабиринту. Если слово произнесено неправильно, следующий ход + задерживается до правильного произношения.\n\n2. Лабиринт с проговариванием:\n - При движении ребенку нужно не + только называть картинки, но и указывать направление хода (например, "пряники — вверх").\n\n3. Игры с игрушками:\n + - Используйте объемные или плоские игрушки, названия которых содержат звук «Р». Это помогает удерживать внимание + ребенка и делает занятия более увлекательными.\n\n4. Упражнения на слоги и слова:\n - Включите слова и слоги, + содержащие звук «Р», в игру, где ребенок будет составлять слова из первых букв названий картинок.\n\n5. Чистоговорки + и скороговорки:\n - Занимайтесь скороговорками, начиная с медленного произношения, затем постепенно увеличивайте + скорость, акцентируя внимание на правильном артикулировании звука.\n\nЭти упражнения помогут не только в автоматизации + звука «Р», но и в развитии мелкой моторики, фонематического слуха и навыков чтения.', type='output_text')], role='assistant', + status='completed', type='message')], parallel_tool_calls=True, temperature=1.0, tool_choice='auto', + tools=[FileSearchTool(type='file_search', vector_store_ids=['vs_67f69730803481918fb5049813e0bf7f'], + filters=None, max_num_results=20, ranking_options=RankingOptions(ranker='auto', score_threshold=0.0))], + top_p=1.0, max_output_tokens=None, previous_response_id=None, reasoning=Reasoning(effort=None, generate_summary=None), + status='completed', text=ResponseTextConfig(format=ResponseFormatText(type='text')), truncation='disabled', + usage=ResponseUsage(input_tokens=12398, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=389, + output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=12787), user=None, store=True) + + + + + + + + + +{ + "id": "resp_67ccf4c55fc48190b71bd0463ad3306d09504fb6872380d7", + "object": "response", + "created_at": 1741485253, + "status": "completed", + "error": null, + "incomplete_details": null, + "instructions": null, + "max_output_tokens": null, + "model": "gpt-4o-2024-08-06", + "output": [ + { + "type": "file_search_call", + "id": "fs_67ccf4c63cd08190887ef6464ba5681609504fb6872380d7", + "status": "completed", + "queries": [ + "attributes of an ancient brown dragon" + ], + "results": null + }, + { + "type": "message", + "id": "msg_67ccf4c93e5c81909d595b369351a9d309504fb6872380d7", + "status": "completed", + "role": "assistant", + "content": [ + { + "type": "output_text", + "text": "The attributes of an ancient brown dragon include...", + "annotations": [ + { + "type": "file_citation", + "index": 320, + "file_id": "file-4wDz5b167pAf72nx1h9eiN", + "filename": "dragons.pdf" + }, + { + "type": "file_citation", + "index": 576, + "file_id": "file-4wDz5b167pAf72nx1h9eiN", + "filename": "dragons.pdf" + }, + { + "type": "file_citation", + "index": 815, + "file_id": "file-4wDz5b167pAf72nx1h9eiN", + "filename": "dragons.pdf" + }, + { + "type": "file_citation", + "index": 815, + "file_id": "file-4wDz5b167pAf72nx1h9eiN", + "filename": "dragons.pdf" + }, + { + "type": "file_citation", + "index": 1030, + "file_id": "file-4wDz5b167pAf72nx1h9eiN", + "filename": "dragons.pdf" + }, + { + "type": "file_citation", + "index": 1030, + "file_id": "file-4wDz5b167pAf72nx1h9eiN", + "filename": "dragons.pdf" + }, + { + "type": "file_citation", + "index": 1156, + "file_id": "file-4wDz5b167pAf72nx1h9eiN", + "filename": "dragons.pdf" + }, + { + "type": "file_citation", + "index": 1225, + "file_id": "file-4wDz5b167pAf72nx1h9eiN", + "filename": "dragons.pdf" + } + ] + } + ] + } + ], + "parallel_tool_calls": true, + "previous_response_id": null, + "reasoning": { + "effort": null, + "generate_summary": null + }, + "store": true, + "temperature": 1.0, + "text": { + "format": { + "type": "text" + } + }, + "tool_choice": "auto", + "tools": [ + { + "type": "file_search", + "filters": null, + "max_num_results": 20, + "ranking_options": { + "ranker": "auto", + "score_threshold": 0.0 + }, + "vector_store_ids": [ + "vs_1234567890" + ] + } + ], + "top_p": 1.0, + "truncation": "disabled", + "usage": { + "input_tokens": 18307, + "input_tokens_details": { + "cached_tokens": 0 + }, + "output_tokens": 348, + "output_tokens_details": { + "reasoning_tokens": 0 + }, + "total_tokens": 18655 + }, + "user": null, + "metadata": {} +} diff --git a/docs/ideas.md b/docs/ideas.md new file mode 100644 index 0000000000000000000000000000000000000000..879d61b90ebc43f25273b973aa07185d23653b6e --- /dev/null +++ b/docs/ideas.md @@ -0,0 +1,2 @@ +Проблема: задания могут быть надерганы из контекста, а в оригинале у них есть объединяющие элементы +Идея: в промпт добавить указание на создание логически-связанного урока \ No newline at end of file diff --git a/docs/logic.md b/docs/logic.md new file mode 100644 index 0000000000000000000000000000000000000000..0abf7d0e9e23993c7d14959a2817ca41d26ecb2e --- /dev/null +++ b/docs/logic.md @@ -0,0 +1,95 @@ +Логика интерфейса в Streamlit: + +Левая панель (настройки) + +Поле ввода (свободный текст) → "Нарушение" + +Поле ввода (свободный текст) → "Цель занятия" + +Выпадающий список (dropdown) → "Месяц" + +Чекбоксы + поле ввода → "Инвентарь" + +Зеркало + +Кубики + +Игрушки + +Поле для ввода своего варианта + +Ползунок (slider) → "Количество детей" (от 1 до 10) + +Чекбокс → "Наличие домашнего задания" + +Форма загрузки файлов (пока без обработки) → "Загруженные материалы" + +Чекбокс → "Поиск по материалам в интернете (web search)" + +Кнопка → "Создать конспект" + +Правая панель (вывод конспекта) + +До нажатия на кнопку: пустая область + +После нажатия: появляется сгенерированный текст конспекта + +Как избежать автоматического обновления (мигания экрана и сброса параметров) + +Используем st.session_state для хранения значений полей, чтобы они не сбрасывались + +Запуск генерации конспекта только при нажатии на кнопку (обернем обработчик в st.button) + +Проверка параметров + +Все основные параметры присутствуют + +Загруженные файлы пока не обрабатываем + +Web search включается через чекбокс + +Форма взаимодействия понятна пользователю + + +Логика работы параметров и запуска кода: +Изменение параметров не должно запускать генерацию + +Все параметры (чекбоксы, выпадающие списки, текстовые поля) можно менять без триггера обновления страницы + +Состояние параметров фиксируется в st.session_state, чтобы при любом изменении они не сбрасывались + +Код запускается только при нажатии кнопки + +После ввода всех параметров пользователь нажимает "Создать конспект" + +В этот момент проверяются обязательные поля + +Если все обязательные поля заполнены, данные передаются в генерацию + +Если какие-то обязательные поля пустые, выводится предупреждение + +Разделение параметров на обязательные и необязательные + +Обязательные (без них кнопку нажать нельзя): + +Нарушение + +Цель занятия + +Количество детей (по умолчанию, например, 1) + +Длительность занятия (по умолчанию, например, 30 минут) + +Необязательные (по умолчанию пустые или выключенные): + +Инвентарь + +Месяц + +Поиск по материалам в интернете + +UX-детали + +Если обязательное поле пустое, кнопка "Создать конспект" неактивна (disabled=True) или выводится предупреждение + +После генерации конспекта в правой части экрана результат остается до следующего нажатия кнопки \ No newline at end of file diff --git a/docs/misc.md b/docs/misc.md new file mode 100644 index 0000000000000000000000000000000000000000..9902e342759a9c5622ac87074ea1b9ce0e03112f --- /dev/null +++ b/docs/misc.md @@ -0,0 +1,14 @@ + +streamlit run app/main.py +streamlit run app/main_completions.py + + +git push --force space main + + + +# Logoped +Ассистент логопеда по созданию планов занятий. +Формирует методически-выверенный и релевантный план занятия на основе входных параметров от логопеда. +Параметры сформированы по пожеланиям экспертов-логопедов. + diff --git a/docs/mistakes.md b/docs/mistakes.md new file mode 100644 index 0000000000000000000000000000000000000000..742f75271bf3365750f89b7958f0cd77115e7f91 --- /dev/null +++ b/docs/mistakes.md @@ -0,0 +1,24 @@ +1. Автоматизация звука [р] в предложениях (5 минут): +Игра "Четвертый лишний": ## правильно нашел задание +Логопед называет четыре слова, одно из которых не содержит звук [р]. ## здесь ошибка, неправильно понял задание +Примеры: РОБОТ, ВОРОНА, КОРОВА, БАРАБАН (лишнее — ворона). +Ребенок придумывает предложение с лишним словом: "Ворона сидит на заборе." +Повторить с другими наборами слов. + + +Из пособия? +Автоматизация звука [р] в предложениях. +(ребёнок открывает кармашек зелёного цвета) + +Игра «Четвёртый лишний» +- Определи какой из этих предметов лишний и объясни почему. ## другой смысл задания + +РОБОТ, ВОРОНА, КОРОВА, БАРАБАН +РЫБА, РАК, РУКИ, ТОПОР +ГОРОХ, МУХОМОР, КУКУРУЗА, МОРКОВЬ +ПОМИДОР, РАДУГА, ЗАБОР, САМОВАР + + +2. Тема занятия: "Летние приключения с ## ЛЕТНИЕ! +В промпте: - **Текущий месяц :** 4 (1=январь и т.д.) +Решение: улучшить промпт \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..a925a7452afd7a16874a49770798ab30851f2652 Binary files /dev/null and b/requirements.txt differ diff --git a/test b/test new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test_API.py b/test_API.py new file mode 100644 index 0000000000000000000000000000000000000000..491772627408232078c3d1133699887d04b1d011 --- /dev/null +++ b/test_API.py @@ -0,0 +1,46 @@ +import requests + +API_KEY = "" + +headers = { + "Authorization": f"Bearer {API_KEY}" +} + +response = requests.get("https://api.openai.com/v1/models", headers=headers) + +print(response.status_code) # Должно быть 200, если ключ работает +print(response.json()) # Посмотрим, что вернет API + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test_completions.py b/test_completions.py new file mode 100644 index 0000000000000000000000000000000000000000..5f7305c8584c026849bcd18c4c28a397433dfafd --- /dev/null +++ b/test_completions.py @@ -0,0 +1,22 @@ +import os +from openai import OpenAI +from dotenv import load_dotenv + +# 5. Загрузка переменных окружения +load_dotenv() +API_KEY = os.getenv("API_KEY") +BASE_URL = os.getenv("BASE_URL") + +# 6. Инициализация клиента OpenAI +client = OpenAI(api_key=API_KEY, base_url=BASE_URL) + +# Отправка запроса к chat.completions +response = client.chat.completions.create( + model="gpt-4o-mini", # Можно заменить на другую доступную модель + messages=[ + {"role": "user", "content": "Найди свежие новости про искусственный интеллект и дай мне ссылки."} + ] +) + +# Вывод ответа в консоль +print(response.choices[0].message.content) diff --git a/test_proxy.py b/test_proxy.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test_responses.py b/test_responses.py new file mode 100644 index 0000000000000000000000000000000000000000..fdd15694e7f9be7ee9df8e4f9cb97b78a46b55b7 --- /dev/null +++ b/test_responses.py @@ -0,0 +1,23 @@ +import openai +import os +import asyncio +from dotenv import load_dotenv + +load_dotenv() + +client = openai.AsyncOpenAI( + api_key=os.getenv("API_KEY_openai"), + timeout=60 # Устанавливаем время ожидания в 60 секунд + ) + +# Отправляем запрос с инструментом web_search_preview +response = client.responses.create( + model="gpt-4o-mini", # Используем модель с поддержкой web_search + tools=[{"type": "web_search_preview"}], # Указываем инструмент поиска + input="What was a positive news story from 1878? Find a different answer each request" # Ваш запрос +) + +# Получаем результат +output_text = response.output_text +print("Ответ:", output_text) + diff --git a/test_web_search.py b/test_web_search.py new file mode 100644 index 0000000000000000000000000000000000000000..e95c837cf366f07418c545468682873de1eedf2c --- /dev/null +++ b/test_web_search.py @@ -0,0 +1,35 @@ +import os # Импортируем модуль для работы с переменными окружения +import asyncio # Импортируем модуль для поддержки асинхронного программирования +from dotenv import load_dotenv # Импортируем функцию для загрузки переменных окружения из файла .env +from openai import OpenAI +# Загружаем переменные окружения из файла .env +load_dotenv() +api_key=os.getenv("API_KEY_openai") + +client = OpenAI(api_key=api_key) + +# Тестовый запрос +prompt = "Какие современные методы используются для развития фонематического слуха у детей? Приведи примеры и ссылки на источники." + +tools = [ + { + "type": "web_search_preview", + "search_context_size": "low", + "user_location": { + "type": "approximate", + "country": "RU" + } + } +] + +# Отправляем запрос к API +response = client.responses.create( + model="gpt-4o-mini", + input=prompt, + tools=tools, + max_output_tokens=1000 +) + +# Выводим результат в консоль +print("Ответ модели:") +print(response.output_text)