Spaces:
Sleeping
Sleeping
Commit ·
58a9b80
0
Parent(s):
олная очистка: удалены все бинарные файлы
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +0 -0
- .idea/.gitignore +3 -0
- .idea/inspectionProfiles/profiles_settings.xml +6 -0
- .idea/logos.iml +10 -0
- .idea/misc.xml +7 -0
- .idea/modules.xml +8 -0
- .idea/vcs.xml +6 -0
- README.md +20 -0
- TASKS.md +27 -0
- app/.gradio/certificate.pem +31 -0
- app/Stable.py +168 -0
- app/data/attributes_template.json +0 -0
- app/data/existing_files.json +0 -0
- app/data/files_management.py +219 -0
- app/drawings.py +128 -0
- app/main.py +347 -0
- app/main_completions.py +191 -0
- app/main_gradio.py +183 -0
- app/main_gradio_nostream.py +343 -0
- app/main_gradio_test.py +379 -0
- app/main_gradio_test_FS.py +386 -0
- app/misc/.gradio/certificate.pem +31 -0
- app/misc/Stable.py +168 -0
- app/misc/main_completions.py +191 -0
- app/misc/main_greadio_noStream.py +234 -0
- app/misc/misc +24 -0
- app/misc/test_API.py +12 -0
- app/misc/test_completions.py +22 -0
- app/misc/test_file_search.py +25 -0
- app/misc/test_proxy.py +0 -0
- app/misc/test_responses.py +21 -0
- app/misc/test_web_search.py +35 -0
- app/prompt.txt +205 -0
- app/quotes.py +34 -0
- app/styles.css +53 -0
- app/test.py +77 -0
- app/theme_builder.py +3 -0
- app/tooltip.js +27 -0
- docs/costs.md +21 -0
- docs/errors.md +5 -0
- docs/file_search +352 -0
- docs/ideas.md +2 -0
- docs/logic.md +95 -0
- docs/misc.md +14 -0
- docs/mistakes.md +24 -0
- requirements.txt +0 -0
- test +0 -0
- test_API.py +46 -0
- test_completions.py +22 -0
- test_proxy.py +0 -0
.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
|