zenivan commited on
Commit
58a9b80
·
0 Parent(s):

олная очистка: удалены все бинарные файлы

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.gitignore ADDED
Binary file (216 Bytes). View file
 
.idea/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
.idea/inspectionProfiles/profiles_settings.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
.idea/logos.iml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.venv" />
6
+ </content>
7
+ <orderEntry type="inheritedJdk" />
8
+ <orderEntry type="sourceFolder" forTests="false" />
9
+ </component>
10
+ </module>
.idea/misc.xml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="Python 3.13 (logos)" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (logos)" project-jdk-type="Python SDK" />
7
+ </project>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/logos.iml" filepath="$PROJECT_DIR$/.idea/logos.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
README.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: KonspektLogoped
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: gradio
7
+ sdk_version: 5.23.3
8
+ app_file: app/main_gradio_test_FS.py
9
+ pinned: false
10
+ short_description: Помощник в создании логопедических конспектов
11
+ ---
12
+
13
+
14
+ CHECK
15
+ CHECK
16
+ CHECK
17
+ CHECK
18
+ git add .
19
+ git commit -m "..."
20
+ git push space main
TASKS.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [x]
2
+ [ ]
3
+
4
+ ## to-do
5
+ [ ] доп. функция - поиск по базе (file_search)
6
+ [ ] поле ввода веб-источников (ограничение web search)
7
+ [ ] сохранение истории сессий пользователя — очень легко сделать через st.session_state["history"].
8
+ [ ] деплой в Hugging Face Spaces
9
+ [ ] анкета для тестовых пользователей - UX, содержание
10
+ [ ] файн-тюнинг мистейков
11
+ [ ] для тестовых пользователей - форма отправки мистейка
12
+ [ ] сделать базу с основными источниками - вместо web search как бы file search -- однозначная платная опция
13
+ [ ] Функция учета предыдущей генерации (assistants message - conversation) для модификации плана занятия (параметр store)
14
+ ## in progress
15
+
16
+ [ ] stream=True
17
+ [ ] web search настройка
18
+ [ ] ссылки удобнее в конце или на каждом источнике?
19
+
20
+ ## done
21
+ [x] интервью с логопедом о нужном функционале и параметрах
22
+ [x] определение входящих параметров
23
+ [x] определение используемых методов
24
+ [x] базовый промпт
25
+ [x] базовый MVP
26
+ [x] если при генерации я меняю настройки - генерация сбивается
27
+ [x] переход на gradio
app/.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5
+ WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6
+ ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7
+ MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8
+ h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9
+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10
+ A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11
+ T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12
+ B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13
+ B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14
+ KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15
+ OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16
+ jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17
+ qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18
+ rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20
+ hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21
+ ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22
+ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23
+ NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24
+ ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25
+ TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26
+ jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27
+ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28
+ 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29
+ mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30
+ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31
+ -----END CERTIFICATE-----
app/Stable.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ from dotenv import load_dotenv
4
+ from openai import OpenAI
5
+ import streamlit as st
6
+
7
+ # Загрузка переменных окружения
8
+ load_dotenv()
9
+ api_key = os.getenv("API_KEY_openai")
10
+
11
+
12
+ def generate_lesson_plan(params):
13
+ client = OpenAI(api_key=api_key)
14
+
15
+ prompt = f"""
16
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры:
17
+ - **Основное нарушение:** {params['нарушение']}
18
+ - **Возраст ребенка:** {params['возраст_ребенка']}
19
+ - **Цель занятия:** {params['цель_занятия']}
20
+ - **Тематика:** {params['тематика']}
21
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
22
+ - **Инвентарь:** {params['инвентарь']}
23
+ - **Особые условия:** {params['особые_условия']}
24
+ - **Длительность занятия:** {params['длительность_занятия']} минут
25
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
26
+ - **Разрешен web search:** {params['разрешен_web_search']}
27
+
28
+ На основе этих параметров составь конспект занятия, который должен включать:
29
+ 1. **Тема занятия, цель и задачи**
30
+ 2. **Необходимые материалы**
31
+ 3. **Ход занятия**. Разбей занятие на логически последовательные этапы.
32
+ В каждый этап встрой конкретные упражнения с достаточным количеством примеров.
33
+ ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников.
34
+ Обязательно интегрируй ссылки на найденные источники в текст ответа.
35
+ 5. **Домашнее задание** (если параметр "наличие домашнего задания: True")
36
+ 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия")
37
+ """
38
+
39
+ tools = []
40
+ tool_choice = None
41
+
42
+ if params['разрешен_web_search']:
43
+ tools.append({
44
+ "type": "web_search_preview",
45
+ "search_context_size": "medium",
46
+ "user_location": {
47
+ "type": "approximate",
48
+ "country": "RU"
49
+ }
50
+ })
51
+ tool_choice = {"type": "web_search_preview"}
52
+
53
+ response = client.responses.create(
54
+ model="gpt-4o-mini",
55
+ input=prompt,
56
+ tools=tools if tools else None,
57
+ tool_choice=tool_choice,
58
+ max_output_tokens=2000
59
+ )
60
+ return response.output_text
61
+
62
+
63
+ # Интерфейс Streamlit
64
+ st.title("Генератор логопедических занятий")
65
+
66
+ # Левая часть с настройками
67
+ with st.sidebar:
68
+ st.header("Настройки занятия")
69
+
70
+ # Обязательные поля
71
+ нарушение = st.text_input(
72
+ "Основное нарушение*",
73
+ placeholder="Пример: Дислалия (свистящие), ОНР II уровня"
74
+ )
75
+
76
+ возраст_ребенка = st.text_input(
77
+ "Возраст ребенка*",
78
+ placeholder="Пример: 5 лет, 6-7 лет"
79
+ )
80
+
81
+ цель_занятия = st.text_input(
82
+ "Цель занятия*",
83
+ placeholder="Пример: Автоматизация звука [Р] в слогах"
84
+ )
85
+
86
+ # Переключатель формата занятия
87
+ формат_занятия = st.radio(
88
+ "Формат занятия*",
89
+ options=["Индивидуальное", "Групповое"],
90
+ index=0,
91
+ horizontal=True
92
+ )
93
+
94
+ количество_детей = 1 # Значение по умолчанию
95
+
96
+ if формат_занятия == "Групповое":
97
+ количество_детей = st.number_input(
98
+ "Количество детей в группе*",
99
+ min_value=2,
100
+ max_value=10,
101
+ value=3
102
+ )
103
+
104
+ # Дополнительные параметры
105
+ тематика = st.text_input(
106
+ "Тематика занятия",
107
+ placeholder="Пример: Животные, Транспорт"
108
+ )
109
+
110
+ особые_условия = st.text_input(
111
+ "Особые условия",
112
+ placeholder="Пример: гиперактивность, РАС"
113
+ )
114
+
115
+ # И��вентарь
116
+ st.subheader("Инвентарь")
117
+ инвентарь_чекбоксы = {
118
+ "Зеркало": st.checkbox("Зеркало"),
119
+ "Карточки": st.checkbox("Карточки"),
120
+ "Игрушки": st.checkbox("Игрушки")
121
+ }
122
+ дополнительный_инвентарь = st.text_input("Другое")
123
+
124
+ инвентарь = ", ".join([k for k, v in инвентарь_чекбоксы.items() if v])
125
+ if дополнительный_инвентарь:
126
+ инвентарь += f", {дополнительный_инвентарь}"
127
+
128
+ # Другие настройки
129
+ длительность_занятия = st.slider(
130
+ "Длительность (минут)",
131
+ min_value=15,
132
+ max_value=60,
133
+ value=30,
134
+ step=5
135
+ )
136
+
137
+ наличие_ДЗ = st.checkbox("Домашнее задание")
138
+ разрешен_web_search = st.checkbox("Поиск материалов в интернете")
139
+
140
+ # Кнопка генерации
141
+ сгенерировать = st.button("Создать конспект")
142
+
143
+ # Правая часть с результатом
144
+ if сгенерировать:
145
+ if not нарушение or not возраст_ребенка or not цель_занятия:
146
+ st.error("Заполните обязательные поля!")
147
+ else:
148
+ params = {
149
+ "нарушение": нарушение,
150
+ "возраст_ребенка": возраст_ребенка,
151
+ "цель_занятия": цель_занятия,
152
+ "тематика": тематика,
153
+ "формат_занятия": формат_занятия,
154
+ "количество_детей": количество_детей,
155
+ "инвентарь": инвентарь,
156
+ "особые_условия": особые_условия,
157
+ "длительность_занятия": длительность_занятия,
158
+ "наличие_ДЗ": наличие_ДЗ,
159
+ "разрешен_web_search": разрешен_web_search
160
+ }
161
+
162
+ with st.spinner("Создаем конспект..."):
163
+ try:
164
+ результат = generate_lesson_plan(params)
165
+ st.success("Готово!")
166
+ st.markdown(результат)
167
+ except Exception as e:
168
+ st.error(f"Ошибка: {e}")
app/data/attributes_template.json ADDED
File without changes
app/data/existing_files.json ADDED
File without changes
app/data/files_management.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import hashlib
4
+ import logging
5
+ from openai import OpenAI
6
+ from dotenv import load_dotenv
7
+
8
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
9
+ logger = logging.getLogger(__name__)
10
+
11
+ load_dotenv()
12
+ client = OpenAI(api_key=os.getenv("API_KEY_openai"))
13
+
14
+ # ==== Управление этапами через флаги ====
15
+ RUN_CREATE_VECTOR_STORE = False
16
+ RUN_UPLOAD_FILES = False
17
+ RUN_GENERATE_TEMPLATE = True
18
+ RUN_UPDATE_ATTRIBUTES = False
19
+ RUN_DELETE_FILE = False
20
+
21
+ # ==== Параметры ====
22
+ VECTOR_STORE_NAME = "Logoped_KB"
23
+ FILES_DIR = "materials/vector_store"
24
+ ATTRIBUTES_FILE = "attributes_template.json"
25
+ VS_ID = os.getenv("VECTOR_STORE_ID")
26
+ FILE_NAME_DELETE = ""
27
+
28
+
29
+
30
+ # 1. Создание векторного хранилища
31
+ def create_vector_store(name: str) -> str:
32
+ """Создает новое векторное хранилище, если его еще нет, и возвращает его ID"""
33
+
34
+ # Получаем список всех векторных хранилищ
35
+ existing_stores = client.vector_stores.list()
36
+
37
+ # Проверяем, существует ли хранилище с таким же именем
38
+ for store in existing_stores:
39
+ if store.name == name:
40
+ logger.info(f"Хранилище с именем '{name}' уже существует, его ID: {store.id}")
41
+ return store.id # Возвращаем существующий ID хранилища
42
+
43
+ # Если хранилище не найдено, создаем новое
44
+ vector_store = client.vector_stores.create(name=name)
45
+ logger.info(f"Создано хранилище: {vector_store.id}")
46
+ return vector_store.id
47
+
48
+
49
+ # 2. Загрузка файлов с проверкой дубликатов
50
+ def upload_files(vector_store_id: str, files_dir: str):
51
+ """
52
+ Загружает новые файлы в хранилище, пропуская уже существующие
53
+ Возвращает список file_id новых файлов
54
+ """
55
+ existing = load_existing_files()
56
+ new_files = []
57
+
58
+ for filename in os.listdir(files_dir):
59
+ file_path = os.path.join(files_dir, filename)
60
+ file_hash = calculate_hash(file_path)
61
+
62
+ # Если файл с таким именем и незменённым хэшем уже существует – пропускаем
63
+ if filename in existing and existing[filename]['hash'] == file_hash:
64
+ logger.info(f"Файл {filename} уже загружен, пропуск")
65
+ continue
66
+
67
+ try:
68
+ with open(file_path, "rb") as f:
69
+ # Загрузка файла в OpenAI (файл попадет в список "files")
70
+ uploaded_file = client.files.create(file=f, purpose="assistants")
71
+
72
+ # Добавление файла в векторное хранилище с (пока пустыми) атрибутами
73
+ # (Если атрибуты вы укажете в update_attributes позже,
74
+ # здесь можно передать {} или заранее известные значения.)
75
+ client.vector_stores.files.create(
76
+ vector_store_id=vector_store_id,
77
+ file_id=uploaded_file.id,
78
+ attributes={} # атрибуты можно обновить позже
79
+ )
80
+
81
+ # Сохраняем сведения о файле локально
82
+ existing[filename] = {
83
+ 'file_id': uploaded_file.id,
84
+ 'hash': file_hash,
85
+ 'attributes': {} # и здесь храните текущие атрибуты
86
+ }
87
+ new_files.append(uploaded_file.id)
88
+
89
+ except Exception as e:
90
+ logger.error(f"Ошибка загрузки {filename}: {str(e)}")
91
+
92
+ save_existing_files(existing)
93
+ return new_files
94
+
95
+
96
+ # 3. Генерация шаблона метаданных
97
+ def generate_attributes_template(vector_store_id: str):
98
+ """Создает JSON-файл с текущими метаданными для ручного редактирования"""
99
+ files_info = load_existing_files()
100
+ vector_files = client.vector_stores.files.list(vector_store_id=vector_store_id)
101
+
102
+ template = {}
103
+ for v_file in vector_files:
104
+ filename = next((k for k, v in files_info.items() if v['file_id'] == v_file.id), None)
105
+ if filename:
106
+ template[v_file.id] = {
107
+ "filename": filename,
108
+ "attributes": files_info[filename].get('attributes', {})
109
+ }
110
+
111
+ with open("attributes_template.json", "w") as f:
112
+ json.dump(template, f, indent=2, ensure_ascii=False)
113
+ logger.info("Шаблон метаданных сгенерирован")
114
+
115
+
116
+ # 4. Обновление метаданных
117
+ def update_attributes(vector_store_id: str, attributes_file: str):
118
+ """Обновляет метаданные файлов на основе редактированного шаблона"""
119
+ with open(attributes_file, "r") as f:
120
+ attributes = json.load(f)
121
+
122
+ existing = load_existing_files()
123
+ updated = 0
124
+
125
+ for file_id, data in attributes.items():
126
+ filename = data['filename']
127
+ if filename not in existing:
128
+ continue
129
+
130
+ try:
131
+ # Обновляем метаданные через API
132
+ client.vector_stores.files.update(
133
+ vector_store_id=vector_store_id,
134
+ file_id=file_id,
135
+ attributes=data['attributes']
136
+ )
137
+
138
+ # Обновляем локальную копию
139
+ existing[filename]['attributes'] = data['attributes']
140
+ updated += 1
141
+
142
+ except Exception as e:
143
+ logger.error(f"Ошибка обновления {filename}: {str(e)}")
144
+
145
+ save_existing_files(existing)
146
+ logger.info(f"Обновлено {updated} записей")
147
+
148
+
149
+ # Вспомогательные функции
150
+ def calculate_hash(file_path: str) -> str:
151
+ """Вычисляет SHA-256 хеш файла для отслеживания изменений"""
152
+ sha = hashlib.sha256()
153
+ with open(file_path, "rb") as f:
154
+ while True:
155
+ data = f.read(65536)
156
+ if not data:
157
+ break
158
+ sha.update(data)
159
+ return sha.hexdigest()
160
+
161
+
162
+ def load_existing_files() -> dict:
163
+ """Загружает информацию о ранее загруженных файлах"""
164
+ try:
165
+ with open("existing_files.json", "r") as f:
166
+ return json.load(f)
167
+ except (FileNotFoundError, json.JSONDecodeError):
168
+ return {}
169
+
170
+
171
+ def save_existing_files(data: dict):
172
+ """Сохраняет информацию о загруженных файлах"""
173
+ with open("existing_files.json", "w") as f:
174
+ json.dump(data, f, indent=2)
175
+
176
+
177
+ def delete_file_by_filename(filename: str):
178
+ """
179
+ Удаляет файл из OpenAI по имени (если он есть в existing_files.json)
180
+ """
181
+ existing = load_existing_files()
182
+ if filename not in existing:
183
+ print(f"Файл '{filename}' не найден в existing_files.json")
184
+ return
185
+
186
+ file_id = existing[filename]['file_id']
187
+ vector_store_id = VS_ID
188
+ try:
189
+ client.vector_stores.files.delete(
190
+ vector_store_id=vector_store_id,
191
+ file_id=file_id
192
+ )
193
+ print(f"Файл '{filename}' c ID {file_id} успешно удалён")
194
+
195
+ # Удаляем из локального кэша
196
+ del existing[filename]
197
+ save_existing_files(existing)
198
+ except Exception as e:
199
+ print(f"Ошибка при удалении файла '{file_id}': {str(e)}")
200
+
201
+ if __name__ == "__main__":
202
+
203
+ # ==== Запуск этапов ====
204
+ if RUN_CREATE_VECTOR_STORE:
205
+ VS_ID = create_vector_store(VECTOR_STORE_NAME)
206
+
207
+ if RUN_UPLOAD_FILES:
208
+ new_files = upload_files(VS_ID, FILES_DIR)
209
+ else:
210
+ new_files = []
211
+
212
+ if RUN_GENERATE_TEMPLATE:
213
+ generate_attributes_template(VS_ID)
214
+
215
+ if RUN_UPDATE_ATTRIBUTES:
216
+ update_attributes(VS_ID, ATTRIBUTES_FILE)
217
+
218
+ if RUN_DELETE_FILE:
219
+ delete_file_by_filename(FILE_NAME_DELETE)
app/drawings.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ drawings = [
2
+ r"""
3
+ \ | /
4
+ .-'-.
5
+ ― ( ☀ ) ―
6
+ '-.-'
7
+ / | \
8
+
9
+
10
+ ___I_
11
+ /\-_--\
12
+ / \_-__\
13
+ | [] | [] |
14
+ |____|____|
15
+ """,
16
+
17
+ r"""
18
+ ###################
19
+ # .-----._ #
20
+ # | O O | # ← Глаза в зеркале
21
+ # | ▽ | # ← Рот
22
+ # '-----' "Р-р-р!" #
23
+ # || #
24
+ ###################
25
+
26
+ |\_/|
27
+ / \
28
+ /_~_~_~_\
29
+ \o_o_o_o/ котик
30
+ || ||
31
+
32
+ """,
33
+
34
+ r"""
35
+ ╔════════════════════════════════════════════╗
36
+ ║ ║
37
+ ║ ║
38
+ ║ ║
39
+ ║ РА РО РУ РЫ ║
40
+ ║ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ║
41
+ ║ │Р│ │Р│ │Р│ │Р│ ║
42
+ ║ └─┘ └─┘ └─┘ └─┘ ║
43
+ ║ ║
44
+ ║ ( ) ← "Язычок!" ║
45
+ ║ /___\ ║
46
+ ║ | | ║
47
+ ║ ║
48
+ ║ ░░░░░░░░░ ║
49
+ ║ ░ ░ ← "Это море!" ║
50
+ ║ ░ >< ░ ║
51
+ ║ ░░░░░░░░░ ║
52
+ ║ ║
53
+ ║ O O O ← "Шарики!" ║
54
+ ║ \ | / ║
55
+ ║ \|/ ║
56
+ ║ "Фуух!" (дуем) ║
57
+ ║ ║
58
+ ║ АБВГДЕЁЖ ← "Азбука" ║
59
+ ║ \_____/ ║
60
+ ║ "Ура!" ║
61
+ ║ ║
62
+ ║ ♪~ ♪~ ♪~ ← "Пою звуки!" ║
63
+ ║ ║
64
+ ╚════════════════════════════════════════════╝
65
+ """,
66
+
67
+ r"""
68
+ _________________________________________________
69
+ / \
70
+ | ( ) |
71
+ | /___\ ← "Язычок на горке!" |
72
+ | / \ |
73
+ | _/ \_ |
74
+ | | | |
75
+ | | ДОМ | ← "А это где звук живет!" |
76
+ | |_______| |
77
+ | |
78
+ | * * * |
79
+ | \ | / ← "Дуем на свечки!" |
80
+ | \ | / |
81
+ | "Оооо" |
82
+ | |
83
+ | ♪ ♪ ♪ |
84
+ | "Пою гласные!" |
85
+ \_________________________________________________/ \
86
+ """,
87
+ r"""
88
+ _________________________________________________
89
+ / \
90
+ | РА РО РУ |
91
+ | ┌──┐ ┌──┐ ┌──┐ |
92
+ | │★ │ │★ │ │★ │ ← "Звездные слоги" |
93
+ | └──┘ └──┘ └──┘ |
94
+ | |
95
+ | .-. |
96
+ | ( О ) ← "Луна!" |
97
+ | `-' |
98
+ | / \ |
99
+ | / \ |
100
+ | "А-а-а-а!" (кричит в космос) |
101
+ | |
102
+ | ∞∞∞∞∞∞∞∞∞ |
103
+ | "Ракета летит!" |
104
+ \_________________________________________________/ \
105
+ """,
106
+ r"""
107
+ _________________________________________________
108
+ / \
109
+ | .-. .-. |
110
+ | ( Р ) ( Ш ) ← "Эти звуки дружат!" |
111
+ | `-' `-' |
112
+ | | | |
113
+ | / \ / \ |
114
+ | "Р-р-р" "Ш-ш-ш" |
115
+ | |
116
+ | O O O |
117
+ | \ | / ← Шарики для дыхания |
118
+ | \ | / |
119
+ | "Фууух!" |
120
+ | |
121
+ | ░░░░░░░░░░░░░░░ |
122
+ | ░ ░ ← "А это море!" |
123
+ | ░ ><((((°> ░ |
124
+ | ░ ░ |
125
+ | ░░░░░░░░░░░░░░░ |
126
+ \_________________________________________________/
127
+ """,
128
+ ]
app/main.py ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import OpenAI
4
+ import gradio as gr
5
+ import time
6
+ from datetime import datetime
7
+ import calendar
8
+ from docx import Document
9
+ import tempfile
10
+ import random # для случайного выбора цитаты
11
+ from app.quotes import quotes
12
+ import logging
13
+
14
+ # Настройка логирования
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format='%(asctime)s - %(levelname)s - %(message)s',
18
+ handlers=[logging.StreamHandler()]
19
+ ) # Вывод в консоль
20
+
21
+ # Загрузка переменных окружения
22
+ load_dotenv()
23
+ api_key = os.getenv("API_KEY_openai")
24
+ client = OpenAI(api_key=api_key)
25
+
26
+ # API_KEY = os.getenv("API_KEY")
27
+ # BASE_URL = os.getenv("BASE_URL")
28
+ # client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
29
+
30
+
31
+ # Функция генерации плана занятия
32
+ def generate_lesson_plan_interface(
33
+ нарушение, возраст_ребенка, особые_условия,
34
+ формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия,
35
+ инвентарь, наличие_ДЗ, разрешен_web_search, текущий_месяц=None
36
+ ):
37
+ # Логика работы с источниками - если есть, передаем
38
+ #источники = [web_sources] if разрешен_web_search and web_sources else []
39
+
40
+
41
+ # Логика определения месяца
42
+ if not текущий_месяц:
43
+ текущий_месяц = calendar.month_name[datetime.now().month]
44
+
45
+ # все параметры
46
+ params = {
47
+ "нарушение": нарушение,
48
+ "возраст_ребенка": возраст_ребенка,
49
+ "цель_занятия": цель_занятия,
50
+ "формат_занятия": формат_занятия,
51
+ "количество_детей": количество_детей,
52
+ "тема": тема,
53
+ "особые_условия": особые_условия,
54
+ "инвентарь": инвентарь,
55
+ "длительность_занятия": длительность_занятия,
56
+ "наличие_ДЗ": наличие_ДЗ,
57
+ "разрешен_web_search": разрешен_web_search,
58
+ #"источники": источники,
59
+ "текущий_месяц": текущий_месяц
60
+ }
61
+
62
+ if формат_занятия == "Индивидуальное":
63
+ params["количество_детей"] = 1
64
+
65
+ # Формируем основной промпт
66
+ base_prompt = f"""
67
+ Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями.
68
+ На основе параметров занятия составь план занятия, который должен включать:
69
+ 1. **Тема занятия, цель и задачи** (не более 3-х)
70
+ 2. **Необходимый инвентарь**
71
+ 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и примерами"""
72
+
73
+ # Добавляем секцию для веб-поиска только если разрешено
74
+ web_search_section = ""
75
+ if разрешен_web_search:
76
+ web_search_section = """
77
+ **ВНИМАНИЕ:** Используй актуальные данные из интернета:
78
+ - Найди примеры релевантных упражнений и интегрируй их в этапы
79
+ - При необходимости адаптируй упражнения по параметрам ребенка и занятия
80
+ - Для каждого заимствованного упражнения укажи источник (ссылку)"""
81
+
82
+ # Продолжение базового промпта
83
+ continuation = f"""
84
+ 4. **Домашнее задание** (только если параметр "наличие домашнего задания" = True)
85
+ 5. **Рекомендации по особым условиям** (если указаны)
86
+
87
+ Параметры занятия:
88
+ - **Основное нарушение:** {нарушение}
89
+ - **Возраст:** {возраст_ребенка}
90
+ - **Цель занятия:** {цель_занятия}
91
+ - **Тема:** {тема or "не указано - определи самостоятельно"}
92
+ - **Формат:** {формат_занятия} ({количество_детей} детей)
93
+ - **Инвентарь:** {инвентарь or "не указан - предложи варианты"}
94
+ - **Особые условия:** {особые_условия or "отсутствуют"}
95
+ - **Длительность:** {длительность_за��ятия} минут
96
+ - **Месяц года:** {текущий_месяц}
97
+
98
+ Стиль оформления:
99
+ - Четкая структура с нумерованными пунктами
100
+ - Минимум общих фраз, максимум конкретики
101
+ - Возрастосообразные примеры и материалы"""
102
+
103
+ # Собираем полный промпт
104
+ full_prompt = base_prompt + web_search_section + continuation
105
+
106
+ tools = []
107
+ tool_choice = None
108
+
109
+ if params['разрешен_web_search']:
110
+ tools.append({
111
+ "type": "web_search_preview",
112
+ "search_context_size": "medium",
113
+ "user_location": {"type": "approximate", "country": "RU"}
114
+ })
115
+ tool_choice = {"type": "web_search_preview"}
116
+
117
+ response = client.responses.create(
118
+ model="gpt-4o-mini",
119
+ input=full_prompt,
120
+ temperature=0.5,
121
+ tools=tools if tools else None,
122
+ tool_choice=tool_choice,
123
+ max_output_tokens=2000,
124
+ stream=True
125
+ )
126
+
127
+
128
+ ####### БЕЗ СТРИМИНГА
129
+ # return response.output_text # Основной вывод без изменений
130
+
131
+ # АННОТАЦИИ В ЛОГ
132
+ # if params['разрешен_web_search']: # Только логируем аннотации при веб-поиске
133
+ # try:
134
+ # content_block = response.output[1].content[0]
135
+ # # logging.info(f"=== ПОЛНЫЙ КОНТЕНТ БЛОКА ===")
136
+ # # logging.info(f"Тип: {content_block.type}")
137
+ # # logging.info(f"Текст: {content_block.text[:200]}...") # Первые 200 символов текста
138
+ # logging.info(f"Аннотации: {content_block.annotations}")
139
+ # # logging.info(f"Сырые данные: {vars(content_block)}") # Вся техническая информация
140
+ # except (IndexError, AttributeError) as e:
141
+ # logging.warning(f"Не удалось получить аннотации: {str(e)}")
142
+
143
+ ####### СТРИМИНГ
144
+ try:
145
+ for event in response:
146
+ if event.type == 'response.output_text.delta':
147
+ yield event.delta
148
+ elif event.type == 'response.completed':
149
+ break
150
+ except Exception as e:
151
+ yield f"Ошибка: {str(e)}"
152
+
153
+
154
+ ############# COMPLETIONS (РАБОТАЕТ БЕЗ TOOLS)
155
+ # response = client.chat.completions.create(
156
+ # model="gpt-4o-mini",
157
+ # messages=[
158
+ # {"role": "system", "content": "Ты — эксперт в области логопедии."},
159
+ # {"role": "user", "content": prompt}
160
+ # ],
161
+ # max_tokens=2000,
162
+ # stream=True # Включаем потоковый режим
163
+ # )
164
+
165
+ # for chunk in response:
166
+ # if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
167
+ # yield chunk.choices[0].delta.content
168
+
169
+
170
+
171
+ ### css привязывать именно так
172
+ css_path = os.path.join(os.path.dirname(__file__), "styles.css")
173
+ theme='earneleh/paris'
174
+ #, css_paths=css_path
175
+ # Интерфейс Gradio
176
+ with gr.Blocks(theme=theme) as demo:
177
+ gr.Markdown("## Логопедический конспект", elem_classes=["main-title"])
178
+ quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"])
179
+
180
+ with gr.Row():
181
+ # Первый блок настройки (Ребенок)
182
+ with gr.Column(elem_classes=["left-col"], scale=1):
183
+ gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"])
184
+ нарушение = gr.Textbox(label="Основное нарушение*", placeholder="Пример: Дислалия (свистящие), ОНР II уровня")
185
+ возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет")
186
+ особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС")
187
+
188
+ gr.Markdown("### 📄 Занятие", elem_classes=["block-title"])
189
+ формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное")
190
+ количество_детей = gr.Slider(
191
+ label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False
192
+ )
193
+ def toggle_group_slider(selected_format):
194
+ return gr.update(visible=(selected_format == "Групповое"))
195
+
196
+
197
+ формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей)
198
+
199
+ цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах")
200
+ тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт")
201
+ длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5)
202
+ инвентарь = gr.Textbox(label="Инвентарь (через запятую)",
203
+ placeholder="Пример: Зеркало, Карточки, Куклы")
204
+
205
+ дз = gr.Checkbox(label="Домашнее задание")
206
+ # Выделенный блок для профессиональных ресурсов
207
+ gr.Markdown("---") # Разделительная линия
208
+ # gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"])
209
+
210
+ with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок
211
+ web = gr.Checkbox(
212
+ label="📚 Поиск упражнений в логопедических базах",
213
+ info="Упражнения подбираются из проверенных источников со ссылками",
214
+ interactive=True
215
+ )
216
+
217
+ #web_sources = gr.Textbox(
218
+ # label="Уточнить источники (необязательно, через запятую):",
219
+ # placeholder="Например: Logopedy.ru, Maam.ru, ...",
220
+ # visible=False,
221
+ #)
222
+
223
+ # Логика отображения поля источников
224
+ #web.change(
225
+ # fn=lambda x: gr.update(visible=x),
226
+ # inputs=web,
227
+ # outputs=web_sources
228
+ #)
229
+
230
+ # Кнопка создания конспекта
231
+ btn = gr.Button("Создать конспект", variant="primary")
232
+
233
+ # Правая колонка — результат (output)
234
+ with gr.Column(elem_classes=["right-col"], scale=2): # Правая колонка — результат
235
+ gr.Markdown("### План урока", elem_classes=["block-title"])
236
+ output = gr.Markdown(
237
+ "Здесь появится план урока после генерации.",
238
+ elem_id="plan-output"
239
+ )
240
+
241
+ download_btn = gr.DownloadButton(
242
+ label="⬇️ Скачать .docx",
243
+ visible=False
244
+ )
245
+ output = gr.Markdown("") # Поле для вывода конспекта
246
+
247
+ # Ввод параметров
248
+ all_inputs = [
249
+ нарушение, возраст, особые_условия,
250
+ формат, количество_детей, цель, тема, длительность,
251
+ инвентарь, дз, web #web_sources
252
+ ]
253
+
254
+
255
+ # Функция для генерации docx файла
256
+ def generate_docx(text: str):
257
+ doc = Document()
258
+ for line in text.split("\n"):
259
+ doc.add_paragraph(line)
260
+ tmp_dir = tempfile.gettempdir()
261
+ file_path = os.path.join(tmp_dir, "Конспект_занятия.docx")
262
+ doc.save(file_path)
263
+ return file_path
264
+
265
+ ################### СТРИМИНГ
266
+ def on_submit_with_spinner(*args):
267
+ # Проверка обязательных полей (остается без изменений)
268
+ if not args[0] or not args[1] or not args[5]:
269
+ yield (
270
+ *[gr.update(interactive=True) for _ in all_inputs],
271
+ gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
272
+ gr.update(visible=False)
273
+ )
274
+ return
275
+
276
+ # Блокируем интерфейс
277
+ yield (
278
+ *[gr.update(interactive=False) for _ in all_inputs],
279
+ gr.update(value="⏳ Конспект создается..."),
280
+ gr.update(visible=False)
281
+ )
282
+
283
+ full_response = []
284
+ try:
285
+ for chunk in generate_lesson_plan_interface(*args):
286
+ full_response.append(chunk)
287
+ yield (
288
+ *[gr.update(interactive=False) for _ in all_inputs],
289
+ gr.update(value="".join(full_response)),
290
+ gr.update(visible=False)
291
+ )
292
+
293
+ # После завершения стрима
294
+ file_path = generate_docx("".join(full_response))
295
+ yield (
296
+ *[gr.update(interactive=True) for _ in all_inputs],
297
+ gr.update(value="".join(full_response)),
298
+ gr.update(visible=True, value=file_path)
299
+ )
300
+
301
+ except Exception as e:
302
+ yield (
303
+ *[gr.update(interactive=True) for _ in all_inputs],
304
+ gr.update(value=f"❌ Ошибка: {str(e)}"),
305
+ gr.update(visible=False)
306
+ )
307
+
308
+ ################## БЕЗ СТРИМИНГА
309
+ # def on_submit_with_spinner(*args):
310
+ # if not args[0] or not args[1] or not args[5]:
311
+ # return (
312
+ # *[gr.update(interactive=True) for _ in all_inputs],
313
+ # gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
314
+ # gr.update(visible=False)
315
+ # )
316
+
317
+ # Блокируем интерфейс
318
+ # try:
319
+ # response_text = generate_lesson_plan_interface(*args)
320
+ # file_path = generate_docx(response_text)
321
+ # return (
322
+ # *[gr.update(interactive=True) for _ in all_inputs],
323
+ # gr.update(value=response_text),
324
+ # gr.update(visible=True, value=file_path)
325
+ # )
326
+
327
+ # except Exception as e:
328
+ # return (
329
+ # *[gr.update(interactive=True) for _ in all_inputs],
330
+ # gr.update(value=f"❌ Ошибка: {str(e)}"),
331
+ # gr.update(visible=False)
332
+ # )
333
+
334
+
335
+ # Привязываем обработчики
336
+ btn.click(
337
+ fn=on_submit_with_spinner,
338
+ inputs=all_inputs,
339
+ outputs=[*all_inputs, output, download_btn] # <- скобку если не стримминг
340
+ ).then(
341
+ lambda: None,
342
+ inputs=[],
343
+ outputs=[]
344
+ )
345
+
346
+ if __name__ == "__main__":
347
+ demo.launch(share=True)
app/main_completions.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import OpenAI
4
+ import streamlit as st
5
+
6
+ # Загрузка переменных окружения
7
+ load_dotenv()
8
+ #api_key = os.getenv("API_KEY_openai")
9
+ API_KEY = os.getenv("API_KEY")
10
+ BASE_URL = os.getenv("BASE_URL")
11
+
12
+
13
+ def generate_lesson_plan(params):
14
+ # client = OpenAI(api_key=api_key)
15
+ client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
16
+ prompt = f"""
17
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры:
18
+ - **Основное нарушение:** {params['нарушение']}
19
+ - **Возраст ребенка:** {params['возраст_ребенка']}
20
+ - **Цель занятия:** {params['цель_занятия']}
21
+ - **Тематика:** {params['тематика']}
22
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
23
+ - **Инвентарь:** {params['инвентарь']}
24
+ - **Особые условия:** {params['особые_условия']}
25
+ - **Длительность занятия:** {params['длительность_занятия']} минут
26
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
27
+ - **Разрешен web search:** {params['разрешен_web_search']}
28
+
29
+ На основе этих параметров составь конспект занятия, который должен включать:
30
+ 1. **Тема занятия, цель и задачи**
31
+ 2. **Необходимые материалы**
32
+ 3. **Ход занятия**. Разбей занятие на логически последовательные этапы.
33
+ В каждый этап встрой конкретные упражнения с достаточным количеством примеров.
34
+ ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников.
35
+ Обязательно интегрируй ссылки на найденные источники в текст ответа.
36
+ 5. **Домашнее задание** (если параметр "наличие домашнего задания: True")
37
+ 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия")
38
+ """
39
+
40
+ # tools = []
41
+ # tool_choice = None
42
+
43
+ # if params['разрешен_web_search']:
44
+ # tools.append({
45
+ # "type": "web_search_preview",
46
+ # "search_context_size": "medium",
47
+ # "user_location": {
48
+ # "type": "approximate",
49
+ # "country": "RU"
50
+ # }
51
+ # })
52
+ # tool_choice = {"type": "web_search_preview"}
53
+
54
+ # response = client.responses.create(
55
+ # model="gpt-4o-mini",
56
+ # input=prompt,
57
+ # tools=tools if tools else None,
58
+ # tool_choice=tool_choice,
59
+ # max_output_tokens=2000
60
+ # )
61
+ # return response.output_text
62
+ #
63
+ response = client.chat.completions.create(
64
+ model="gpt-4o-mini",
65
+ messages=[
66
+ {"role": "system", "content": "Ты — эксперт в области логопедии."},
67
+ {"role": "user", "content": prompt}
68
+ ],
69
+ max_tokens=2000
70
+ )
71
+ return response.choices[0].message.content
72
+
73
+
74
+ # Интерфейс Streamlit
75
+ st.title("Генератор логопедических занятий")
76
+
77
+ # Инициализация флагов в session_state
78
+ if "loading" not in st.session_state:
79
+ st.session_state["loading"] = False
80
+ if "lesson_plan" not in st.session_state:
81
+ st.session_state["lesson_plan"] = ""
82
+
83
+ # Используем обычный блок в сайдбаре для динамических виджетов
84
+ with st.sidebar:
85
+ st.header("Настройки занятия")
86
+
87
+ нарушение = st.text_input(
88
+ "Основное нарушение*",
89
+ placeholder="Пример: Дислалия (свистящие), ОНР II уровня",
90
+ disabled=st.session_state["loading"]
91
+ )
92
+ возраст_ребенка = st.text_input(
93
+ "Возраст ребенка*",
94
+ placeholder="Пример: 5 лет, 6-7 лет",
95
+ disabled=st.session_state["loading"]
96
+ )
97
+ цель_занятия = st.text_input(
98
+ "Цель занятия*",
99
+ placeholder="Пример: Автоматизация звука [Р] в слогах",
100
+ disabled=st.session_state["loading"]
101
+ )
102
+
103
+ формат_занятия = st.radio(
104
+ "Формат занятия*",
105
+ options=["Индивидуальное", "Групповое"],
106
+ index=0,
107
+ horizontal=True,
108
+ disabled=st.session_state["loading"]
109
+ )
110
+
111
+ # Это поле появляется динамически, когда выбрано "Групповое"
112
+ if формат_занятия == "Групповое":
113
+ количество_детей = st.number_input(
114
+ "Количество детей в группе*",
115
+ min_value=2,
116
+ max_value=10,
117
+ value=3,
118
+ disabled=st.session_state["loading"]
119
+ )
120
+ else:
121
+ количество_детей = 1
122
+
123
+ тематика = st.text_input(
124
+ "Тематика занятия",
125
+ placeholder="Пример: Животные, Транспорт",
126
+ disabled=st.session_state["loading"]
127
+ )
128
+ особые_условия = st.text_input(
129
+ "Особые условия",
130
+ placeholder="Пример: гиперактивность, РАС",
131
+ disabled=st.session_state["loading"]
132
+ )
133
+
134
+ st.subheader("Инвентарь")
135
+ инвентарь_чекбоксы = {
136
+ "Зеркало": st.checkbox("Зеркало", disabled=st.session_state["loading"]),
137
+ "Карточки": st.checkbox("Карточки", disabled=st.session_state["loading"]),
138
+ "Игрушки": st.checkbox("Игрушки", disabled=st.session_state["loading"])
139
+ }
140
+ дополнительный_инвентарь = st.text_input("Другое", disabled=st.session_state["loading"])
141
+ инвентарь = ", ".join([k for k, v in инвентарь_чекбоксы.items() if v])
142
+ if дополнительный_инвентарь:
143
+ инвентарь += f", {дополнительный_инвентарь}"
144
+ инвентарь = инвентарь.strip(", ")
145
+
146
+ длительность_занятия = st.slider(
147
+ "Длительность (минут)",
148
+ min_value=15,
149
+ max_value=60,
150
+ value=30,
151
+ step=5,
152
+ disabled=st.session_state["loading"]
153
+ )
154
+ наличие_ДЗ = st.checkbox("Домашнее задание", disabled=st.session_state["loading"])
155
+ разрешен_web_search = st.checkbox("Поиск материалов в интернете", disabled=st.session_state["loading"])
156
+
157
+ # Кнопка отправки, блокируется если идет генерация
158
+ submit = st.button("Создать конспект", disabled=st.session_state["loading"], key="submit_btn")
159
+
160
+ # Если кнопка нажата, запускаем генерацию
161
+ if submit:
162
+ if not нарушение or not возраст_ребенка or not цель_занятия:
163
+ st.sidebar.error("Заполните обязательные поля!")
164
+ else:
165
+ st.session_state["loading"] = True # Блокируем виджеты
166
+ params = {
167
+ "нарушение": нарушение,
168
+ "возраст_ребенка": возраст_ребенка,
169
+ "цель_занятия": цель_занятия,
170
+ "тематика": тематика,
171
+ "формат_занятия": формат_занятия,
172
+ "количество_детей": количество_детей,
173
+ "инвентарь": инвентарь,
174
+ "особые_условия": особые_условия,
175
+ "длительность_занятия": длительность_занятия,
176
+ "наличие_ДЗ": наличие_ДЗ,
177
+ "разрешен_web_search": разрешен_web_search
178
+ }
179
+ with st.spinner("Создаем конспект..."):
180
+ try:
181
+ результат = generate_lesson_plan(params)
182
+ st.session_state["lesson_plan"] = результат
183
+ st.success("Готово!")
184
+ except Exception as e:
185
+ st.error(f"Ошибка: {e}")
186
+ finally:
187
+ st.session_state["loading"] = False
188
+
189
+ # Отображаем ранее сгенерированный конспект, если он есть и генерация не идёт
190
+ if st.session_state.get("lesson_plan", "") and not st.session_state["loading"]:
191
+ st.markdown(st.session_state["lesson_plan"])
app/main_gradio.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import OpenAI
4
+ import gradio as gr
5
+ import time
6
+
7
+ # Загрузка переменных окружения
8
+ load_dotenv()
9
+ API_KEY = os.getenv("API_KEY")
10
+ BASE_URL = os.getenv("BASE_URL")
11
+ #client = OpenAI(api_key=api_key)
12
+ client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
13
+
14
+
15
+ # Функция генерации плана занятия
16
+ def generate_lesson_plan_interface(
17
+ нарушение, возраст_ребенка, цель_занятия, формат_занятия,
18
+ количество_детей, тематика, особые_условия,
19
+ зеркало, карточки, игрушки, дополнительный_инвентарь,
20
+ длительность_занятия, наличие_ДЗ, разрешен_web_search
21
+ ):
22
+ инвентарь = []
23
+ if зеркало: инвентарь.append("Зеркало")
24
+ if карточки: инвентарь.append("Карточки")
25
+ if игрушки: инвентарь.append("Игрушки")
26
+ if дополнительный_инвентарь: инвентарь.append(дополнительный_инвентарь)
27
+ инвентарь = ", ".join(инвентарь)
28
+
29
+ params = {
30
+ "нарушение": нарушение,
31
+ "возраст_ребенка": возраст_ребенка,
32
+ "цель_занятия": цель_занятия,
33
+ "формат_занятия": формат_занятия,
34
+ "количество_детей": количество_детей,
35
+ "тематика": тематика,
36
+ "особые_условия": особые_условия,
37
+ "инвентарь": инвентарь,
38
+ "длительность_занятия": длительность_занятия,
39
+ "наличие_ДЗ": наличие_ДЗ,
40
+ "разрешен_web_search": разрешен_web_search
41
+ }
42
+
43
+ prompt = f"""
44
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры:
45
+ - **Основное нарушение:** {params['нарушение']}
46
+ - **Возраст ребенка:** {params['возраст_ребенка']}
47
+ - **Цель занятия:** {params['цель_занятия']}
48
+ - **Тематика:** {params['тематика']}
49
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
50
+ - **Инвентарь:** {params['инвентарь']}
51
+ - **Особые условия:** {params['особые_условия']}
52
+ - **Длительность занятия:** {params['длительность_занятия']} минут
53
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
54
+ - **Разрешен web search:** {params['разрешен_web_search']}
55
+
56
+ На основе этих параметров составь конспект занятия, который должен включать:
57
+ 1. **Тема занятия, цель и задачи**
58
+ 2. **Необходимые материалы**
59
+ 3. **Ход занятия**. Разбей занятие на логически последовательные этапы.
60
+ В каждый этап встрой конкретные упражнения с достаточным количеством примеров.
61
+ ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников.
62
+ Обязательно интегрируй ссылки на найденные источники в текст ответа.
63
+ 5. **Домашнее задание** (если параметр "наличие домашнего задания: True")
64
+ 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия")
65
+ """
66
+
67
+ # tools = []
68
+ # tool_choice = None
69
+
70
+ # if params['разрешен_web_search']:
71
+ # tools.append({
72
+ # "type": "web_search_preview",
73
+ # "search_context_size": "medium",
74
+ # "user_location": {
75
+ # "type": "approximate",
76
+ # "country": "RU"
77
+ # }
78
+ # })
79
+ # tool_choice = {"type": "web_search_preview"}
80
+
81
+ # response = client.responses.create(
82
+ # model="gpt-4o-mini",
83
+ # input=prompt,
84
+ # tools=tools if tools else None,
85
+ # tool_choice=tool_choice,
86
+ # max_output_tokens=2000
87
+ # )
88
+ # return response.output_text
89
+ #
90
+ response = client.chat.completions.create(
91
+ model="gpt-4o-mini",
92
+ messages=[
93
+ {"role": "system", "content": "Ты — эксперт в области логопедии."},
94
+ {"role": "user", "content": prompt}
95
+ ],
96
+ max_tokens=2000
97
+ )
98
+ return response.choices[0].message.content
99
+
100
+ # Интерфейс Gradio
101
+ with gr.Blocks() as demo:
102
+ gr.Markdown("## 🧠 Генератор логопедических занятий")
103
+
104
+ with gr.Row():
105
+ нарушение = gr.Textbox(label="Основное нарушение*", placeholder="Пример: Дислалия (свистящие), ОНР II уровня")
106
+ возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет")
107
+ цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах")
108
+
109
+ тематика = gr.Textbox(label="Тематика", placeholder="Пример: Животные, Транспорт")
110
+ особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС")
111
+
112
+ формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия*", value="Индивидуальное")
113
+ количество_детей = gr.Slider(
114
+ label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False
115
+ )
116
+
117
+ def toggle_group_slider(selected_format):
118
+ return gr.update(visible=(selected_format == "Групповое"))
119
+
120
+
121
+ формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей)
122
+
123
+ with gr.Group():
124
+ gr.Markdown("### Инвентарь")
125
+ зеркало = gr.Checkbox(label="Зеркало")
126
+ карточки = gr.Checkbox(label="Карточки")
127
+ игрушки = gr.Checkbox(label="Игрушки")
128
+ дополнительный = gr.Textbox(label="Дополнительно", placeholder="Пример: мячи, планшеты")
129
+
130
+ длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5)
131
+ дз = gr.Checkbox(label="Домашнее задание")
132
+ web = gr.Checkbox(label="Разрешить поиск в интернете")
133
+
134
+ btn = gr.Button("Создать конспект")
135
+ output = gr.Markdown()
136
+
137
+ def on_submit(*args):
138
+ return generate_lesson_plan_interface(*args)
139
+
140
+ # Элементы, которые будем блокировать во время генерации
141
+ all_inputs = [
142
+ нарушение, возраст, цель, формат, количество_детей,
143
+ тематика, особые_условия, зеркало, карточки, игрушки,
144
+ дополнительный, длительность, дз, web, btn
145
+ ]
146
+
147
+ def on_submit_with_spinner(
148
+ нарушение, возраст, цель, формат, количество_детей,
149
+ тематика, особые_условия, зеркало, карточки, игрушки,
150
+ дополнительный, длительность, дз, web
151
+ ):
152
+ # Блокируем все элементы и выводим "Генерация..."
153
+ for elem in all_inputs:
154
+ elem.interactive = False
155
+ yield gr.update(value="⏳ Генерация конспекта...")
156
+
157
+ # Генерация
158
+ result = generate_lesson_plan_interface(
159
+ нарушение, возраст, цель, формат, количество_детей,
160
+ тематика, особые_условия, зеркало, карточки, игрушки,
161
+ дополнительный, длительность, дз, web
162
+ )
163
+
164
+ # Выводим результат
165
+ yield gr.update(value=result)
166
+
167
+ # Разблокируем элементы
168
+ for elem in all_inputs:
169
+ elem.interactive = True
170
+
171
+ btn.click(
172
+ fn=on_submit_with_spinner,
173
+ inputs=[
174
+ нарушение, возраст, цель, формат, количество_детей,
175
+ тематика, особые_условия, зеркало, карточки, игрушки,
176
+ дополнительный, длительность, дз, web
177
+ ],
178
+ outputs=output,
179
+ show_progress=True
180
+ )
181
+
182
+ if __name__ == "__main__":
183
+ demo.launch()
app/main_gradio_nostream.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import OpenAI
4
+ import gradio as gr
5
+ import time
6
+ from datetime import datetime
7
+ import calendar
8
+ from docx import Document
9
+ import tempfile
10
+ import random # для случайного выбора цитаты
11
+ from app.quotes import quotes
12
+ import logging
13
+
14
+ # Настройка логирования
15
+ logging.basicConfig(
16
+ level=logging.INFO,
17
+ format='%(asctime)s - %(levelname)s - %(message)s',
18
+ handlers=[logging.StreamHandler()]
19
+ ) # Вывод в консоль
20
+
21
+ # Загрузка переменных окружения
22
+ load_dotenv()
23
+ api_key = os.getenv("API_KEY_openai")
24
+ client = OpenAI(api_key=api_key)
25
+
26
+ # API_KEY = os.getenv("API_KEY")
27
+ # BASE_URL = os.getenv("BASE_URL")
28
+ # client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
29
+
30
+
31
+ # Функция генерации плана занятия
32
+ def generate_lesson_plan_interface(
33
+ нарушение, возраст_ребенка, особые_условия,
34
+ формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия,
35
+ инвентарь, наличие_ДЗ, разрешен_web_search, текущий_месяц=None
36
+ ):
37
+ # Логика работы с источниками - если есть, передаем
38
+ #источники = [web_sources] if разрешен_web_search and web_sources else []
39
+
40
+
41
+ # Логика определения месяца
42
+ if not текущий_месяц:
43
+ текущий_месяц = calendar.month_name[datetime.now().month]
44
+
45
+ # все параметры
46
+ params = {
47
+ "нарушение": нарушение,
48
+ "возраст_ребенка": возраст_ребенка,
49
+ "цель_занятия": цель_занятия,
50
+ "формат_занятия": формат_занятия,
51
+ "количество_детей": количество_детей,
52
+ "тема": тема,
53
+ "особые_условия": особые_условия,
54
+ "инвентарь": инвентарь,
55
+ "длительность_занятия": длительность_занятия,
56
+ "наличие_ДЗ": наличие_ДЗ,
57
+ "разрешен_web_search": разрешен_web_search,
58
+ #"источники": источники,
59
+ "текущий_месяц": текущий_месяц
60
+ }
61
+
62
+ if формат_занятия == "Индивидуальное":
63
+ params["количество_детей"] = 1
64
+
65
+ # Формируем основной промпт
66
+ base_prompt = f"""
67
+ Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями.
68
+ На основе параметров занятия составь план занятия, который должен включать:
69
+ 1. **Тема занятия, цель и задачи** (не более 3-х)
70
+ 2. **Необходимый инвентарь**
71
+ 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и примерами"""
72
+
73
+ # Добавляем секцию для веб-поиска только если разрешено
74
+ web_search_section = ""
75
+ if разрешен_web_search:
76
+ web_search_section = """
77
+ **ВНИМАНИЕ:** Используй актуальные данные из интернета:
78
+ - Найди примеры релевантных упражнений и интегрируй их в этапы
79
+ - При необходимости адаптируй упражнения по параметрам ребенка и занятия
80
+ - Для каждого заимствованного упражнения укажи источник (ссылку)"""
81
+
82
+ # Продолжение базового промпта
83
+ continuation = f"""
84
+ 4. **Домашнее задание** (только если параметр "наличие домашнего задания" = True)
85
+ 5. **Рекомендации по особым условиям** (если указаны)
86
+
87
+ Параметры занятия:
88
+ - **Основное нарушение:** {нарушение}
89
+ - **Возраст:** {возраст_ребенка}
90
+ - **Цель занятия:** {цель_занятия}
91
+ - **Тема:** {тема or "не указано - определи самостоятельно"}
92
+ - **Формат:** {формат_занятия} ({количество_детей} детей)
93
+ - **Инвентарь:** {инвентарь or "не указан - предложи варианты"}
94
+ - **Особые условия:** {особые_условия or "отсутствуют"}
95
+ - **Длительность:** {длительность_за��ятия} минут
96
+ - **Месяц года:** {текущий_месяц}
97
+
98
+ Стиль оформления:
99
+ - Четкая структура с нумерованными пунктами
100
+ - Минимум общих фраз, максимум конкретики
101
+ - Возрастосообразные примеры и материалы"""
102
+
103
+ # Собираем полный промпт
104
+ full_prompt = base_prompt + web_search_section + continuation
105
+
106
+ tools = []
107
+ tool_choice = None
108
+
109
+ if params['разрешен_web_search']:
110
+ tools.append({
111
+ "type": "web_search_preview",
112
+ "search_context_size": "medium",
113
+ "user_location": {"type": "approximate", "country": "RU"}
114
+ })
115
+ tool_choice = {"type": "web_search_preview"}
116
+
117
+ response = client.responses.create(
118
+ model="gpt-4o-mini",
119
+ input=full_prompt,
120
+ temperature=0.5,
121
+ tools=tools if tools else None,
122
+ tool_choice=tool_choice,
123
+ max_output_tokens=2000,
124
+ stream=False
125
+ )
126
+
127
+
128
+ ####### БЕЗ СТРИМИНГА
129
+ return response.output_text # Основной вывод без изменений
130
+
131
+ # АННОТАЦИИ В ЛОГ
132
+ # if params['разрешен_web_search']: # Только логируем аннотации при веб-поиске
133
+ # try:
134
+ # content_block = response.output[1].content[0]
135
+ # # logging.info(f"=== ПОЛНЫЙ КОНТЕНТ БЛОКА ===")
136
+ # # logging.info(f"Тип: {content_block.type}")
137
+ # # logging.info(f"Текст: {content_block.text[:200]}...") # Первые 200 символов текста
138
+ # logging.info(f"Аннотации: {content_block.annotations}")
139
+ # # logging.info(f"Сырые данные: {vars(content_block)}") # Вся техническая информация
140
+ # except (IndexError, AttributeError) as e:
141
+ # logging.warning(f"Не удалось получить аннотации: {str(e)}")
142
+
143
+ ####### СТРИМИНГ
144
+ # try:
145
+ # for event in response:
146
+ # if event.type == 'response.output_text.delta':
147
+ # yield event.delta
148
+ # elif event.type == 'response.completed':
149
+ # break
150
+ # except Exception as e:
151
+ # yield f"Ошибка: {str(e)}"
152
+
153
+
154
+ ############# COMPLETIONS (РАБОТАЕТ БЕЗ TOOLS)
155
+ # response = client.chat.completions.create(
156
+ # model="gpt-4o-mini",
157
+ # messages=[
158
+ # {"role": "system", "content": "Ты — эксперт в области логопедии."},
159
+ # {"role": "user", "content": prompt}
160
+ # ],
161
+ # max_tokens=2000,
162
+ # stream=True # Включаем потоковый режим
163
+ # )
164
+
165
+ # for chunk in response:
166
+ # if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
167
+ # yield chunk.choices[0].delta.content
168
+
169
+ current_dir = os.path.dirname(__file__)
170
+ css_path = os.path.join(current_dir, "styles.css")
171
+
172
+ # Интерфейс Gradio
173
+ with gr.Blocks(css_paths=css_path) as demo:
174
+ gr.Markdown("## Логопедический конспект", elem_classes=["main-title"])
175
+ gr.HTML("<style>body { background-color: pink !important; }</style>")
176
+
177
+ with gr.Row():
178
+ # Первый блок настройки (Ребенок)
179
+ with gr.Column(scale=1):
180
+ quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"])
181
+
182
+ gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"])
183
+ нарушение = gr.Textbox(label="Основное нарушение*",
184
+ placeholder="Пример: Дислалия (свистящие), ОНР II уровня")
185
+ возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет")
186
+ особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС")
187
+
188
+ gr.Markdown("### 📄 Занятие", elem_classes=["block-title"])
189
+ формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное")
190
+ количество_детей = gr.Slider(
191
+ label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False
192
+ )
193
+
194
+
195
+ def toggle_group_slider(selected_format):
196
+ return gr.update(visible=(selected_format == "Групповое"))
197
+
198
+
199
+ формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей)
200
+
201
+ цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах")
202
+ тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт")
203
+ длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5)
204
+ инвентарь = gr.Textbox(label="Инвентарь (через запятую)",
205
+ placeholder="Пример: Зеркало, Карточки, Куклы")
206
+
207
+ дз = gr.Checkbox(label="Домашнее задание")
208
+ # Выделенный блок для профессиональных ресурсов
209
+ gr.Markdown("---") # Горизонтальная разделительная линия
210
+ # gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"])
211
+
212
+ with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок
213
+ web = gr.Checkbox(
214
+ label="📚 Поиск упражнений в логопедических базах",
215
+ info="Упражнения подбираются из проверенных источников со ссылками",
216
+ interactive=True
217
+ )
218
+
219
+ #web_sources = gr.Textbox(
220
+ # label="Уточнить источники (необязательно, через запятую):",
221
+ # placeholder="Например: Logopedy.ru, Maam.ru, ...",
222
+ # visible=False,
223
+ #)
224
+
225
+ # Логика отображения поля источников
226
+ #web.change(
227
+ # fn=lambda x: gr.update(visible=x),
228
+ # inputs=web,
229
+ # outputs=web_sources
230
+ #)
231
+
232
+ # Кнопка создания конспекта
233
+ btn = gr.Button("Создать конспект", variant="primary")
234
+
235
+ # Правая колонка — результат (output)
236
+ with gr.Column(scale=2): # Правая колонка — результат
237
+ download_btn = gr.DownloadButton(
238
+ label="⬇️ Скачать .docx",
239
+ visible=False
240
+ )
241
+ output = gr.Markdown("") # Поле для вывода конспекта
242
+
243
+ # Ввод параметров
244
+ all_inputs = [
245
+ нарушение, возраст, особые_условия,
246
+ формат, количество_детей, цель, тема, длительность,
247
+ инвентарь, дз, web #web_sources
248
+ ]
249
+
250
+
251
+ # Функция для генерации docx файла
252
+ def generate_docx(text: str):
253
+ doc = Document()
254
+ for line in text.split("\n"):
255
+ doc.add_paragraph(line)
256
+ tmp_dir = tempfile.gettempdir()
257
+ file_path = os.path.join(tmp_dir, "Конспект_занятия.docx")
258
+ doc.save(file_path)
259
+ return file_path
260
+
261
+ ################### СТРИМИНГ
262
+ # def on_submit_with_spinner(*args):
263
+ # # Проверка обязательных полей (остается без изменений)
264
+ # if not args[0] or not args[1] or not args[5]:
265
+ # yield (
266
+ # *[gr.update(interactive=True) for _ in all_inputs],
267
+ # gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
268
+ # gr.update(visible=False)
269
+ # )
270
+ # return
271
+
272
+ # Блокируем интерфейс
273
+ # yield (
274
+ # *[gr.update(interactive=False) for _ in all_inputs],
275
+ # gr.update(value="⏳ Конспект создается..."),
276
+ # gr.update(visible=False)
277
+ # )
278
+
279
+ # full_response = []
280
+ # try:
281
+ # for chunk in generate_lesson_plan_interface(*args):
282
+ # full_response.append(chunk)
283
+ # yield (
284
+ # *[gr.update(interactive=False) for _ in all_inputs],
285
+ # gr.update(value="".join(full_response)),
286
+ # gr.update(visible=False)
287
+ # )
288
+
289
+ # После завершения стрима
290
+ # file_path = generate_docx("".join(full_response))
291
+ # yield (
292
+ # *[gr.update(interactive=True) for _ in all_inputs],
293
+ # gr.update(value="".join(full_response)),
294
+ # gr.update(visible=True, value=file_path)
295
+ # )
296
+
297
+ # except Exception as e:
298
+ # yield (
299
+ # *[gr.update(interactive=True) for _ in all_inputs],
300
+ # gr.update(value=f"❌ Ошибка: {str(e)}"),
301
+ # gr.update(visible=False)
302
+ # )
303
+
304
+ ################## БЕЗ СТРИМИНГА
305
+ def on_submit_with_spinner(*args):
306
+ if not args[0] or not args[1] or not args[5]:
307
+ return (
308
+ *[gr.update(interactive=True) for _ in all_inputs],
309
+ gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
310
+ gr.update(visible=False)
311
+ )
312
+
313
+ # Блокируем интерфейс
314
+ try:
315
+ response_text = generate_lesson_plan_interface(*args)
316
+ file_path = generate_docx(response_text)
317
+ return (
318
+ *[gr.update(interactive=True) for _ in all_inputs],
319
+ gr.update(value=response_text),
320
+ gr.update(visible=True, value=file_path)
321
+ )
322
+
323
+ except Exception as e:
324
+ return (
325
+ *[gr.update(interactive=True) for _ in all_inputs],
326
+ gr.update(value=f"❌ Ошибка: {str(e)}"),
327
+ gr.update(visible=False)
328
+ )
329
+
330
+
331
+ # Привязываем обработчики
332
+ btn.click(
333
+ fn=on_submit_with_spinner,
334
+ inputs=all_inputs,
335
+ outputs=[*all_inputs, output, download_btn]) # <- скобку если не стримминг
336
+ # ).then(
337
+ # lambda: None,
338
+ # inputs=[],
339
+ # outputs=[]
340
+ # )
341
+
342
+ if __name__ == "__main__":
343
+ demo.launch(share=True)
app/main_gradio_test.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import OpenAI
4
+ import gradio as gr
5
+ import time
6
+ from datetime import datetime
7
+ import calendar
8
+ from docx import Document
9
+ import tempfile
10
+
11
+ import random
12
+ from app.quotes import quotes
13
+ from app.drawings import drawings
14
+ import logging
15
+
16
+ # Настройка логирования
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(levelname)s - %(message)s',
20
+ handlers=[logging.StreamHandler()]
21
+ ) # Вывод в консоль
22
+
23
+ # Загрузка переменных окружения
24
+ load_dotenv()
25
+ api_key = os.getenv("API_KEY_openai")
26
+ client = OpenAI(api_key=api_key)
27
+
28
+ # API_KEY = os.getenv("API_KEY")
29
+ # BASE_URL = os.getenv("BASE_URL")
30
+ # client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
31
+
32
+
33
+ # Функция генерации плана занятия
34
+ def generate_lesson_plan_interface(
35
+ нарушение, возраст_ребенка, особые_условия,
36
+ формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия,
37
+ инвентарь, наличие_ДЗ, разрешен_web_search, текущий_месяц=None
38
+ ):
39
+ # Логика работы с источниками - если есть, передаем
40
+ #источники = [web_sources] if разрешен_web_search and web_sources else []
41
+
42
+
43
+ # Логика определения месяца
44
+ if not текущий_месяц:
45
+ текущий_месяц = calendar.month_name[datetime.now().month]
46
+
47
+ # все параметры
48
+ params = {
49
+ "нарушение": нарушение,
50
+ "возраст_ребенка": возраст_ребенка,
51
+ "цель_занятия": цель_занятия,
52
+ "формат_занятия": формат_занятия,
53
+ "количество_детей": количество_детей,
54
+ "тема": тема,
55
+ "особые_условия": особые_условия,
56
+ "инвентарь": инвентарь,
57
+ "длительность_занятия": длительность_занятия,
58
+ "наличие_ДЗ": наличие_ДЗ,
59
+ "разрешен_web_search": разрешен_web_search,
60
+ #"источники": источники,
61
+ "текущий_месяц": текущий_месяц
62
+ }
63
+
64
+ if формат_занятия == "Индивидуальное":
65
+ количество_детей = 1 # Изменяем значение переменной
66
+ params["количество_детей"] = 1 # И обновляем словарь параметров
67
+
68
+ # Формируем основной промпт
69
+ base_prompt = f"""
70
+ Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями.
71
+ На основе параметров занятия составь план занятия, который должен включать:
72
+ 1. **Тема занятия, цель и задачи** (не более 3-х)
73
+ 2. **Необходимый инвентарь**
74
+ 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и примерами"""
75
+
76
+ # Добавляем секцию для веб-поиска только если разрешено
77
+ web_search_section = ""
78
+ if разрешен_web_search:
79
+ web_search_section = """
80
+ **ВНИМАНИЕ:** Используй актуальные данные из интернета:
81
+ - Найди примеры релевантных упражнений и интегрируй их в этапы
82
+ - При необходимости адаптируй упражнения по параметрам ребенка и занятия
83
+ - Для каждого заимствованного упражнения укажи источник (ссылку)"""
84
+
85
+ # Продолжение базового промпта
86
+ continuation = f"""
87
+ 4. **Домашнее задание** (только если параметр "наличие домашнего задания" = True)
88
+ 5. **Рекомендации по особым условиям** (если указаны)
89
+
90
+ Параметры занятия:
91
+ - **Основное нарушение:** {нарушение}
92
+ - **Возраст:** {возраст_ребенка}
93
+ - **Цель занятия:** {цель_занятия}
94
+ - **Тема:** {тема or "не указано - определи самостоятельно"}
95
+ - **Формат:** {формат_занятия} ({количество_детей} детей)
96
+ - **Инвентарь:** {инвентарь or "не указан - предложи варианты"}
97
+ - **Особые условия:** {особые_условия or "отсутствуют"}
98
+ - **Длительность:** {длительность_занятия} минут
99
+ - **Месяц года:** {текущий_месяц}
100
+
101
+ Стиль оформления:
102
+ - Четкая структура с нумерованными пунктами
103
+ - Минимум общих фраз, максимум конкретики
104
+ - Возрастосообразные примеры и материалы"""
105
+
106
+ # Собираем полный промпт
107
+ full_prompt = base_prompt + web_search_section + continuation
108
+
109
+ tools = []
110
+ tool_choice = None
111
+
112
+ if params['разрешен_web_search']:
113
+ tools.append({
114
+ "type": "web_search_preview",
115
+ "search_context_size": "medium",
116
+ "user_location": {"type": "approximate", "country": "RU"}
117
+ })
118
+ tool_choice = {"type": "web_search_preview"}
119
+
120
+ response = client.responses.create(
121
+ model="gpt-4o-mini",
122
+ input=full_prompt,
123
+ temperature=0.5,
124
+ tools=tools if tools else None,
125
+ tool_choice=tool_choice,
126
+ max_output_tokens=2000,
127
+ stream=True
128
+ )
129
+
130
+
131
+ ####### БЕЗ СТРИМИНГА
132
+ # return response.output_text # Основной вывод без изменений
133
+
134
+ # АННОТАЦИИ В ЛОГ
135
+ # if params['разрешен_web_search']: # Только логируем аннотации при веб-поиске
136
+ # try:
137
+ # content_block = response.output[1].content[0]
138
+ # # logging.info(f"=== ПОЛНЫЙ КОНТЕНТ БЛОКА ===")
139
+ # # logging.info(f"Тип: {content_block.type}")
140
+ # # logging.info(f"Текст: {content_block.text[:200]}...") # Первые 200 символов текста
141
+ # logging.info(f"Аннотации: {content_block.annotations}")
142
+ # # logging.info(f"Сырые данные: {vars(content_block)}") # Вся техническая информация
143
+ # except (IndexError, AttributeError) as e:
144
+ # logging.warning(f"Не удалось получить аннотации: {str(e)}")
145
+
146
+ ####### СТРИМИНГ
147
+ try:
148
+ for event in response:
149
+ if event.type == 'response.output_text.delta':
150
+ yield event.delta
151
+ elif event.type == 'response.completed':
152
+ break
153
+ except Exception as e:
154
+ yield f"Ошибка: {str(e)}"
155
+
156
+
157
+ ############# COMPLETIONS (РАБОТАЕТ БЕЗ TOOLS)
158
+ # response = client.chat.completions.create(
159
+ # model="gpt-4o-mini",
160
+ # messages=[
161
+ # {"role": "system", "content": "Ты — эксперт в области логопедии."},
162
+ # {"role": "user", "content": prompt}
163
+ # ],
164
+ # max_tokens=2000,
165
+ # stream=True # Включаем потоковый режим
166
+ # )
167
+
168
+ # for chunk in response:
169
+ # if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
170
+ # yield chunk.choices[0].delta.content
171
+
172
+
173
+ # Случайный рисунок в блокноте
174
+ drawing = random.choice(drawings)
175
+ # Текст с подсказкой и рисунком в блокноте
176
+ hint_text = f"""Здесь появится план занятия — заполните вводные и нажмите кнопку **Создать конспект**
177
+
178
+ <pre>
179
+ {drawing}
180
+ </pre>
181
+ """
182
+ ### css привязывать именно так
183
+
184
+ #theme='earneleh/paris'
185
+
186
+
187
+
188
+ theme = gr.themes.Base(
189
+ secondary_hue="rose",
190
+ neutral_hue="stone",
191
+ ).set(
192
+ body_background_fill='*primary_50',
193
+ body_text_color='*primary_900',
194
+ body_text_size='*text_lg',
195
+ body_text_color_subdued='*primary_700',
196
+ body_text_weight='600',
197
+ )
198
+
199
+ css_path = os.path.join(os.path.dirname(__file__), "styles.css")
200
+ #
201
+ # Интерфейс Gradio
202
+ with gr.Blocks(theme=theme, css_paths=css_path) as demo:
203
+ gr.Markdown("# Логопедический конспект", elem_classes=["main-title"])
204
+ quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"])
205
+
206
+ with gr.Row():
207
+ # Левый блок (все поля)
208
+ with gr.Column(elem_classes=["left-col"], scale=1):
209
+ # Блок 1: Ребёнок (заголовок + поля)
210
+ with gr.Column(variant="panel"): # <<< Обертка для первого блока
211
+ gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"])
212
+ нарушение = gr.Textbox(label="Основное нарушение*", placeholder="Пример: Дислалия (свистящие), ОНР II уровня")
213
+ возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет")
214
+ особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС")
215
+
216
+
217
+
218
+ # Блок 2: Занятие (заголовок + поля)
219
+ with gr.Column(variant="panel"): # <<< Обертка для второго блока
220
+ gr.Markdown("### 📄 Занятие", elem_classes=["block-title"])
221
+ формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное")
222
+ количество_детей = gr.Slider(label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False)
223
+ def toggle_group_slider(selected_format):
224
+ if selected_format == "Индивидуальное":
225
+ return gr.update(visible=False, value=1) # Скрыть слайдер и установить значение 1
226
+ else:
227
+ return gr.update(visible=True, value=2) # Показать слайдер и установить значение 2
228
+ # Привязываем изменение формата к функции
229
+ формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей)
230
+
231
+ цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах")
232
+ тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт")
233
+ длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5)
234
+ инвентарь = gr.Textbox(label="Инвентарь (через запятую)", placeholder="Пример: Зеркало, Карточки, Куклы")
235
+ дз = gr.Checkbox(label="Домашнее задание")
236
+ # Выделенный блок для профессиональных ресурсов
237
+ gr.Markdown("---") # Разделительная линия
238
+ # gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"])
239
+
240
+ with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок
241
+ web = gr.Checkbox(
242
+ label="📚 Поиск упражнений в логопедических базах",
243
+ info="Упражнения подбираются из проверенных источников со ссылками",
244
+ interactive=True
245
+ )
246
+
247
+ #web_sources = gr.Textbox(
248
+ # label="Уточнить источники (необязательно, через запятую):",
249
+ # placeholder="Например: Logopedy.ru, Maam.ru, ...",
250
+ # visible=False,
251
+ #)
252
+
253
+ # Логика отображения поля источников
254
+ #web.change(
255
+ # fn=lambda x: gr.update(visible=x),
256
+ # inputs=web,
257
+ # outputs=web_sources
258
+ #)
259
+
260
+ # Кнопка создания конспекта
261
+ btn = gr.Button("Создать конспект", variant="primary")
262
+
263
+ # Правая колонка — результат (output)
264
+ with gr.Column(elem_classes=["right-col"], scale=2):
265
+ # Общий блок-панель для правой колонки
266
+ with gr.Column(variant="panel"): # <<< Главная панель
267
+ gr.Markdown("### Конспект", elem_classes=["block-title"]) # Заголовок ВНУТРИ панели
268
+ # Блок с выводом конспекта
269
+ output = gr.Markdown(
270
+ hint_text,
271
+ elem_id="plan-output"
272
+ )
273
+ # Кнопка скачивания (оставляем внутри панели)
274
+ download_btn = gr.DownloadButton(
275
+ label="⬇️ Скачать .docx",
276
+ visible=False
277
+ )
278
+
279
+ # Ввод параметров
280
+ all_inputs = [
281
+ нарушение, возраст, особые_условия,
282
+ формат, количество_детей, цель, тема, длительность,
283
+ инвентарь, дз, web #web_sources
284
+ ]
285
+
286
+
287
+ # Функция для генерации docx файла
288
+ def generate_docx(text: str):
289
+ doc = Document()
290
+ for line in text.split("\n"):
291
+ doc.add_paragraph(line)
292
+ tmp_dir = tempfile.gettempdir()
293
+ file_path = os.path.join(tmp_dir, "Конспект_занятия.docx")
294
+ doc.save(file_path)
295
+ return file_path
296
+
297
+ ################### СТРИМИНГ
298
+ def on_submit_with_spinner(*args):
299
+ # Проверка обязательных полей (остается без изменений)
300
+ if not args[0] or not args[1] or not args[5]:
301
+ yield (
302
+ *[gr.update(interactive=True) for _ in all_inputs],
303
+ gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
304
+ gr.update(visible=False)
305
+ )
306
+ return
307
+
308
+ # Блокируем интерфейс
309
+ yield (
310
+ *[gr.update(interactive=False) for _ in all_inputs],
311
+ gr.update(value="⏳ Конспект создается..."),
312
+ gr.update(visible=False)
313
+ )
314
+
315
+ full_response = []
316
+ try:
317
+ for chunk in generate_lesson_plan_interface(*args):
318
+ full_response.append(chunk)
319
+ yield (
320
+ *[gr.update(interactive=False) for _ in all_inputs],
321
+ gr.update(value="".join(full_response)),
322
+ gr.update(visible=False)
323
+ )
324
+
325
+ # После завершения стрима
326
+ file_path = generate_docx("".join(full_response))
327
+ yield (
328
+ *[gr.update(interactive=True) for _ in all_inputs],
329
+ gr.update(value="".join(full_response)),
330
+ gr.update(visible=True, value=file_path)
331
+ )
332
+
333
+ except Exception as e:
334
+ yield (
335
+ *[gr.update(interactive=True) for _ in all_inputs],
336
+ gr.update(value=f"❌ Ошибка: {str(e)}"),
337
+ gr.update(visible=False)
338
+ )
339
+
340
+ ################## БЕЗ СТРИМИНГА
341
+ # def on_submit_with_spinner(*args):
342
+ # if not args[0] or not args[1] or not args[5]:
343
+ # return (
344
+ # *[gr.update(interactive=True) for _ in all_inputs],
345
+ # gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
346
+ # gr.update(visible=False)
347
+ # )
348
+
349
+ # Блокируем интерфейс
350
+ # try:
351
+ # response_text = generate_lesson_plan_interface(*args)
352
+ # file_path = generate_docx(response_text)
353
+ # return (
354
+ # *[gr.update(interactive=True) for _ in all_inputs],
355
+ # gr.update(value=response_text),
356
+ # gr.update(visible=True, value=file_path)
357
+ # )
358
+
359
+ # except Exception as e:
360
+ # return (
361
+ # *[gr.update(interactive=True) for _ in all_inputs],
362
+ # gr.update(value=f"❌ Ошибка: {str(e)}"),
363
+ # gr.update(visible=False)
364
+ # )
365
+
366
+
367
+ # Привязываем обработчики
368
+ btn.click(
369
+ fn=on_submit_with_spinner,
370
+ inputs=all_inputs,
371
+ outputs=[*all_inputs, output, download_btn] # <- скобку если не стримминг
372
+ ).then(
373
+ lambda: None,
374
+ inputs=[],
375
+ outputs=[]
376
+ )
377
+
378
+ if __name__ == "__main__":
379
+ demo.launch(share=True)
app/main_gradio_test_FS.py ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import OpenAI
4
+ import gradio as gr
5
+ import time
6
+ from datetime import datetime
7
+ import calendar
8
+ from docx import Document
9
+ import tempfile
10
+
11
+ import random
12
+ from app.quotes import quotes
13
+ from app.drawings import drawings
14
+ import logging
15
+
16
+ # Настройка логирования
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(levelname)s - %(message)s',
20
+ handlers=[logging.StreamHandler()] # Вывод в консоль
21
+ )
22
+
23
+ # Загрузка переменных окружения
24
+ load_dotenv()
25
+ api_key = os.getenv("API_KEY_openai")
26
+ client = OpenAI(api_key=api_key)
27
+ VS_ID = os.getenv("VECTOR_STORE_ID")
28
+ # API_KEY = os.getenv("API_KEY")
29
+ # BASE_URL = os.getenv("BASE_URL")
30
+ # client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
31
+
32
+
33
+ # Функция генерации плана занятия
34
+ def generate_lesson_plan_interface(
35
+ нарушение, возраст_ребенка, особые_условия,
36
+ формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия,
37
+ инвентарь, наличие_ДЗ, разрешен_file_search, текущий_месяц=None
38
+ ):
39
+ # Логика работы с источниками - если есть, передаем
40
+ #источники = [web_sources] if разрешен_web_search and web_sources else []
41
+
42
+
43
+ # Логика определения месяца
44
+ if not текущий_месяц:
45
+ текущий_месяц = calendar.month_name[datetime.now().month]
46
+
47
+ # все параметры
48
+ params = {
49
+ "нарушение": нарушение,
50
+ "возраст_ребенка": возраст_ребенка,
51
+ "цель_занятия": цель_занятия,
52
+ "формат_занятия": формат_занятия,
53
+ "количество_детей": количество_детей,
54
+ "тема": тема,
55
+ "особые_условия": особые_условия,
56
+ "инвентарь": инвентарь,
57
+ "длительность_занятия": длительность_занятия,
58
+ "наличие_ДЗ": наличие_ДЗ,
59
+ "разрешен_file_search": разрешен_file_search,
60
+ #"источники": источники,
61
+ "текущий_месяц": текущий_месяц
62
+ }
63
+
64
+ if формат_занятия == "Индивидуальное":
65
+ количество_детей = 1 # Изменяем значение переменной
66
+ params["количество_детей"] = 1 # И обновляем словарь параметров
67
+
68
+ # Формируем основной промпт
69
+ base_prompt = f"""
70
+ Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями.
71
+ На основе параметров занятия составь план занятия, который должен включать:
72
+ 1. **Тема занятия, цель и задачи** (не более 3-х)
73
+ 2. **Необходимый инвентарь**
74
+ 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и примерами"""
75
+
76
+
77
+ file_search_section = ""
78
+ if разрешен_file_search:
79
+ file_search_section = """
80
+ **Используй данные из базы:
81
+ - Найди примеры релевантных упражнений и интегрируй их в этапы
82
+ - При необходимости адаптируй упражнения по параметрам ребенка и занятия
83
+ - Укажи ссылки на источники
84
+ """
85
+
86
+ # Продолжение базового промпта
87
+ continuation = f"""
88
+ 4. **Домашнее задание** (только если параметр "наличие домашнего задания" = True)
89
+ 5. **Рекомендации по особым условиям** (только если указаны)
90
+
91
+ Параметры занятия:
92
+ - **Основное нарушение:** {нарушение}
93
+ - **Возраст:** {возраст_ребенка}
94
+ - **Цель занятия:** {цель_занятия}
95
+ - **Тема:** {тема or "не указано - определи самостоятельно"}
96
+ - **Формат:** {формат_занятия} ({количество_детей} детей)
97
+ - **Инвентарь:** {инвентарь or "не указан - предложи варианты"}
98
+ - **Особые условия:** {особые_условия or "отсутствуют"}
99
+ - **Длительность:** {длительность_занятия} ми��ут
100
+ - **Месяц года:** {текущий_месяц}
101
+
102
+ Стиль оформления:
103
+ - Четкая структура с нумерованными пунктами
104
+ - Минимум общих фраз, максимум конкретики
105
+ - Возрастосообразные примеры и материалы"""
106
+
107
+ # Собираем полный промпт
108
+ full_prompt = base_prompt + file_search_section + continuation
109
+
110
+ tools = []
111
+ tool_choice = None
112
+ #FILE SEARCH
113
+ if params['разрешен_file_search']:
114
+ tools.append({
115
+ "type": "file_search",
116
+ "vector_store_ids": [VS_ID],
117
+ "max_num_results": 5
118
+ })
119
+ #WEB SEARCH
120
+ # if params['разрешен_web_search']:
121
+ # tools.append({
122
+ # "type": "web_search_preview",
123
+ # "search_context_size": "medium",
124
+ # "user_location": {"type": "approximate", "country": "RU"}
125
+ # })
126
+ # tool_choice = {"type": "web_search_preview"}
127
+
128
+ response = client.responses.create(
129
+ model=os.getenv("MODEL"),
130
+ input=full_prompt,
131
+ temperature=0.7,
132
+ tools=tools if tools else None,
133
+ tool_choice=tool_choice,
134
+ max_output_tokens=2000,
135
+ stream=True
136
+ )
137
+
138
+
139
+ ####### БЕЗ СТРИМИНГА
140
+ # return response.output_text # Основной вывод без изменений
141
+
142
+ # АННОТАЦИИ В ЛОГ
143
+ # if params['разрешен_web_search']: # Только логируем аннотации при веб-поиске
144
+ # try:
145
+ # content_block = response.output[1].content[0]
146
+ # # logging.info(f"=== ПОЛНЫЙ КОНТЕНТ БЛОКА ===")
147
+ # # logging.info(f"Тип: {content_block.type}")
148
+ # # logging.info(f"Текст: {content_block.text[:200]}...") # Первые 200 символов текста
149
+ # logging.info(f"Аннотации: {content_block.annotations}")
150
+ # # logging.info(f"Сырые данные: {vars(content_block)}") # Вся техническая информация
151
+ # except (IndexError, AttributeError) as e:
152
+ # logging.warning(f"Не удалось получить аннотации: {str(e)}")
153
+
154
+ ####### СТРИМИНГ
155
+ try:
156
+ for event in response:
157
+ if event.type == 'response.output_text.delta':
158
+ yield event.delta
159
+ elif event.type == 'response.completed':
160
+ break
161
+ except Exception as e:
162
+ yield f"Ошибка: {str(e)}"
163
+
164
+
165
+ ############# COMPLETIONS (РАБОТАЕТ БЕЗ TOOLS)
166
+ # response = client.chat.completions.create(
167
+ # model="gpt-4o-mini",
168
+ # messages=[
169
+ # {"role": "system", "content": "Ты — эксперт в области логопедии."},
170
+ # {"role": "user", "content": prompt}
171
+ # ],
172
+ # max_tokens=2000,
173
+ # stream=True # Включаем потоковый режим
174
+ # )
175
+
176
+ # for chunk in response:
177
+ # if chunk.choices and chunk.choices[0].delta and chunk.choices[0].delta.content:
178
+ # yield chunk.choices[0].delta.content
179
+
180
+
181
+ # Случайный рисунок в блокноте
182
+ drawing = random.choice(drawings)
183
+ # Текст с подсказкой и рисунком в блокноте
184
+ hint_text = f"""Здесь появится план занятия — заполните вводные и нажмите кнопку **Создать конспект**
185
+
186
+ <pre>
187
+ {drawing}
188
+ </pre>
189
+ """
190
+
191
+ ########## ИНТЕРФЕЙС
192
+ #ТЕМА И СТИЛИ
193
+
194
+ ### css привязывать именно так
195
+ #theme='earneleh/paris'
196
+ theme = gr.themes.Base(
197
+ secondary_hue="rose",
198
+ neutral_hue="stone",
199
+ ).set(
200
+ body_background_fill='*primary_50',
201
+ body_text_color='*primary_900',
202
+ body_text_size='*text_lg',
203
+ body_text_color_subdued='*primary_700',
204
+ body_text_weight='600',
205
+ )
206
+ css_path = os.path.join(os.path.dirname(__file__), "styles.css")
207
+
208
+ # ИНТЕРФЕЙС
209
+ with gr.Blocks(theme=theme, css_paths=css_path) as demo:
210
+ gr.Markdown("# Логопедический конспект", elem_classes=["main-title"])
211
+ quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"])
212
+
213
+ with gr.Row():
214
+ # Левый блок (все поля)
215
+ with gr.Column(elem_classes=["left-col"], scale=1):
216
+ # Блок 1: Ребёнок (заголовок + поля)
217
+ with gr.Column(variant="panel"): # <<< Обертка для первого блока
218
+ gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"])
219
+ нарушение = gr.Textbox(label="Основное нарушение*", placeholder="Пример: Дислалия (свистящие), ОНР II уровня")
220
+ возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет")
221
+ особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС")
222
+
223
+
224
+
225
+ # Блок 2: Занятие (заголовок + поля)
226
+ with gr.Column(variant="panel"): # <<< Обертка для второго блока
227
+ gr.Markdown("### 📄 Занятие", elem_classes=["block-title"])
228
+ формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное")
229
+ количество_детей = gr.Slider(label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False)
230
+ def toggle_group_slider(selected_format):
231
+ if selected_format == "Индивидуальное":
232
+ return gr.update(visible=False, value=1) # Скрыть слайдер и установить значение 1
233
+ else:
234
+ return gr.update(visible=True, value=2) # Показать слайдер и установить значение 2
235
+ # Привязываем изменение формата к функции
236
+ формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей)
237
+
238
+ цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах")
239
+ тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт")
240
+ длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5)
241
+ инвентарь = gr.Textbox(label="Инвентарь (через запятую)", placeholder="Пример: Зеркало, Карточки, Куклы")
242
+ дз = gr.Checkbox(label="Домашнее задание")
243
+ # Выделенный блок для профессиональных ресурсов
244
+ gr.Markdown("---") # Разделительная линия
245
+ # gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"])
246
+
247
+ with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок
248
+ file_search = gr.Checkbox(
249
+ label="📚 Поиск упражнений в логопедических базах",
250
+ info="Упражнения подбираются из проверенных источников со ссылками (FS)",
251
+ interactive=True
252
+ )
253
+
254
+ #web_sources = gr.Textbox(
255
+ # label="Уточнить источники (необязательно, через запятую):",
256
+ # placeholder="Например: Logopedy.ru, Maam.ru, ...",
257
+ # visible=False,
258
+ #)
259
+
260
+ # Логика отображения поля источников
261
+ #web.change(
262
+ # fn=lambda x: gr.update(visible=x),
263
+ # inputs=web,
264
+ # outputs=web_sources
265
+ #)
266
+
267
+ # Кнопка создания конспекта
268
+ btn = gr.Button("Создать конспект", variant="primary")
269
+
270
+ # Правая колонка — результат (output)
271
+ with gr.Column(elem_classes=["right-col"], scale=2):
272
+ # Общий блок-панель для правой колонки
273
+ with gr.Column(variant="panel"): # <<< Главная панель
274
+ gr.Markdown("### Конспект", elem_classes=["block-title"]) # Заголовок ВНУТРИ панели
275
+ # Блок с выводом конспекта
276
+ output = gr.Markdown(
277
+ hint_text,
278
+ elem_id="plan-output"
279
+ )
280
+ # Кнопка скачивания (оставляем внутри панели)
281
+ download_btn = gr.DownloadButton(
282
+ label="⬇️ Скачать .docx",
283
+ visible=False
284
+ )
285
+
286
+ # Ввод параметров
287
+ all_inputs = [
288
+ нарушение, возраст, особые_условия,
289
+ формат, количество_детей, цель, тема, длительность,
290
+ инвентарь, дз, file_search #web_sources
291
+ ]
292
+
293
+
294
+ # Функция для генерации docx файла
295
+ def generate_docx(text: str):
296
+ doc = Document()
297
+ for line in text.split("\n"):
298
+ doc.add_paragraph(line)
299
+ tmp_dir = tempfile.gettempdir()
300
+ file_path = os.path.join(tmp_dir, "Конспект_занятия.docx")
301
+ doc.save(file_path)
302
+ return file_path
303
+
304
+ ################### СТРИМИНГ
305
+ def on_submit_with_spinner(*args):
306
+ # Проверка обязательных полей (остается без изменений)
307
+ if not args[0] or not args[1] or not args[5]:
308
+ yield (
309
+ *[gr.update(interactive=True) for _ in all_inputs],
310
+ gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
311
+ gr.update(visible=False)
312
+ )
313
+ return
314
+
315
+ # Блокируем интерфейс
316
+ yield (
317
+ *[gr.update(interactive=False) for _ in all_inputs],
318
+ gr.update(value="⏳ Конспект создается..."),
319
+ gr.update(visible=False)
320
+ )
321
+
322
+ full_response = []
323
+ try:
324
+ for chunk in generate_lesson_plan_interface(*args):
325
+ full_response.append(chunk)
326
+ yield (
327
+ *[gr.update(interactive=False) for _ in all_inputs],
328
+ gr.update(value="".join(full_response)),
329
+ gr.update(visible=False)
330
+ )
331
+
332
+ # После завершения стрима
333
+ file_path = generate_docx("".join(full_response))
334
+ yield (
335
+ *[gr.update(interactive=True) for _ in all_inputs],
336
+ gr.update(value="".join(full_response)),
337
+ gr.update(visible=True, value=file_path)
338
+ )
339
+
340
+ except Exception as e:
341
+ yield (
342
+ *[gr.update(interactive=True) for _ in all_inputs],
343
+ gr.update(value=f"❌ Ошибка: {str(e)}"),
344
+ gr.update(visible=False)
345
+ )
346
+
347
+ ################## БЕЗ СТРИМИНГА
348
+ # def on_submit_with_spinner(*args):
349
+ # if not args[0] or not args[1] or not args[5]:
350
+ # return (
351
+ # *[gr.update(interactive=True) for _ in all_inputs],
352
+ # gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
353
+ # gr.update(visible=False)
354
+ # )
355
+
356
+ # Блокируем интерфейс
357
+ # try:
358
+ # response_text = generate_lesson_plan_interface(*args)
359
+ # file_path = generate_docx(response_text)
360
+ # return (
361
+ # *[gr.update(interactive=True) for _ in all_inputs],
362
+ # gr.update(value=response_text),
363
+ # gr.update(visible=True, value=file_path)
364
+ # )
365
+
366
+ # except Exception as e:
367
+ # return (
368
+ # *[gr.update(interactive=True) for _ in all_inputs],
369
+ # gr.update(value=f"❌ Ошибка: {str(e)}"),
370
+ # gr.update(visible=False)
371
+ # )
372
+
373
+
374
+ # Привязываем обработчики
375
+ btn.click(
376
+ fn=on_submit_with_spinner,
377
+ inputs=all_inputs,
378
+ outputs=[*all_inputs, output, download_btn] # <- скобку если не стримминг
379
+ ).then(
380
+ lambda: None,
381
+ inputs=[],
382
+ outputs=[]
383
+ )
384
+
385
+ if __name__ == "__main__":
386
+ demo.launch()
app/misc/.gradio/certificate.pem ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3
+ TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4
+ cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5
+ WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6
+ ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7
+ MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8
+ h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9
+ 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10
+ A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11
+ T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12
+ B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13
+ B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14
+ KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15
+ OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16
+ jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17
+ qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18
+ rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19
+ HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20
+ hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21
+ ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22
+ 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23
+ NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24
+ ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25
+ TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26
+ jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27
+ oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28
+ 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29
+ mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30
+ emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31
+ -----END CERTIFICATE-----
app/misc/Stable.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import asyncio
3
+ from dotenv import load_dotenv
4
+ from openai import OpenAI
5
+ import streamlit as st
6
+
7
+ # Загрузка переменных окружения
8
+ load_dotenv()
9
+ api_key = os.getenv("API_KEY_openai")
10
+
11
+
12
+ def generate_lesson_plan(params):
13
+ client = OpenAI(api_key=api_key)
14
+
15
+ prompt = f"""
16
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры:
17
+ - **Основное нарушение:** {params['нарушение']}
18
+ - **Возраст ребенка:** {params['возраст_ребенка']}
19
+ - **Цель занятия:** {params['цель_занятия']}
20
+ - **Тематика:** {params['тематика']}
21
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
22
+ - **Инвентарь:** {params['инвентарь']}
23
+ - **Особые условия:** {params['особые_условия']}
24
+ - **Длительность занятия:** {params['длительность_занятия']} минут
25
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
26
+ - **Разрешен web search:** {params['разрешен_web_search']}
27
+
28
+ На основе этих параметров составь конспект занятия, который должен включать:
29
+ 1. **Тема занятия, цель и задачи**
30
+ 2. **Необходимые материалы**
31
+ 3. **Ход занятия**. Разбей занятие на логически последовательные этапы.
32
+ В каждый этап встрой конкретные упражнения с достаточным количеством примеров.
33
+ ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников.
34
+ Обязательно интегрируй ссылки на найденные источники в текст ответа.
35
+ 5. **Домашнее задание** (если параметр "наличие домашнего задания: True")
36
+ 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия")
37
+ """
38
+
39
+ tools = []
40
+ tool_choice = None
41
+
42
+ if params['разрешен_web_search']:
43
+ tools.append({
44
+ "type": "web_search_preview",
45
+ "search_context_size": "medium",
46
+ "user_location": {
47
+ "type": "approximate",
48
+ "country": "RU"
49
+ }
50
+ })
51
+ tool_choice = {"type": "web_search_preview"}
52
+
53
+ response = client.responses.create(
54
+ model="gpt-4o-mini",
55
+ input=prompt,
56
+ tools=tools if tools else None,
57
+ tool_choice=tool_choice,
58
+ max_output_tokens=2000
59
+ )
60
+ return response.output_text
61
+
62
+
63
+ # Интерфейс Streamlit
64
+ st.title("Генератор логопедических занятий")
65
+
66
+ # Левая часть с настройками
67
+ with st.sidebar:
68
+ st.header("Настройки занятия")
69
+
70
+ # Обязательные поля
71
+ нарушение = st.text_input(
72
+ "Основное нарушение*",
73
+ placeholder="Пример: Дислалия (свистящие), ОНР II уровня"
74
+ )
75
+
76
+ возраст_ребенка = st.text_input(
77
+ "Возраст ребенка*",
78
+ placeholder="Пример: 5 лет, 6-7 лет"
79
+ )
80
+
81
+ цель_занятия = st.text_input(
82
+ "Цель занятия*",
83
+ placeholder="Пример: Автоматизация звука [Р] в слогах"
84
+ )
85
+
86
+ # Переключатель формата занятия
87
+ формат_занятия = st.radio(
88
+ "Формат занятия*",
89
+ options=["Индивидуальное", "Групповое"],
90
+ index=0,
91
+ horizontal=True
92
+ )
93
+
94
+ количество_детей = 1 # Значение по умолчанию
95
+
96
+ if формат_занятия == "Групповое":
97
+ количество_детей = st.number_input(
98
+ "Количество детей в группе*",
99
+ min_value=2,
100
+ max_value=10,
101
+ value=3
102
+ )
103
+
104
+ # Дополнительные параметры
105
+ тематика = st.text_input(
106
+ "Тематика занятия",
107
+ placeholder="Пример: Животные, Транспорт"
108
+ )
109
+
110
+ особые_условия = st.text_input(
111
+ "Особые условия",
112
+ placeholder="Пример: гиперактивность, РАС"
113
+ )
114
+
115
+ # И��вентарь
116
+ st.subheader("Инвентарь")
117
+ инвентарь_чекбоксы = {
118
+ "Зеркало": st.checkbox("Зеркало"),
119
+ "Карточки": st.checkbox("Карточки"),
120
+ "Игрушки": st.checkbox("Игрушки")
121
+ }
122
+ дополнительный_инвентарь = st.text_input("Другое")
123
+
124
+ инвентарь = ", ".join([k for k, v in инвентарь_чекбоксы.items() if v])
125
+ if дополнительный_инвентарь:
126
+ инвентарь += f", {дополнительный_инвентарь}"
127
+
128
+ # Другие настройки
129
+ длительность_занятия = st.slider(
130
+ "Длительность (минут)",
131
+ min_value=15,
132
+ max_value=60,
133
+ value=30,
134
+ step=5
135
+ )
136
+
137
+ наличие_ДЗ = st.checkbox("Домашнее задание")
138
+ разрешен_web_search = st.checkbox("Поиск материалов в интернете")
139
+
140
+ # Кнопка генерации
141
+ сгенерировать = st.button("Создать конспект")
142
+
143
+ # Правая часть с результатом
144
+ if сгенерировать:
145
+ if not нарушение or not возраст_ребенка or not цель_занятия:
146
+ st.error("Заполните обязательные поля!")
147
+ else:
148
+ params = {
149
+ "нарушение": нарушение,
150
+ "возраст_ребенка": возраст_ребенка,
151
+ "цель_занятия": цель_занятия,
152
+ "тематика": тематика,
153
+ "формат_занятия": формат_занятия,
154
+ "количество_детей": количество_детей,
155
+ "инвентарь": инвентарь,
156
+ "особые_условия": особые_условия,
157
+ "длительность_занятия": длительность_занятия,
158
+ "наличие_ДЗ": наличие_ДЗ,
159
+ "разрешен_web_search": разрешен_web_search
160
+ }
161
+
162
+ with st.spinner("Создаем конспект..."):
163
+ try:
164
+ результат = generate_lesson_plan(params)
165
+ st.success("Готово!")
166
+ st.markdown(результат)
167
+ except Exception as e:
168
+ st.error(f"Ошибка: {e}")
app/misc/main_completions.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import OpenAI
4
+ import streamlit as st
5
+
6
+ # Загрузка переменных окружения
7
+ load_dotenv()
8
+ #api_key = os.getenv("API_KEY_openai")
9
+ API_KEY = os.getenv("API_KEY")
10
+ BASE_URL = os.getenv("BASE_URL")
11
+
12
+
13
+ def generate_lesson_plan(params):
14
+ # client = OpenAI(api_key=api_key)
15
+ client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
16
+ prompt = f"""
17
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры:
18
+ - **Основное нарушение:** {params['нарушение']}
19
+ - **Возраст ребенка:** {params['возраст_ребенка']}
20
+ - **Цель занятия:** {params['цель_занятия']}
21
+ - **Тематика:** {params['тематика']}
22
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
23
+ - **Инвентарь:** {params['инвентарь']}
24
+ - **Особые условия:** {params['особые_условия']}
25
+ - **Длительность занятия:** {params['длительность_занятия']} минут
26
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
27
+ - **Разрешен web search:** {params['разрешен_web_search']}
28
+
29
+ На основе этих параметров составь конспект занятия, который должен включать:
30
+ 1. **Тема занятия, цель и задачи**
31
+ 2. **Необходимые материалы**
32
+ 3. **Ход занятия**. Разбей занятие на логически последовательные этапы.
33
+ В каждый этап встрой конкретные упражнения с достаточным количеством примеров.
34
+ ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников.
35
+ Обязательно интегрируй ссылки на найденные источники в текст ответа.
36
+ 5. **Домашнее задание** (если параметр "наличие домашнего задания: True")
37
+ 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия")
38
+ """
39
+
40
+ # tools = []
41
+ # tool_choice = None
42
+
43
+ # if params['разрешен_web_search']:
44
+ # tools.append({
45
+ # "type": "web_search_preview",
46
+ # "search_context_size": "medium",
47
+ # "user_location": {
48
+ # "type": "approximate",
49
+ # "country": "RU"
50
+ # }
51
+ # })
52
+ # tool_choice = {"type": "web_search_preview"}
53
+
54
+ # response = client.responses.create(
55
+ # model="gpt-4o-mini",
56
+ # input=prompt,
57
+ # tools=tools if tools else None,
58
+ # tool_choice=tool_choice,
59
+ # max_output_tokens=2000
60
+ # )
61
+ # return response.output_text
62
+ #
63
+ response = client.chat.completions.create(
64
+ model="gpt-4o-mini",
65
+ messages=[
66
+ {"role": "system", "content": "Ты — эксперт в области логопедии."},
67
+ {"role": "user", "content": prompt}
68
+ ],
69
+ max_tokens=2000
70
+ )
71
+ return response.choices[0].message.content
72
+
73
+
74
+ # Интерфейс Streamlit
75
+ st.title("Генератор логопедических занятий")
76
+
77
+ # Инициализация флагов в session_state
78
+ if "loading" not in st.session_state:
79
+ st.session_state["loading"] = False
80
+ if "lesson_plan" not in st.session_state:
81
+ st.session_state["lesson_plan"] = ""
82
+
83
+ # Используем обычный блок в сайдбаре для динамических виджетов
84
+ with st.sidebar:
85
+ st.header("Настройки занятия")
86
+
87
+ нарушение = st.text_input(
88
+ "Основное нарушение*",
89
+ placeholder="Пример: Дислалия (свистящие), ОНР II уровня",
90
+ disabled=st.session_state["loading"]
91
+ )
92
+ возраст_ребенка = st.text_input(
93
+ "Возраст ребенка*",
94
+ placeholder="Пример: 5 лет, 6-7 лет",
95
+ disabled=st.session_state["loading"]
96
+ )
97
+ цель_занятия = st.text_input(
98
+ "Цель занятия*",
99
+ placeholder="Пример: Автоматизация звука [Р] в слогах",
100
+ disabled=st.session_state["loading"]
101
+ )
102
+
103
+ формат_занятия = st.radio(
104
+ "Формат занятия*",
105
+ options=["Индивидуальное", "Групповое"],
106
+ index=0,
107
+ horizontal=True,
108
+ disabled=st.session_state["loading"]
109
+ )
110
+
111
+ # Это поле появляется динамически, когда выбрано "Групповое"
112
+ if формат_занятия == "Групповое":
113
+ количество_детей = st.number_input(
114
+ "Количество детей в группе*",
115
+ min_value=2,
116
+ max_value=10,
117
+ value=3,
118
+ disabled=st.session_state["loading"]
119
+ )
120
+ else:
121
+ количество_детей = 1
122
+
123
+ тематика = st.text_input(
124
+ "Тематика занятия",
125
+ placeholder="Пример: Животные, Транспорт",
126
+ disabled=st.session_state["loading"]
127
+ )
128
+ особые_условия = st.text_input(
129
+ "Особые условия",
130
+ placeholder="Пример: гиперактивность, РАС",
131
+ disabled=st.session_state["loading"]
132
+ )
133
+
134
+ st.subheader("Инвентарь")
135
+ инвентарь_чекбоксы = {
136
+ "Зеркало": st.checkbox("Зеркало", disabled=st.session_state["loading"]),
137
+ "Карточки": st.checkbox("Карточки", disabled=st.session_state["loading"]),
138
+ "Игрушки": st.checkbox("Игрушки", disabled=st.session_state["loading"])
139
+ }
140
+ дополнительный_инвентарь = st.text_input("Другое", disabled=st.session_state["loading"])
141
+ инвентарь = ", ".join([k for k, v in инвентарь_чекбоксы.items() if v])
142
+ if дополнительный_инвентарь:
143
+ инвентарь += f", {дополнительный_инвентарь}"
144
+ инвентарь = инвентарь.strip(", ")
145
+
146
+ длительность_занятия = st.slider(
147
+ "Длительность (минут)",
148
+ min_value=15,
149
+ max_value=60,
150
+ value=30,
151
+ step=5,
152
+ disabled=st.session_state["loading"]
153
+ )
154
+ наличие_ДЗ = st.checkbox("Домашнее задание", disabled=st.session_state["loading"])
155
+ разрешен_web_search = st.checkbox("Поиск материалов в интернете", disabled=st.session_state["loading"])
156
+
157
+ # Кнопка отправки, блокируется если идет генерация
158
+ submit = st.button("Создать конспект", disabled=st.session_state["loading"], key="submit_btn")
159
+
160
+ # Если кнопка нажата, запускаем генерацию
161
+ if submit:
162
+ if not нарушение or not возраст_ребенка or not цель_занятия:
163
+ st.sidebar.error("Заполните обязательные поля!")
164
+ else:
165
+ st.session_state["loading"] = True # Блокируем виджеты
166
+ params = {
167
+ "нарушение": нарушение,
168
+ "возраст_ребенка": возраст_ребенка,
169
+ "цель_занятия": цель_занятия,
170
+ "тематика": тематика,
171
+ "формат_занятия": формат_занятия,
172
+ "количество_детей": количество_детей,
173
+ "инвентарь": инвентарь,
174
+ "особые_условия": особые_условия,
175
+ "длительность_занятия": длительность_занятия,
176
+ "наличие_ДЗ": наличие_ДЗ,
177
+ "разрешен_web_search": разрешен_web_search
178
+ }
179
+ with st.spinner("Создаем конспект..."):
180
+ try:
181
+ результат = generate_lesson_plan(params)
182
+ st.session_state["lesson_plan"] = результат
183
+ st.success("Готово!")
184
+ except Exception as e:
185
+ st.error(f"Ошибка: {e}")
186
+ finally:
187
+ st.session_state["loading"] = False
188
+
189
+ # Отображаем ранее сгенерированный конспект, если он есть и генерация не идёт
190
+ if st.session_state.get("lesson_plan", "") and not st.session_state["loading"]:
191
+ st.markdown(st.session_state["lesson_plan"])
app/misc/main_greadio_noStream.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from openai import OpenAI
4
+ import gradio as gr
5
+ import time
6
+ from datetime import datetime
7
+ import calendar
8
+ from docx import Document
9
+ import tempfile
10
+ import random # для случайного выбора цитаты
11
+ from app.quotes import quotes
12
+ # Загрузка переменных окружения
13
+ load_dotenv()
14
+ api_key = os.getenv("API_KEY_openai")
15
+ client = OpenAI(api_key=api_key)
16
+
17
+ #API_KEY = os.getenv("API_KEY")
18
+ #BASE_URL = os.getenv("BASE_URL")
19
+ #client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
20
+
21
+
22
+ # Функция генерации плана занятия
23
+ def generate_lesson_plan_interface(
24
+ нарушение, возраст_ребенка, особые_условия,
25
+ формат_занятия, количество_детей, цель_занятия, тема, длительность_занятия,
26
+ инвентарь, наличие_ДЗ, разрешен_web_search, web_sources, текущий_месяц=None
27
+ ):
28
+
29
+ # Логика работы с источниками - если есть, передаем
30
+ источники = [web_sources] if разрешен_web_search else []
31
+
32
+ # Логика определения месяца
33
+ if not текущий_месяц:
34
+ текущий_месяц = datetime.now().month
35
+
36
+ # все параметры
37
+ params = {
38
+ "нарушение": нарушение,
39
+ "возраст_ребенка": возраст_ребенка,
40
+ "цель_занятия": цель_занятия,
41
+ "формат_занятия": формат_занятия,
42
+ "количество_детей": количество_детей,
43
+ "тема": тема,
44
+ "особые_условия": особые_условия,
45
+ "инвентарь": инвентарь,
46
+ "длительность_занятия": длительность_занятия,
47
+ "наличие_ДЗ": наличие_ДЗ,
48
+ "разрешен_web_search": разрешен_web_search,
49
+ "источники": источники,
50
+ "текущий_месяц": текущий_месяц
51
+ }
52
+
53
+ prompt = f"""
54
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры:
55
+ - **Основное нарушение:** {params['нарушение']}
56
+ - **Возраст ребенка:** {params['возраст_ребенка']}
57
+ - **Цель занятия:** {params['цель_занятия']}
58
+ - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"}
59
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
60
+ - **Инвентарь:** {params['инвентарь'] or "не указано"}
61
+ - **Особые условия:** {params['особые_условия'] or "не указано"}
62
+ - **Длительность занятия:** {params['длительность_занятия']} минут
63
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
64
+ - **Разрешен web search:** {params['разрешен_web_search']}
65
+ - **Текущий месяц:** {params['текущий_месяц']}
66
+
67
+ На основе этих параметров составь конспект занятия, который должен включать:
68
+ 1. **Тема занятия, цель и задачи**
69
+ 2. **Необходимые материалы**
70
+ 3. **Ход занятия**. Разбей занятие на логически последовательные этапы.
71
+ В каждый этап встрой конкретные упражнения с достаточным количеством примеров.
72
+ ВАЖНО: если разрешен web search: True, то выстрой занятие на базе упражнений из релевантных источников.
73
+ Обязательно приведи ссылки на источники источники в текст ответа).
74
+ 5. **Домашнее задание** (только если параметр "наличие домашнего задания: True")
75
+ 6. **Рекомендации по особым условиям** (если указан параметр "Особые условия")
76
+ """
77
+
78
+ tools = []
79
+ tool_choice = None
80
+
81
+ if params['разрешен_web_search']:
82
+ tools.append({
83
+ "type": "web_search_preview",
84
+ "search_context_size": "medium",
85
+ "user_location": {
86
+ "type": "approximate",
87
+ "country": "RU"
88
+ }
89
+ })
90
+ tool_choice = {"type": "web_search_preview"}
91
+
92
+ response = client.responses.create(
93
+ model="gpt-4o-mini",
94
+ input=prompt,
95
+ tools=tools if tools else None,
96
+ tool_choice=tool_choice,
97
+ max_output_tokens=2000
98
+ )
99
+ return response.output_text
100
+
101
+ # response = client.chat.completions.create(
102
+ # model="gpt-4o-mini",
103
+ # messages=[
104
+ # {"role": "system", "content": "Ты — эксперт в области логопедии."},
105
+ # {"role": "user", "content": prompt}
106
+ # ],
107
+ # max_tokens=2000
108
+ # )
109
+ # return response.choices[0].message.content
110
+
111
+ # Интерфейс Gradio
112
+ with gr.Blocks() as demo:
113
+ gr.Markdown("## 🧠 Генератор логопедических занятий")
114
+
115
+ with gr.Row():
116
+ # Первый блок настройки (Ребенок)
117
+ with gr.Column(scale=1):
118
+ quote_box = gr.Markdown(random.choice(quotes), elem_classes=["quote-block"])
119
+
120
+ gr.Markdown("### 🧒 Ребёнок", elem_classes=["block-title"])
121
+ нарушение = gr.Textbox(label="Основное нарушение*",
122
+ placeholder="Пример: Дислалия (свистящие), ОНР II уровня")
123
+ возраст = gr.Textbox(label="Возраст ребенка*", placeholder="Пример: 5 лет, 6-7 лет")
124
+ особые_условия = gr.Textbox(label="Особые условия", placeholder="Пример: гиперактивность, РАС")
125
+
126
+ gr.Markdown("### 📄 Занятие", elem_classes=["block-title"])
127
+ формат = gr.Radio(["Индивидуальное", "Групповое"], label="Формат занятия", value="Индивидуальное")
128
+ количество_детей = gr.Slider(
129
+ label="Количество детей в группе", minimum=2, maximum=10, value=2, step=1, visible=False
130
+ )
131
+
132
+ def toggle_group_slider(selected_format):
133
+ return gr.update(visible=(selected_format == "Групповое"))
134
+
135
+
136
+ формат.change(fn=toggle_group_slider, inputs=формат, outputs=количество_детей)
137
+
138
+ цель = gr.Textbox(label="Цель занятия*", placeholder="Пример: Автоматизация звука [Р] в слогах")
139
+ тема = gr.Textbox(label="Тема", placeholder="Пример: Животные, Транспорт")
140
+ длительность = gr.Slider(label="Длительность занятия (мин)", minimum=15, maximum=60, value=30, step=5)
141
+ инвентарь = gr.Textbox(label="Инвентарь (введите через запятую)",
142
+ placeholder="Пример: Зеркало, Карточки, Игрушки")
143
+
144
+ дз = gr.Checkbox(label="Домашнее задание")
145
+ # Выделенный блок для профессиональных ресурсов
146
+ gr.Markdown("---") # Горизонтальная разделительная линия
147
+ #gr.Markdown("### 🔍 Дополнительные ресурсы", elem_classes=["block-title", "pro-title"])
148
+
149
+ with gr.Row(variant="panel"): # Вариант "panel" добавляет фоновый оттенок
150
+ web = gr.Checkbox(
151
+ label="📚 Поиск упражнений в логопедических базах",
152
+ info="Упражнения подбираются из проверенных источников со ссылками",
153
+ interactive=True
154
+ )
155
+
156
+ web_sources = gr.Textbox(
157
+ label="Уточнить источники (необязательно):",
158
+ placeholder="Например: Logopedy.ru, Maam.ru, ...",
159
+ visible=False,
160
+ )
161
+
162
+ # Логика отображения поля источников
163
+ web.change(
164
+ fn=lambda x: gr.update(visible=x),
165
+ inputs=web,
166
+ outputs=web_sources
167
+ )
168
+
169
+ # Кнопка создания конспекта
170
+ btn = gr.Button("Создать конспект", variant="primary")
171
+
172
+ # Правая колонка — результат (output)
173
+ with gr.Column(scale=2): # Правая колонка — результат
174
+ download_btn = gr.DownloadButton(
175
+ label="⬇️ Скачать .docx",
176
+ visible=False
177
+ )
178
+ output = gr.Markdown("") # Поле для вывода конспекта
179
+
180
+ # Ввод параметров
181
+ all_inputs = [
182
+ нарушение, возраст, особые_условия,
183
+ формат, количество_детей, цель, тема, длительность,
184
+ инвентарь, дз, web, web_sources
185
+ ]
186
+
187
+ # Функция для генерации docx файла
188
+ def generate_docx(text: str):
189
+ doc = Document()
190
+ for line in text.split("\n"):
191
+ doc.add_paragraph(line)
192
+ tmp_dir = tempfile.gettempdir()
193
+ file_path = os.path.join(tmp_dir, "Конспект_занятия.docx")
194
+ doc.save(file_path)
195
+ return file_path
196
+
197
+
198
+ def on_submit_with_spinner(*args):
199
+ # Проверка обязательных полей
200
+ if not args[0] or not args[1] or not args[5]:
201
+ yield (
202
+ *[gr.update(interactive=True) for _ in all_inputs],
203
+ gr.update(value="❗Заполните обязательные поля: нарушение, возраст, цель занятия"),
204
+ gr.update(visible=False)
205
+ )
206
+ return
207
+
208
+ # Показываем спиннер
209
+ yield (
210
+ *[gr.update(interactive=False) for _ in all_inputs],
211
+ gr.update(value="⏳ Конспект создается..."),
212
+ gr.update(visible=False)
213
+ )
214
+
215
+ # Генерация конспекта
216
+ result = generate_lesson_plan_interface(*args)
217
+ file_path = generate_docx(result)
218
+ random_quote = random.choice(quotes)
219
+ yield (
220
+ *[gr.update(interactive=True) for _ in all_inputs],
221
+ gr.update(value=result),
222
+ gr.update(visible=True, value=file_path), # Передаём путь к файлу прямо в DownloadButton
223
+ )
224
+
225
+ # Привязываем обработчики
226
+ btn.click(
227
+ fn=on_submit_with_spinner,
228
+ inputs=all_inputs,
229
+ outputs=[* all_inputs, output, download_btn]
230
+ )
231
+
232
+
233
+ if __name__ == "__main__":
234
+ demo.launch(share=True)
app/misc/misc ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def delete_file_by_name(filename: str):
3
+ """
4
+ Удаляет файл из OpenAI по имени (если он есть в existing_files.json)
5
+ """
6
+ existing = load_existing_files()
7
+ if filename not in existing:
8
+ print(f"Файл '{filename}' не найден в existing_files.json")
9
+ return
10
+
11
+ file_id = existing[filename]['file_id']
12
+ vector_store_id = VS_ID
13
+ try:
14
+ client.vector_stores.files.delete(
15
+ vector_store_id=vector_store_id,
16
+ file_id=file_id
17
+ )
18
+ print(f"Файл '{filename}' (ID: {file_id}) успешно удалён")
19
+
20
+ # Удаляем из локального кэша
21
+ del existing[filename]
22
+ save_existing_files(existing)
23
+ except Exception as e:
24
+ print(f"Ошибка при удалении файла '{filename}': {str(e)}")
app/misc/test_API.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ API_KEY = ""
4
+
5
+ headers = {
6
+ "Authorization": f"Bearer {API_KEY}"
7
+ }
8
+
9
+ response = requests.get("https://api.openai.com/v1/models", headers=headers)
10
+
11
+ print(response.status_code) # Должно быть 200, если ключ работает
12
+ print(response.json()) # Посмотрим, что вернет API
app/misc/test_completions.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from openai import OpenAI
3
+ from dotenv import load_dotenv
4
+
5
+ # 5. Загрузка переменных окружения
6
+ load_dotenv()
7
+ API_KEY = os.getenv("API_KEY")
8
+ BASE_URL = os.getenv("BASE_URL")
9
+
10
+ # 6. Инициализация клиента OpenAI
11
+ client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
12
+
13
+ # Отправка запроса к chat.completions
14
+ response = client.chat.completions.create(
15
+ model="gpt-4o-mini", # Можно заменить на другую доступную модель
16
+ messages=[
17
+ {"role": "user", "content": "Найди свежие новости про искусственный интеллект и дай мне ссылки."}
18
+ ]
19
+ )
20
+
21
+ # Вывод ответа в консоль
22
+ print(response.choices[0].message.content)
app/misc/test_file_search.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import OpenAI
2
+ import os
3
+ import asyncio
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ API_KEY = os.getenv("API_KEY_openai")
9
+ VS_ID = os.getenv("VECTOR_STORE_ID")
10
+ client = OpenAI(api_key=API_KEY)
11
+
12
+ response = client.responses.create(
13
+ model="gpt-4o-mini",
14
+ input="Приведи из базы упражнения дляя автматизации звука Р. Указывай точные названия источников в [квадратных скобках]."
15
+ "Строго: если релевантные сведения в данных по запросу отсутствуют, сообщи об этом. Ответ давай в пределах {max_output_tokens} токенов",
16
+ tools=[{
17
+ "type": "file_search",
18
+ "vector_store_ids": [VS_ID]
19
+ }],
20
+ max_output_tokens=1000,
21
+ include=["file_search_call.results"]
22
+ )
23
+
24
+ print (response.output_text)
25
+
app/misc/test_proxy.py ADDED
File without changes
app/misc/test_responses.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import OpenAI
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ API_KEY=os.getenv("API_KEY_openai")
6
+ load_dotenv()
7
+ client = OpenAI(api_key=API_KEY)
8
+ VS_ID = os.getenv("VECTOR_STORE_ID")
9
+ response = client.responses.create(
10
+ model="gpt-4o-mini",
11
+ input="Придумай упражнения для автоматизации звука Р",
12
+ tools=[{
13
+ "type": "file_search",
14
+ "vector_store_ids": [VS_ID]
15
+ }]
16
+ )
17
+
18
+ print(response.output_text)
19
+
20
+
21
+
app/misc/test_web_search.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os # Импортируем модуль для работы с переменными окружения
2
+ import asyncio # Импортируем модуль для поддержки асинхронного программирования
3
+ from dotenv import load_dotenv # Импортируем функцию для загрузки переменных окружения из файла .env
4
+ from openai import OpenAI
5
+ # Загружаем переменные окружения из файла .env
6
+ load_dotenv()
7
+ api_key=os.getenv("API_KEY_openai")
8
+
9
+ client = OpenAI(api_key=api_key)
10
+
11
+ # Тестовый запрос
12
+ prompt = "Какие современные методы используются для развития фонематического слуха у детей? Приведи примеры и ссылки на источники."
13
+
14
+ tools = [
15
+ {
16
+ "type": "web_search_preview",
17
+ "search_context_size": "low",
18
+ "user_location": {
19
+ "type": "approximate",
20
+ "country": "RU"
21
+ }
22
+ }
23
+ ]
24
+
25
+ # Отправляем запрос к API
26
+ response = client.responses.create(
27
+ model="gpt-4o-mini",
28
+ input=prompt,
29
+ tools=tools,
30
+ max_output_tokens=1000
31
+ )
32
+
33
+ # Выводим результат в консоль
34
+ print("Ответ модели:")
35
+ print(response.output_text)
app/prompt.txt ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры:
2
+ - **Нарушение:** {нарушение}
3
+ - **Цель занятия:** {цель_занятия}
4
+ - **Месяц:** {месяц}
5
+ - **Инвентарь:** {инвентарь}
6
+ - **Количество детей:** {количество_детей}
7
+ - **Длительность занятия:** {длительность_занятия}
8
+ - **Наличие домашнего задания:** {наличие_ДЗ}
9
+ - **Загруженные материалы (если есть):** {загруженные_материалы}
10
+ - **Разрешен web search:** {разрешен_web_search}
11
+
12
+ На основе этих параметров составь подробный конспект занятия, который должен включать:
13
+
14
+ 1. **Тема занятия, цель и задачи:**
15
+ - Четко сформулируй тему занятия.
16
+ - Определи основную цель и конкретные задачи, которые необходимо решить.
17
+
18
+ 2. **Необходимые материалы:**
19
+ - Перечисли все материалы и инвентарь, необходимые для проведения занятия (например, зеркала, лото, карточки и т.д.).
20
+
21
+ 3. **Ход занятия:**
22
+ - Разбей занятие на логически последовательные этапы: организационный момент, основная часть, речевые игры, артикуляционная разминка, заключительная часть.
23
+
24
+ 4. **Встроенные упражнения с достаточным количеством материала:**
25
+ - Приведи конкретные упражнения (например, списки слов, предложения, игры) с подробными примерами, соответствующими заявленным параметрам.
26
+
27
+ 5. **Использование загруженных файлов (если есть):**
28
+ - Если логопед загрузил файлы с логопедическими материалами, используй `file search` для поиска наиболее релевантных фрагментов, связанных с темой и целью занятия.
29
+ - Интегрируй найденные материалы в ход занятия, указывая, где и как они используются.
30
+ - Если подходящих материалов в файлах нет, сгенерируй примеры самостоятельно.
31
+
32
+ 6. **Подбор материалов из свободных источников (если разрешен web search):**
33
+ - Если разрешен веб-поиск ({разрешен_web_search} = "Да"), найди релевантные материалы (иллюстрации, видео, списки слов), которые будут интегрированы в ход занятия.
34
+ - Для каждого найденного материала укажи, на каком этапе занятия он используется и какую функцию выполняет (например, "Видео с артикуляционной гимнастикой", "Карточки с изображениями для упражнения по звуку [С]").
35
+ - Если веб-поиск не разрешен или не удалось найти подходящие материалы, сгенерируй примеры самостоятельно.
36
+
37
+ 7. **Домашнее задание (если предусмотрено):**
38
+ - Если параметр {наличие_ДЗ} указывает на наличие домашнего задания, предложи конкретное домашнее задание, соответствующее теме занятия.
39
+
40
+ **Важно:** Используй загруженные материалы и web search только если они доступны. В остальных случаях сгенерируй примеры самостоятельно.
41
+
42
+
43
+ prompt = f"""
44
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями.
45
+ Ты получаешь запрос на разработку занятия со следующими параметрами:
46
+ - **Основное нарушение ребенка:** {params['нарушение']}
47
+ - **Возраст:** {params['возраст_ребенка']}
48
+ - **Цель занятия:** {params['цель_занятия']}
49
+ - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"}
50
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
51
+ - **Доступный инвентарь:** {params['инвентарь'] or "не указано, на твое усмотрение"}
52
+ - **Особые условия:** {params['особые_условия'] or "отсутствуют"}
53
+ - **Длительность занятия:** {params['длительность_занятия']} минут
54
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
55
+ - **Поиск по интернету:** {params['разрешен_web_search']}
56
+ - **Текущий месяц :** {params['текущий_месяц']} (1=январь и т.д.)
57
+
58
+ На основе этих параметров составь план занятия, который должен включать:
59
+ 1. **Тема занятия, цель и задачи (не более 3-х)**
60
+ 2. **Необходимый инвентарь**
61
+ 3. **Ход занятия**. Состоит из логически последовательных этапов.
62
+ В каждый этап встроены конкретные упражнения с достаточным количеством примеров.
63
+ СТРОГО: если **поиск по интернету: True**, то обязательно найди упражнения из релевантных источников.
64
+ (если параметр "наличие домашнего задания": True): 5. **Домашнее задание**
65
+ (если параметр "особые условия": True)6. **Рекомендации по особым условиям** (если указан параметр "Особые условия", иначе пропусти)
66
+ """
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями.
80
+ Ты получаешь запрос на разработку занятия со следующими параметрами:
81
+ - **Основное нарушение ребенка:** {params['нарушение']}
82
+ - **Возраст:** {params['возраст_ребенка']}
83
+ - **Цель занятия:** {params['цель_занятия']}
84
+ - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"}
85
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
86
+ - **Доступный инвентарь:** {params['инвентарь'] or "не указано, на твое усмотрение"}
87
+ - **Особые условия:** {params['особые_условия'] or "отсутствуют"}
88
+ - **Длительность занятия:** {params['длительность_занятия']} минут
89
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
90
+ - **Поиск по интернету:** {params['разрешен_web_search']}
91
+ - **Текущий месяц :** {params['текущий_месяц']} (1=январь и т.д.)
92
+
93
+
94
+ На основе этих параметров составь план занятия, который должен включать:
95
+ 1. **Тема занятия, цель и задачи (не более 3-х)**
96
+ 2. **Необходимый инвентарь**
97
+ 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и достаточным количеством примеров.
98
+ 4. **Домашнее задание** (если параметр "наличие домашнего задания": True)
99
+ 5. **Рекомендации по особым условиям** (если параметр "особые условия" указан)
100
+
101
+ **Особое условие:**
102
+ Если **поиск по интернету** равен **True**, то:
103
+ - Найди актуальные примеры упражнений и интегрируй их в соответствующие этапы занятия.
104
+ - Для каждого найденного упражнения укажи источник (ссылку).
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+ **Инструкции для веб-поис��а (если разрешен):**
119
+ 1. **Сформулируй поисковые запросы** на основе данных о ребенке:
120
+ - Учти: нарушение ({params['нарушение']}), возраст ({params['возраст_ребенка']}), цель ({params['цель_занятия']}).
121
+ - Пример запроса: "логопедические упражнения при дизартрии для детей 5 лет на развитие артикуляционной моторики".
122
+ 2. **Используй только авторитетные источники**:
123
+ - Научные статьи, образовательные порталы (например, logoped.ru, defektolog.pro), методические пособия.
124
+ - Избегай блогов, форумов и непроверенных ресурсов.
125
+ 3. **Адаптируй найденные упражнения**:
126
+ - Проверь соответствие инвентарю ({params['инвентарь']}) и длительности ({params['длительность_занятия']} мин).
127
+ - Упрости/усложни задания в зависимости от возраста.
128
+ - Если упражнение требует запрещенного инвентаря — предложи альтернативу.
129
+ 4. **Укажи источники** в формате: [Название ресурса] (ссылка/автор) — например: "Упражнение «Веселый язычок» (методика Н.В. Нищевой)".
130
+
131
+ **Структура плана занятия (допол
132
+
133
+
134
+
135
+
136
+
137
+ {
138
+ "нарушение": "дислалия",
139
+ "возраст_ребенка": "5 лет",
140
+ "цель_занятия": "автоматизация звука [Р] в словах и фразах",
141
+ "тема": "Путешествие в страну звука [Р]",
142
+ "формат_занятия": "индивидуальное",
143
+ "количество_детей": "1",
144
+ "инвентарь": "зеркало, картинки со звуком [Р], мяч, кубики с буквами",
145
+ "особые_условия": "гиперактивность",
146
+ "длительность_занятия": "30",
147
+ "наличие_ДЗ": "True",
148
+ "разрешен_web_search": "True",
149
+ "текущий_месяц": "4"
150
+ }
151
+
152
+
153
+
154
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями.
155
+ Ты получаешь запрос на разработку занятия со следующими параметрами:
156
+ - **Основное нарушение ребенка:** {params['нарушение']}
157
+ - **Возраст:** {params['возраст_ребенка']}
158
+ - **Цель занятия:** {params['цель_занятия']}
159
+ - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"}
160
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
161
+ - **Доступный инвентарь:** {params['инвентарь'] or "не указано, на твое усмотрение"}
162
+ - **Особые условия:** {params['особые_условия'] or "не указаны"}
163
+ - **Длительность занятия:** {params['длительность_занятия']} минут
164
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
165
+ - **Поиск по интернету:** {params['разрешен_web_search']}
166
+ - **Текущий месяц года:** {params['текущий_месяц']} (1=январь и т.д.)
167
+
168
+ На основе этих параметров составь план занятия, который должен включать:
169
+ 1. **Тема занятия, цель и задачи (не более 3-х)**
170
+ 2. **Необходимый инвентарь**
171
+ 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и достаточным количеством примеров.
172
+ **Особое условие:**
173
+ Если **поиск по интернету** равен **True**, то:
174
+ - Найди актуальные примеры упражнений и интегрируй их в соответствующие этапы занятия.
175
+ - При необходимости адаптируй найденные упражнения для ребенка/детей.
176
+ - Для каждого найденного упражнения укажи источник (сс��лку).
177
+ 4. **Домашнее задание** (если параметр "наличие домашнего задания": True, иначе не упоминай в ответе)
178
+ 5. **Рекомендации по особым условиям** (если параметр "особые условия" указан, иначе не упоминай в ответе)
179
+
180
+
181
+ Ты — эксперт-логопед, разрабатывающий занятия для детей с речевыми нарушениями.
182
+ На основе параметров занятия составь план занятия, который должен включать:
183
+ 1. **Тема занятия, цель и задачи (не более 3-х)**
184
+ 2. **Необходимый инвентарь**
185
+ 3. **Ход занятия** – логически последовательные этапы с конкретными упражнениями и достаточным количеством примеров.
186
+ **Особое условие:**
187
+ Если **поиск по интернету** равен **True**, то:
188
+ - Найди примеры релевантных упражнений и интегрируй их в соответствующие этапы занятия.
189
+ - При необходимости адаптируй найденные упражнения для ребенка/детей.
190
+ - Для каждого найденного упражнения укажи источник (ссылку).
191
+ 4. **Домашнее задание** (если параметр "наличие домашнего задания": True, иначе не упоминай в ответе)
192
+ 5. **Рекомендации по особым условиям** (если параметр "особые условия" указан, иначе не упоминай в ответе)
193
+
194
+ Параметры занятия:
195
+ - **Основное нарушение ребенка:** {params['нарушение']}
196
+ - **Возраст:** {params['возраст_ребенка']}
197
+ - **Цель занятия:** {params['цель_занятия']}
198
+ - **Тема:** {params['тема'] or "не указано. Придумай сам, опираясь на данные о ребенке и цели занятия"}
199
+ - **Формат занятия:** {params['формат_занятия']} ({params['количество_детей']} детей)
200
+ - **Доступный инвентарь:** {params['инвентарь'] or "не указано, на твое усмотрение"}
201
+ - **Особые условия:** {params['особые_условия'] or "не указаны"}
202
+ - **Длительность занятия:** {params['длительность_занятия']} минут
203
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
204
+ - **Поиск по интернету:** {params['разрешен_web_search']}
205
+ - **Текущий месяц года:** {params['текущий_месяц']} (1=январь и т.д.)
app/quotes.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ quotes = [
2
+ "<em>«Речь - не только средство общения, но и орудие мышления.»</em><br>Р.Е. Левина, 'Основы теории и практики логопедии' (1968)",
3
+ "<em>«Логопед должен быть и врачом, и педагогом, и психологом.»</em><br>С.С. Ляпидевский, 'Невропатология' (1965)",
4
+ "<em>«Речь - это зеркало души, отражающее её состояние.»</em><br>В.К. Орфинская, 'Методы работы при алалии' (1963)",
5
+ "<em>«Коррекция речи - это создание условий для естественного развития.»</em><br>О.В. Правдина, 'Логопедия' (1973)",
6
+ "<em>«Заикание - не болезнь, а привычка, которую можно изменить.»</em><br>Ф.А. Рау, 'Заикание и его преодоление' (1948)",
7
+ "<em>«Логопед открывает ребёнку мир, где слова обретают смысл.»</em><br>Т.Б. Филичева, 'Логопедическая работа в специальном детском саду' (1987)",
8
+ "<em>«Заикание - вызов, который можно преодолеть.»</em><br>В.И. Селивёрстов, 'Заикание у детей' (1994)",
9
+ "<em>«Речевая патология - не тупик, а особый путь развития.»</em><br>Е.М. Мастюкова, 'Ребёнок с отклонениями в развитии' (1992)",
10
+ "<em>«Дислексия - иной способ восприятия текста.»</em><br>О.Б. Иншакова, 'Альбом для логопеда' (2003)",
11
+ "<em>«Ребёнок с нарушением речи - прежде всего ребёнок.»</em><br>Г.В. Чиркина, 'Коррекция нарушений речи' (2009)",
12
+ "<em>«Логопедия - это наука о возможностях.»</em><br>Т.Г. Визель, 'Основы нейропсихологии' (2005)",
13
+ "<em>«Каждое слово ребёнка - шаг в развитии.»</em><br>Н.С. Жукова, 'Логопедия. Преодоление ОНР' (1998)",
14
+ "<em>«Игра - главный инструмент в работе с речью.»</em><br>Е.А. Стребелева, 'Коррекционно-педагогическая помощь' (2008)",
15
+ "<em>«Речь формируется в диалоге, а не в упражнениях.»</em><br>А.В. Ястребова, 'Преодоление общего недоразвития речи' (1999)",
16
+ "<em>«Мотивация - двигатель речевого развития.»</em><br>Л.Г. Парамонова, 'Логопедия для всех' (2002)",
17
+ "<em>«Ошибки в речи - это ступени к правильному произношению.»</em><br>Т.А. Ткаченко, 'Если дошкольник плохо говорит' (2001)",
18
+ "<em>«Ребёнок учится говорить, когда ему есть что сказать.»</em><br>С.Н. Шаховская, 'Логопедическая работа с детьми' (1985)",
19
+ "<em>«Логопед не исправляет речь, а помогает ей развиваться.»</em><br>В.К. Воробьёва, 'Методика развития речи' (2006)",
20
+ "<em>«Речевые нарушения - это сигналы, а не приговор.»</em><br>Р.И. Лалаева, 'Логопедическая работа в коррекционных классах' (1998)",
21
+ "<em>«Главное в логопедии - видеть личность за дефектом.»</em><br>Б.М. Гриншпун, 'Дефектология и логопедия' (1983)",
22
+
23
+ # Зарубежные
24
+ "<em>«Речь - это мост между мыслью и действием.»</em><br>Чарльз Ван Райпер (1954)",
25
+ "<em>«Заикание - это не то, что у тебя есть, это то, что ты делаешь.»</em><br>Оливер Бладстайн (1987)",
26
+ "<em>«Логопедия - это не исправление ошибок, а раскрытие потенциала.»</em><br>Рэймонд Д. Кент (1994)",
27
+ "<em>«Язык рождается из потребности человека соединиться с другим человеком.»</em><br>Роман Якобсон (1960)",
28
+ "<em>«Говорить - значит быть человеком.»</em><br>Пол Брока (1861)",
29
+ "<em>«Лучший способ помочь ребёнку заговорить - это слушать.»</em><br>Элейн Силлман",
30
+ "<em>«Даже самая сложная речь начинается с одного слова.»</em><br>��арри Гутар (1982)",
31
+ "<em>«Молчание — это не отсутствие речи, а её начало.»</em><br>Питер Рэминг (1985)",
32
+ "<em>«Если вы видите в человеке то, чего он в себе не видит, — он этим становится.»</em><br>Виктор Франкл",
33
+ "<em>«Ожидания взрослых напрямую влияют на прогресс ребёнка»</em><br>Пигмалилион в классе, Р. Розенталь и Л. Джекобсон (1968)"
34
+ ]
app/styles.css ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Основной фон */
2
+ /*.gradio-container {
3
+ background-color: #fef3c7;
4
+ } */
5
+
6
+ /* Заголовок */
7
+ /* .main-title h2 { */
8
+ text-align: left;
9
+ font-size: 2rem;
10
+ margin: 0; /* Убираем отступы вокруг заголовка */
11
+ /*color: #003366;*/
12
+ margin-bottom: -5px; !important /* Убираем отступ снизу заголовка */
13
+ padding: 0;
14
+ }
15
+
16
+
17
+ /* Цитата */
18
+ .quote-block {
19
+ text-align: left;
20
+ /*font-style: italic;*/
21
+ padding: 0;
22
+ margin: -1px 0 0 0; /* верхний небольшой отступ, снизу чуть больше */
23
+ /*max-width: 30em;*/ /* ограничиваем ширину, чтобы не тянулась на всю строку */
24
+ line-height: 1.4em;
25
+ }
26
+
27
+
28
+ /* .left-col { */
29
+ /* background-color: #CDE8FA; /* более тёмный голубой */
30
+ /* padding: 20px; */
31
+ /* Можно и цвет текста подкорректировать */
32
+ /* color: #003366; */
33
+ /* } */
34
+
35
+ .right-col {
36
+ background-color: #F0FAFF; /* посветлее */
37
+ padding: 0px;
38
+ color: #fef3c7;
39
+ }
40
+
41
+ /* Поле для конспекта */
42
+ #plan-output {
43
+ border: 2px dashed #bbb;
44
+ border-radius: 12px;
45
+ background-color: ##f5f5f5;
46
+ padding: 20px;
47
+ min-height: 1100px;
48
+ font-style: italic;
49
+ color: #555;
50
+ box-shadow: 0 2px 6px rgba(0,0,0,0.05);
51
+ }
52
+
53
+ /*#f5f5f5*/
app/test.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os # Импортируем модуль для работы с переменными окружения
2
+ import asyncio # Импортируем модуль для поддержки асинхронного программирования
3
+ from dotenv import load_dotenv # Импортируем функцию для загрузки переменных окружения из файла .env
4
+ from openai import OpenAI
5
+ # Загружаем переменные окружения из файла .env
6
+ load_dotenv()
7
+ api_key=os.getenv("API_KEY_openai")
8
+ async def main():
9
+ client = OpenAI(api_key=api_key)
10
+ # Пример параметров для запроса
11
+ params = {
12
+ "нарушение": "ОНР", # Тип речевого нарушения
13
+ "цель_занятия": "Развитие фонематического слуха", # Цель занятия
14
+ "месяц": "апрель", # Месяц проведения занятия
15
+ "инвентарь": "зеркало, карточки с изображениями", # Необходимый инвентарь
16
+ "количество_детей": 1, # Количество детей на занятии
17
+ "длительность_занятия": 30, # Длительность занятия в минутах
18
+ "наличие_ДЗ": False, # Наличие домашнего задания (True или False)
19
+ "загруженные_материалы": None, # Путь к загруженным материалам, если есть
20
+ "разрешен_web_search": True # Разрешение на использование веб-поиска
21
+ }
22
+
23
+ # Формируем текст запроса (промпт) на основе предоставленных параметров
24
+ prompt = f"""
25
+ Ты — эксперт в области логопедии, специализирующийся на разработке занятий для детей с речевыми нарушениями. На вход ты получаешь следующие параметры:
26
+ - **Нарушение:** {params['нарушение']}
27
+ - **Цель занятия:** {params['цель_занятия']}
28
+ - **Месяц:** {params['месяц']}
29
+ - **Инвентарь:** {params['инвентарь']}
30
+ - **Количество детей:** {params['количество_детей']}
31
+ - **Длительность занятия:** {params['длительность_занятия']}
32
+ - **Наличие домашнего задания:** {params['наличие_ДЗ']}
33
+ - **Загруженные материалы (если есть):** {params['загруженные_материалы']}
34
+ - **Разрешен web search:** {params['разрешен_web_search']}
35
+
36
+ На основе этих параметров составь конспект занятия, который должен включать:
37
+ 1. **Тема занятия, цель и задачи:** Четко сформулируй тему занятия, основную цель и конкретные задачи, которые необходимо решить.
38
+ 2. **Необходимые материалы:** Перечисли все материалы и инвентарь, необходимые для проведения занятия.
39
+ 3. **Ход занятия:** Разбей занятие на логически последовательные этапы.
40
+ 4. **Встроенные в ход занятия упражнения с достаточным количеством материала:** Приведи конкретные упражнения с подробными примерами.
41
+ ВАЖНО: если разрешен web search: True, подбери примеры для упражнений из релевантных источников. Обязательно интегрируй ссылки на найденные источники в текст ответа.
42
+ 5. **Использование загруженных файлов (если есть):** Если логопед загрузил файлы с логопедическими материалами, используй их для поиска наиболее релевантных фрагментов и интегрируй их в ход занятия.
43
+ 6. **Домашнее задание (если True):** Если параметр 'наличие_ДЗ' указывает на наличие домашнего задания, предложи конкретное домашнее задание, соответствующее теме занятия.
44
+ """
45
+
46
+ # Определяем список инструментов, которые будут использоваться в запросе
47
+ tools = []
48
+ tool_choice = None # По умолчанию tool_choice отсутствует
49
+
50
+ if params['разрешен_web_search']:
51
+ tools.append({
52
+ "type": "web_search_preview",
53
+ "search_context_size": "medium",
54
+ "user_location": {
55
+ "type": "approximate",
56
+ "country": "RU"
57
+ }
58
+ })
59
+ tool_choice = {"type": "web_search_preview"} # Устанавливаем tool_choice только если web search включен
60
+
61
+ if params['загруженные_материалы']:
62
+ tools.append({"type": "file_search"}) # Добавляем инструмент поиска по файлам, если материалы загружены
63
+
64
+ # Отправляем запрос к API OpenAI с использованием эндпоинта responses
65
+ response = client.responses.create(
66
+ model="gpt-4o-mini", # Используемая модель
67
+ input=prompt, # Текст запроса
68
+ tools=tools if tools else None, # Включаем инструменты, если они были определены
69
+ tool_choice=tool_choice, # Передаем tool_choice только если он определен
70
+ max_output_tokens=1200 # Максимальное количество токенов в ответе
71
+ )
72
+
73
+ # Выводим результат в консоль
74
+ print(response.output_text)
75
+
76
+ # Запускаем асинхронную функцию
77
+ asyncio.run(main())
app/theme_builder.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import gradio as gr
2
+
3
+ gr.themes.builder()
app/tooltip.js ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // app/tooltip.js
2
+ function initProCheckboxTooltip() {
3
+ const checkboxLabel = document.querySelector('#pro-checkbox .wrap .label-wrap');
4
+ if (checkboxLabel) {
5
+ checkboxLabel.setAttribute(
6
+ 'data-tooltip',
7
+ 'Включает упражнения из: Logopedia.pro, Maam.ru и других проверенных источников'
8
+ );
9
+
10
+ if (!checkboxLabel.querySelector('.tooltip-icon')) {
11
+ const icon = document.createElement('span');
12
+ icon.className = 'tooltip-icon';
13
+ icon.textContent = ' ⓘ';
14
+ icon.style.color = '#1976D2';
15
+ checkboxLabel.querySelector('label').appendChild(icon);
16
+ }
17
+ }
18
+ }
19
+
20
+ // Инициализация при загрузке
21
+ document.addEventListener('DOMContentLoaded', initProCheckboxTooltip);
22
+
23
+ // Отслеживание динамических изменений DOM
24
+ new MutationObserver(initProCheckboxTooltip).observe(document.body, {
25
+ childList: true,
26
+ subtree: true
27
+ });
docs/costs.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ openai - gpt-4o-mini:
2
+ input 0.15/1M
3
+ output 0.60/1M
4
+
5
+ web search:
6
+ gpt-4o-mini or gpt-4o-mini-search-preview:
7
+ low - 0.025$
8
+ med - 0.0275$
9
+ high - 0.03$
10
+ - responses web_search - 0.03$
11
+ - file search tool call - 0,0025$
12
+
13
+ image input
14
+ ~30000/1 страница ~ 0.005$ ???
15
+
16
+
17
+
18
+ aitunnel
19
+ gpt-4o-mini
20
+ image input 0.85р=0.01$
21
+ текстовый запрос - от 0.01р~0.0001$
docs/errors.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ ❌ Ошибка: Error code: 500 - {'error': {'message': 'An error occurred while processing your request. You can retry your request, or contact us through
2
+ our help center at help.openai.com if the error persists.
3
+ Please include the request ID req_91a6425c4b4c40ed774a0cb21c9b7cf5 in your message.', 'type': 'server_error', 'param': None, 'code': 'server_error'}}
4
+
5
+
docs/file_search ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ RETRIEVAL
2
+ https://platform.openai.com/docs/guides/retrieval?attributes-filter-example=filename&vector-store-operations=create&vector-store-batch-operations=update
3
+
4
+
5
+ Attributes
6
+ Each vector_store.file can have associated attributes, a dictionary of values that can be referenced when performing semantic search with attribute filtering.
7
+ The dictionary can have at most 16 keys, with a limit of 256 characters each.
8
+
9
+ Create vector store file with attributes
10
+ client.vector_stores.files.create(
11
+ vector_store_id="vs_123",
12
+ file_id="file_123",
13
+ attributes={
14
+ "region": "US",
15
+ "category": "Marketing",
16
+ "date": 1672531200 # Jan 1, 2023
17
+ }
18
+ )
19
+
20
+
21
+
22
+
23
+ Chunking
24
+ 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,
25
+ with 400-token overlap between consecutive chunks.
26
+
27
+ You can adjust this by setting chunking_strategy when adding files to the vector store. There are certain limitations to chunking_strategy:
28
+
29
+ max_chunk_size_tokens must be between 100 and 4096 inclusive.
30
+ chunk_overlap_tokens must be non-negative and should not exceed max_chunk_size_tokens / 2.
31
+
32
+
33
+
34
+
35
+ ############ Vector store operations
36
+ CREATE
37
+ client.vector_stores.create(
38
+ name="Support FAQ",
39
+ file_ids=["file_123"]
40
+ )
41
+
42
+ UPDATE
43
+ client.vector_stores.update(
44
+ vector_store_id="vs_123",
45
+ name="Support FAQ Updated"
46
+ )
47
+
48
+
49
+ ############ Vector store file operations
50
+ CREATE
51
+ client.vector_stores.files.create_and_poll(
52
+ vector_store_id="vs_123",
53
+ file_id="file_123"
54
+ )
55
+
56
+
57
+ BATCH CREATE
58
+ client.vector_stores.file_batches.create_and_poll(
59
+ vector_store_id="vs_123",
60
+ file_ids=["file_123", "file_456"]
61
+ )
62
+
63
+ UPDATE
64
+ client.vector_stores.files.update(
65
+ vector_store_id="vs_123",
66
+ file_id="file_123",
67
+ attributes={"key": "value"}
68
+ )
69
+ LIST
70
+ client.vector_stores.files.list(
71
+ vector_store_id="vs_123"
72
+ )
73
+
74
+ ############# ATTRIBUTES
75
+ Each vector_store.file can have associated attributes, a dictionary of values that can be referenced when performing semantic search with attribute filtering.
76
+ The dictionary can have at most 16 keys, with a limit of 256 characters each.
77
+
78
+ CREATE VS FILE WITH ATTRIBUTES
79
+ client.vector_stores.files.create(
80
+ vector_store_id="vs_123",
81
+ file_id="file_123",
82
+ attributes={
83
+ "region": "US",
84
+ "category": "Marketing",
85
+ "date": 1672531200 # Jan 1, 2023
86
+ }
87
+ )
88
+
89
+ SEARCH QUERY
90
+ user_query = "What is the return policy?"
91
+
92
+ results = client.vector_stores.search(
93
+ vector_store_id=vector_store.id,
94
+ query=user_query,
95
+ )
96
+
97
+
98
+ FILE SEARCH TOOL
99
+ from openai import OpenAI
100
+ client = OpenAI()
101
+
102
+ response = client.responses.create(
103
+ model="gpt-4o-mini",
104
+ input="What is deep research by OpenAI?",
105
+ tools=[{
106
+ "type": "file_search",
107
+ "vector_store_ids": ["<vector_store_id>"]
108
+ }]
109
+ )
110
+ print(response)
111
+
112
+ RESPONSE
113
+ {
114
+ "output": [
115
+ {
116
+ "type": "file_search_call",
117
+ "id": "fs_67c09ccea8c48191ade9367e3ba71515",
118
+ "status": "completed",
119
+ "queries": ["What is deep research?"],
120
+ "search_results": null
121
+ },
122
+ {
123
+ "id": "msg_67c09cd3091c819185af2be5d13d87de",
124
+ "type": "message",
125
+ "role": "assistant",
126
+ "content": [
127
+ {
128
+ "type": "output_text",
129
+ "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...",
130
+ "annotations": [
131
+ {
132
+ "type": "file_citation",
133
+ "index": 992,
134
+ "file_id": "file-2dtbBZdjtDKS8eqWxqbgDi",
135
+ "filename": "deep_research_blog.pdf"
136
+ },
137
+ {
138
+ "type": "file_citation",
139
+ "index": 992,
140
+ "file_id": "file-2dtbBZdjtDKS8eqWxqbgDi",
141
+ "filename": "deep_research_blog.pdf"
142
+ },
143
+ {
144
+ "type": "file_citation",
145
+ "index": 1176,
146
+ "file_id": "file-2dtbBZdjtDKS8eqWxqbgDi",
147
+ "filename": "deep_research_blog.pdf"
148
+ },
149
+ {
150
+ "type": "file_citation",
151
+ "index": 1176,
152
+ "file_id": "file-2dtbBZdjtDKS8eqWxqbgDi",
153
+ "filename": "deep_research_blog.pdf"
154
+ }
155
+ ]
156
+ }
157
+ ]
158
+ }
159
+ ]
160
+ }
161
+
162
+
163
+ LIMIT RESULTS
164
+ "max_num_results": 2
165
+ INCLUDE SEARCH RESULTS
166
+ include=["file_search_call.results"]
167
+
168
+
169
+ METADATA FILTERING
170
+
171
+ "filters": {
172
+ "type": "eq",
173
+ "key": "type",
174
+ "value": "blog"
175
+ }
176
+
177
+
178
+
179
+
180
+
181
+
182
+
183
+
184
+
185
+ Response(id='resp_67f6d96ee8408192a8849670feda558e0faa975217e529bc',
186
+ created_at=1744230766.0, error=None, incomplete_details=None, instructions=None,
187
+ metadata={}, model='gpt-4o-mini-2024-07-18', object='response', output=[ResponseFileSearchToolCall(id='fs_67f6d97084588192afdf4ebf9b3121cc0faa975217e529bc',
188
+ queries=['упражнения для автоматизации звука Р', 'упражнения для звука Р', 'автоматизация звука Р'], status='completed', type='file_search_call', results=None),
189
+ ResponseOutputMessage(id='msg_67f6d97428f0819299ea4a771e639b3f0faa975217e529bc',
190
+ content=[ResponseOutputText(annotations=[AnnotationFileCitation(file_id='file-PiEcinbLiZwssqrka7N2P3',
191
+ index=315, type='file_citation', filename='Komarova_avtomatizatsia_zvuka_R.pdf'),
192
+ AnnotationFileCitation(file_id='file-QX2xWtyQLiCrU9rcMa9SAH', index=475, type='file_citation',
193
+ filename='Komarova_avtomatizatsia_zvuka_R-_1_.pdf'), AnnotationFileCitation(file_id='file-QX2xWtyQLiCrU9rcMa9SAH',
194
+ index=664, type='file_citation', filename='Komarova_avtomatizatsia_zvuka_R-_1_.pdf'),
195
+ AnnotationFileCitation(file_id='file-PiEcinbLiZwssqrka7N2P3', index=828, type='file_citation',
196
+ filename='Komarova_avtomatizatsia_zvuka_R.pdf'), AnnotationFileCitation(file_id='file-PiEcinbLiZwssqrka7N2P3',
197
+ index=828, type='file_citation', filename='Komarova_avtomatizatsia_zvuka_R.pdf'),
198
+ AnnotationFileCitation(file_id='file-JGY4tskUsk4fpd9vJN7dxr', index=1030, type='file_citation',
199
+ filename='Avtomatizatsia_zvuki_M_M_N_N.doc')], text='Вот несколько упражнений для автоматизации звука «Р»,
200
+ которые можно использовать с детьми в возрасте 4-7 лет:\n\n1. Лабиринт:\n - Ребенок произносит звук «Р»,
201
+ называя картинки на странице, перемещая игрушку по лабиринту. Если слово произнесено неправильно, следующий ход
202
+ задерживается до правильного произношения.\n\n2. Лабиринт с проговариванием:\n - При движении ребенку нужно не
203
+ только называть картинки, но и указывать направление хода (например, "пряники — вверх").\n\n3. Игры с игрушками:\n
204
+ - Используйте объемные или плоские игрушки, названия которых содержат звук «Р». Это помогает удерживать внимание
205
+ ребенка и делает занятия более увлекательными.\n\n4. Упражнения на слоги и слова:\n - Включите слова и слоги,
206
+ содержащие звук «Р», в игру, где ребенок будет составлять слова из первых букв названий картинок.\n\n5. Чистоговорки
207
+ и скороговорки:\n - Занимайтесь скороговорками, начиная с медленного произношения, затем постепенно увеличивайте
208
+ скорость, акцентируя внимание на правильном артикулировании звука.\n\nЭти упражнения помогут не только в автоматизации
209
+ звука «Р», но и в развитии мелкой моторики, фонематического слуха и навыков чтения.', type='output_text')], role='assistant',
210
+ status='completed', type='message')], parallel_tool_calls=True, temperature=1.0, tool_choice='auto',
211
+ tools=[FileSearchTool(type='file_search', vector_store_ids=['vs_67f69730803481918fb5049813e0bf7f'],
212
+ filters=None, max_num_results=20, ranking_options=RankingOptions(ranker='auto', score_threshold=0.0))],
213
+ top_p=1.0, max_output_tokens=None, previous_response_id=None, reasoning=Reasoning(effort=None, generate_summary=None),
214
+ status='completed', text=ResponseTextConfig(format=ResponseFormatText(type='text')), truncation='disabled',
215
+ usage=ResponseUsage(input_tokens=12398, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=389,
216
+ output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=12787), user=None, store=True)
217
+
218
+
219
+
220
+
221
+
222
+
223
+
224
+
225
+
226
+ {
227
+ "id": "resp_67ccf4c55fc48190b71bd0463ad3306d09504fb6872380d7",
228
+ "object": "response",
229
+ "created_at": 1741485253,
230
+ "status": "completed",
231
+ "error": null,
232
+ "incomplete_details": null,
233
+ "instructions": null,
234
+ "max_output_tokens": null,
235
+ "model": "gpt-4o-2024-08-06",
236
+ "output": [
237
+ {
238
+ "type": "file_search_call",
239
+ "id": "fs_67ccf4c63cd08190887ef6464ba5681609504fb6872380d7",
240
+ "status": "completed",
241
+ "queries": [
242
+ "attributes of an ancient brown dragon"
243
+ ],
244
+ "results": null
245
+ },
246
+ {
247
+ "type": "message",
248
+ "id": "msg_67ccf4c93e5c81909d595b369351a9d309504fb6872380d7",
249
+ "status": "completed",
250
+ "role": "assistant",
251
+ "content": [
252
+ {
253
+ "type": "output_text",
254
+ "text": "The attributes of an ancient brown dragon include...",
255
+ "annotations": [
256
+ {
257
+ "type": "file_citation",
258
+ "index": 320,
259
+ "file_id": "file-4wDz5b167pAf72nx1h9eiN",
260
+ "filename": "dragons.pdf"
261
+ },
262
+ {
263
+ "type": "file_citation",
264
+ "index": 576,
265
+ "file_id": "file-4wDz5b167pAf72nx1h9eiN",
266
+ "filename": "dragons.pdf"
267
+ },
268
+ {
269
+ "type": "file_citation",
270
+ "index": 815,
271
+ "file_id": "file-4wDz5b167pAf72nx1h9eiN",
272
+ "filename": "dragons.pdf"
273
+ },
274
+ {
275
+ "type": "file_citation",
276
+ "index": 815,
277
+ "file_id": "file-4wDz5b167pAf72nx1h9eiN",
278
+ "filename": "dragons.pdf"
279
+ },
280
+ {
281
+ "type": "file_citation",
282
+ "index": 1030,
283
+ "file_id": "file-4wDz5b167pAf72nx1h9eiN",
284
+ "filename": "dragons.pdf"
285
+ },
286
+ {
287
+ "type": "file_citation",
288
+ "index": 1030,
289
+ "file_id": "file-4wDz5b167pAf72nx1h9eiN",
290
+ "filename": "dragons.pdf"
291
+ },
292
+ {
293
+ "type": "file_citation",
294
+ "index": 1156,
295
+ "file_id": "file-4wDz5b167pAf72nx1h9eiN",
296
+ "filename": "dragons.pdf"
297
+ },
298
+ {
299
+ "type": "file_citation",
300
+ "index": 1225,
301
+ "file_id": "file-4wDz5b167pAf72nx1h9eiN",
302
+ "filename": "dragons.pdf"
303
+ }
304
+ ]
305
+ }
306
+ ]
307
+ }
308
+ ],
309
+ "parallel_tool_calls": true,
310
+ "previous_response_id": null,
311
+ "reasoning": {
312
+ "effort": null,
313
+ "generate_summary": null
314
+ },
315
+ "store": true,
316
+ "temperature": 1.0,
317
+ "text": {
318
+ "format": {
319
+ "type": "text"
320
+ }
321
+ },
322
+ "tool_choice": "auto",
323
+ "tools": [
324
+ {
325
+ "type": "file_search",
326
+ "filters": null,
327
+ "max_num_results": 20,
328
+ "ranking_options": {
329
+ "ranker": "auto",
330
+ "score_threshold": 0.0
331
+ },
332
+ "vector_store_ids": [
333
+ "vs_1234567890"
334
+ ]
335
+ }
336
+ ],
337
+ "top_p": 1.0,
338
+ "truncation": "disabled",
339
+ "usage": {
340
+ "input_tokens": 18307,
341
+ "input_tokens_details": {
342
+ "cached_tokens": 0
343
+ },
344
+ "output_tokens": 348,
345
+ "output_tokens_details": {
346
+ "reasoning_tokens": 0
347
+ },
348
+ "total_tokens": 18655
349
+ },
350
+ "user": null,
351
+ "metadata": {}
352
+ }
docs/ideas.md ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Проблема: задания могут быть надерганы из контекста, а в оригинале у них есть объединяющие элементы
2
+ Идея: в промпт добавить указание на создание логически-связанного урока
docs/logic.md ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Логика интерфейса в Streamlit:
2
+
3
+ Левая панель (настройки)
4
+
5
+ Поле ввода (свободный текст) → "Нарушение"
6
+
7
+ Поле ввода (свободный текст) → "Цель занятия"
8
+
9
+ Выпадающий список (dropdown) → "Месяц"
10
+
11
+ Чекбоксы + поле ввода → "Инвентарь"
12
+
13
+ Зеркало
14
+
15
+ Кубики
16
+
17
+ Игрушки
18
+
19
+ Поле для ввода своего варианта
20
+
21
+ Ползунок (slider) → "Количество детей" (от 1 до 10)
22
+
23
+ Чекбокс → "Наличие домашнего задания"
24
+
25
+ Форма загрузки файлов (пока без обработки) → "Загруженные материалы"
26
+
27
+ Чекбокс → "Поиск по материалам в интернете (web search)"
28
+
29
+ Кнопка → "Создать конспект"
30
+
31
+ Правая панель (вывод конспекта)
32
+
33
+ До нажатия на кнопку: пустая область
34
+
35
+ После нажатия: появляется сгенерированный текст конспекта
36
+
37
+ Как избежать автоматического обновления (мигания экрана и сброса параметров)
38
+
39
+ Используем st.session_state для хранения значений полей, чтобы они не сбрасывались
40
+
41
+ Запуск генерации конспекта только при нажатии на кнопку (обернем обработчик в st.button)
42
+
43
+ Проверка параметров
44
+
45
+ Все основные параметры присутствуют
46
+
47
+ Загруженные файлы пока не обрабатываем
48
+
49
+ Web search включается через чекбокс
50
+
51
+ Форма взаимодействия понятна пользователю
52
+
53
+
54
+ Логика работы параметров и запуска кода:
55
+ Изменение параметров не должно запускать генерацию
56
+
57
+ Все параметры (чекбоксы, выпадающие списки, текстовые поля) можно менять без триггера обновления страницы
58
+
59
+ Состояние параметров фиксируется в st.session_state, чтобы при любом изменении они не сбрасывались
60
+
61
+ Код запускается только при нажатии кнопки
62
+
63
+ После ввода всех параметров пользователь нажимает "Создать конспект"
64
+
65
+ В этот момент проверяются обязательные поля
66
+
67
+ Если все обязательные поля заполнены, данные передаются в генерацию
68
+
69
+ Если какие-то обязательные поля пустые, выводится предупреждение
70
+
71
+ Разделение параметров на обязательные и необязательные
72
+
73
+ Обязательные (без них кнопку нажать нельзя):
74
+
75
+ Нарушение
76
+
77
+ Цель занятия
78
+
79
+ Количество детей (по умолчанию, например, 1)
80
+
81
+ Длительность занятия (по умолчанию, например, 30 минут)
82
+
83
+ Необязательные (по умолчанию пустые или выключенные):
84
+
85
+ Инвентарь
86
+
87
+ Месяц
88
+
89
+ Поиск по материалам в интернете
90
+
91
+ UX-детали
92
+
93
+ Если обязательное поле пустое, кнопка "Создать конспект" неактивна (disabled=True) или выводится предупреждение
94
+
95
+ После генерации конспекта в правой части экрана результат остается до следующего нажатия кнопки
docs/misc.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ streamlit run app/main.py
3
+ streamlit run app/main_completions.py
4
+
5
+
6
+ git push --force space main
7
+
8
+
9
+
10
+ # Logoped
11
+ Ассистент логопеда по созданию планов занятий.
12
+ Формирует методически-выверенный и релевантный план занятия на основе входных параметров от логопеда.
13
+ Параметры сформированы по пожеланиям экспертов-логопедов.
14
+
docs/mistakes.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 1. Автоматизация звука [р] в предложениях (5 минут):
2
+ Игра "Четвертый лишний": ## правильно нашел задание
3
+ Логопед называет четыре слова, одно из которых не содержит звук [р]. ## здесь ошибка, неправильно понял задание
4
+ Примеры: РОБОТ, ВОРОНА, КОРОВА, БАРАБАН (лишнее — ворона).
5
+ Ребенок придумывает предложение с лишним словом: "Ворона сидит на заборе."
6
+ Повторить с другими наборами слов.
7
+
8
+
9
+ Из пособия?
10
+ Автоматизация звука [р] в предложениях.
11
+ (ребёнок открывает кармашек зелёного цвета)
12
+
13
+ Игра «Четвёртый лишний»
14
+ - Определи какой из этих предметов лишний и объясни почему. ## другой смысл задания
15
+
16
+ РОБОТ, ВОРОНА, КОРОВА, БАРАБАН
17
+ РЫБА, РАК, РУКИ, ТОПОР
18
+ ГОРОХ, МУХОМОР, КУКУРУЗА, МОРКОВЬ
19
+ ПОМИДОР, РАДУГА, ЗАБОР, САМОВАР
20
+
21
+
22
+ 2. Тема занятия: "Летние приключения с ## ЛЕТНИЕ!
23
+ В промпте: - **Текущий месяц :** 4 (1=январь и т.д.)
24
+ Решение: улучшить промпт
requirements.txt ADDED
Binary file (122 Bytes). View file
 
test ADDED
File without changes
test_API.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ API_KEY = ""
4
+
5
+ headers = {
6
+ "Authorization": f"Bearer {API_KEY}"
7
+ }
8
+
9
+ response = requests.get("https://api.openai.com/v1/models", headers=headers)
10
+
11
+ print(response.status_code) # Должно быть 200, если ключ работает
12
+ print(response.json()) # Посмотрим, что вернет API
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
test_completions.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from openai import OpenAI
3
+ from dotenv import load_dotenv
4
+
5
+ # 5. Загрузка переменных окружения
6
+ load_dotenv()
7
+ API_KEY = os.getenv("API_KEY")
8
+ BASE_URL = os.getenv("BASE_URL")
9
+
10
+ # 6. Инициализация клиента OpenAI
11
+ client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
12
+
13
+ # Отправка запроса к chat.completions
14
+ response = client.chat.completions.create(
15
+ model="gpt-4o-mini", # Можно заменить на другую доступную модель
16
+ messages=[
17
+ {"role": "user", "content": "Найди свежие новости про искусственный интеллект и дай мне ссылки."}
18
+ ]
19
+ )
20
+
21
+ # Вывод ответа в консоль
22
+ print(response.choices[0].message.content)
test_proxy.py ADDED
File without changes