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)