1 Commits
dev ... main

Author SHA1 Message Date
swrneko
d231707572 change prompt 2025-12-14 18:37:53 +03:00
8 changed files with 63 additions and 275 deletions

10
.gitignore vendored
View File

@@ -1,5 +1,5 @@
.env .env
__pycache__/ __pycache__/
outputs/* outputs/*
venv/ venv/

20
app.py
View File

@@ -5,11 +5,13 @@ from config import *
# Подгрузка сервисов # Подгрузка сервисов
from services.llm_factory import get_llm_provider from services.llm_factory import get_llm_provider
from services.fasterWhisper import FasterWhisper
# Загрузка доп. модулей # Загрузка доп. модулей
from handlers.gradioHandler import GradioHandlers from handlers.gradioHandler import GradioHandlers
from handlers.fileHandlers import FileHandlers from handlers.fileHandlers import FileHandlers
from services.fasterWhisper import FasterWhisper
from handlers.convertMdToPdf import ConvertMdToPdf from handlers.convertMdToPdf import ConvertMdToPdf
from handlers.glueAudio import GlueAudio from handlers.glueAudio import GlueAudio
@@ -113,20 +115,13 @@ def main():
saveFileCheckbox.change(gh.updateTextbox, inputs=saveFileCheckbox, outputs=filename) saveFileCheckbox.change(gh.updateTextbox, inputs=saveFileCheckbox, outputs=filename)
saveFileCheckbox.change(gh.updateTextbox, inputs=saveFileCheckbox, outputs=filenamePdf) saveFileCheckbox.change(gh.updateTextbox, inputs=saveFileCheckbox, outputs=filenamePdf)
recognizeBtn.click( recognizeBtn.click(gh.handleRecognizeBtn, outputs=[recognizedText], inputs=[audioFiles, fastWhisperModel, device, compute_type, beamSize, vadFilter, minSilenceDurationMs, speechPadMs, temp0, temp1, temp2, wordTimestamps, noSpeechThreshold, conditionOnPreviousText, gr.State(GLUED_AUDIO_FILENAME), gr.State(OUTPUT_PATH)])
gh.handleRecognizeBtn,
inputs=[audioFiles, fastWhisperModel, device, compute_type, beamSize,
vadFilter, minSilenceDurationMs, speechPadMs, temp0, temp1, temp2,
wordTimestamps, noSpeechThreshold, conditionOnPreviousText, gr.State(GLUED_AUDIO_FILENAME), gr.State(OUTPUT_PATH)],
outputs=[recognizedText],
)
# Если пайплайн включен то тогда делаем автоматически # Если пайплайн включен то тогда делаем автоматически
# автоматический пайплайн # автоматический пайплайн
recognizedText.change( recognizedText.change(
gh.generateByCondition, gh.generateByCondition,
inputs=[apiKey, llmProvider, llmModel, systemPrompt, recognizedText, llmTemperature, inputs=[apiKey, llmProvider, llmModel, systemPrompt, recognizedText, llmTemperature, isPipelineEnabledCheckbox, gr.State("change"), saveFileCheckbox, filename, filenamePdf, gr.State(OUTPUT_PATH)],
isPipelineEnabledCheckbox, gr.State("change"), saveFileCheckbox, filename, filenamePdf, gr.State(OUTPUT_PATH)],
outputs=[refinedText, refinedTextMD] outputs=[refinedText, refinedTextMD]
) )
@@ -136,12 +131,11 @@ def main():
llmProvider.change( llmProvider.change(
gh.update_model_dropdown, gh.update_model_dropdown,
inputs=llmProvider, inputs=llmProvider,
outputs=[llmModel, apiKey] outputs=llmModel
) )
refineTextBtn.click( refineTextBtn.click(
gh.generateByCondition, gh.generateByCondition,
inputs=[apiKey, llmProvider, llmModel, systemPrompt, recognizedText, llmTemperature, inputs=[apiKey, llmProvider, llmModel, systemPrompt, recognizedText, llmTemperature, isPipelineEnabledCheckbox, gr.State("click"), saveFileCheckbox, filename, filenamePdf, gr.State(OUTPUT_PATH)],
isPipelineEnabledCheckbox, gr.State("click"), saveFileCheckbox, filename, filenamePdf, gr.State(OUTPUT_PATH)],
outputs=[refinedText, refinedTextMD] outputs=[refinedText, refinedTextMD]
) )

View File

@@ -8,10 +8,7 @@ DEVICES = ['cpu', 'cuda']
COMPUTE_TYPE = ['auto', 'int8', 'float16', 'float32'] COMPUTE_TYPE = ['auto', 'int8', 'float16', 'float32']
# Стандартный API ключ # Стандартный API ключ
IO_API_KEY=os.getenv('IO_API_KEY') DEFAULT_API_KEY=os.getenv('API_KEY')
GEMINI_API_KEY=os.getenv('GEMINI_API_KEY')
DEFAULT_API_KEY=IO_API_KEY
# Словарь провайдеров и их моделей # Словарь провайдеров и их моделей
LLM_PROVIDERS = ['io.net', 'Gemini', 'gpt4free'] LLM_PROVIDERS = ['io.net', 'Gemini', 'gpt4free']
@@ -49,38 +46,42 @@ OUTPUT_PATH='outputs'
GLUED_AUDIO_FILENAME='glued.mp3' GLUED_AUDIO_FILENAME='glued.mp3'
DEFAULT_SYSTEM_PROMPT='''You are a diligent university student who has recorded a lecture as an audio file and later transcribed it into raw text. DEFAULT_SYSTEM_PROMPT='''
Your task is to rewrite this unstructured transcript into a clear, logically organized, and detailed lecture summary (lecture notes). You are a smart university student creating easy-to-understand study notes summary of lesson for a classmate who is a beginner. Your source is a raw text/audio transcript.
Guidelines: GOAL: rewrite the information into a clear, structured summary in RUSSIAN.
1. Structure:
- Organize the text into a hierarchy of sections and subsections.
- Use headings, bullet points, or numbering where appropriate.
- Present the material in a logical flow (from introduction → main points → details → examples → conclusion).
2. Clarity & Cohesion: KEY RULES FOR CONTENT:
- Remove filler words, repetitions, and irrelevant fragments. 1. **Logical Structure:** Use Markdown headers (#, ##), bullet points, and short paragraphs.
- Rewrite incomplete sentences into full, grammatically correct sentences. 2. **No "Water":** Remove filler words. Keep only practical information.
- Ensure smooth transitions between topics, making the summary feel continuous and well-connected. 3. **Student Tone:** Write naturally, as if sharing notes with a friend. Avoid robotic phrases like "It is important to note".
3. Depth & Detail: KEY RULES FOR LATEX (CRITICAL FOR PYLATEXENC):
- Capture all important concepts, definitions, examples, and explanations from the lecture. 1. **Math Mode:** ANY variable (like t, L, C), number in a formula, or equation MUST be wrapped in dollar signs `$`.
- Expand shorthand or fragmented thoughts into full, precise explanations. * BAD: i(t) = i_pr + i_sv
- Where appropriate, rephrase or clarify confusing passages for better understanding. * GOOD: $i(t) = i_{pr} + i_{sv}$
2. **Subscripts:** Always use curly braces `{}` for subscripts longer than one character.
* BAD: $i_pr$
* GOOD: $i_{pr}$ (or $i_{пр}$ if using cyrillic)
3. **Symbols:** Use standard LaTeX commands for symbols.
* Arrow: use `\to` (e.g., $t \to \infty$).
* Infinity: use `\infty`.
* Multiplication: use `\cdot` or just space.
4. **Consistency:** Never leave a mathematical symbol as plain text. If you mention "current i", write "ток $i$".
4. Accuracy: EXAMPLE OF DESIRED OUTPUT FORMAT:
- Preserve the lecturer’s original meaning, intent, and terminology. # Тема лекции
- Avoid adding personal opinions or new information that was not in the lecture. ## Основные понятия
* **Переходный процесс** — это когда цепь перестраивается с одного режима на другой (например, щелкнули выключателем).
* Математически это описывается дифференциальными уравнениями. Порядок уравнения = количеству реактивных элементов ($L$ и $C$).
5. Style: ## Классический метод
- Write in a formal, academic tone suitable for study notes. Решение ищется в виде суммы двух частей:
- Aim for readability: concise sentences, but thorough coverage of concepts. $$i(t) = i_{pr} + i_{sv}$$
- Use emphasis (e.g., bold or italic text) only when it improves comprehension.
Final Output: A cohesive, detailed, and well-structured lecture summary, suitable for later studying and revision. 1. **Принужденная составляющая** ($i_{pr}$) — это режим, который установится в будущем, когда все успокоится ($t \to \infty$).
Use only russian language! 2. **Свободная составляющая** ($i_{sv}$) — это то, что происходит "само по себе" из-за энергии, запасенной в $L$ и $C$.
USE LATEX IN DOLLAR SIGN ($)!
EXTRA BIG LENTH OF CONSPECT! ***
MAKE AS LONG AS POSIBLE AND AS BE GOOD! STRICTLY FOLLOW THESE FORMATTING RULES. OUTPUT IN RUSSIAN.
''' '''

View File

@@ -1,61 +1,11 @@
import subprocess from pydub import AudioSegment
from pathlib import Path
class GlueAudio(): class GlueAudio():
def glue(self, audio_files: list, output_path: str, output_filename: str) -> Path: def glue(self, audioFiles):
""" glued = AudioSegment.empty()
Склеивает аудиофайлы с помощью FFmpeg, используя промежуточный список файлов.
Этот метод чрезвычайно эффективен по памяти и скорости.
Args: for audioFile in audioFiles:
audio_files (list): Список путей к исходным аудиофайлам. audio = AudioSegment.from_file(audioFile)
output_path (str): Директория для сохранения итогового файла. glued += audio
output_filename (str): Имя итогового склеенного файла.
Returns: return glued
Path: Путь к созданному склеенному файлу.
"""
output_dir = Path(output_path)
output_dir.mkdir(parents=True, exist_ok=True)
final_audio_path = output_dir / output_filename
if not audio_files:
raise ValueError("Список аудиофайлов для склейки пуст.")
# 1. Формируем часть команды с входными файлами (-i file1 -i file2 ...)
input_args = []
for file_path in audio_files:
input_args.extend(['-i', str(Path(file_path).resolve())])
# 2. Формируем строку для filter_complex
num_files = len(audio_files)
stream_specifiers = "".join([f"[{i}:a]" for i in range(num_files)])
filter_complex_str = f"{stream_specifiers}concat=n={num_files}:v=0:a=1[outa]"
# 3. Собираем полную команду
command = [
'ffmpeg',
*input_args, # Распаковываем список входных файлов
'-filter_complex', filter_complex_str,
'-map', '[outa]',
'-c:a', 'libmp3lame',
'-q:a', '2',
str(final_audio_path),
'-y'
]
try:
# 4. Выполняем команду
print(f"Выполнение команды FFmpeg: {' '.join(command)}")
subprocess.run(command, check=True, capture_output=True, text=True)
print("FFmpeg успешно завершил склейку.")
except FileNotFoundError:
raise FileNotFoundError("FFmpeg не найден. Убедитесь, что он установлен и доступен в системной переменной PATH.")
except subprocess.CalledProcessError as e:
print("Ошибка при выполнении FFmpeg!")
print("Stderr:", e.stderr)
raise RuntimeError(f"Ошибка FFmpeg при склейке файлов: {e.stderr}")
return final_audio_path

View File

@@ -1,6 +1,6 @@
from config import LLM_MODELS # Импортируем словарь моделей from config import LLM_MODELS # Импортируем словарь моделей
import gradio as gr import gradio as gr
from config import GEMINI_API_KEY, IO_API_KEY
class GradioHandlers: class GradioHandlers:
def __init__(self, llm_factory, ConvertMdToPdf, FileHandlers, FasterWhisper, GlueAudio): def __init__(self, llm_factory, ConvertMdToPdf, FileHandlers, FasterWhisper, GlueAudio):
@@ -9,39 +9,23 @@ class GradioHandlers:
self.ga = GlueAudio() self.ga = GlueAudio()
self.ConvertMdToPdf = ConvertMdToPdf() self.ConvertMdToPdf = ConvertMdToPdf()
self.FasterWhisper = FasterWhisper() self.FasterWhisper = FasterWhisper()
self.llm_factory = llm_factory self.llm_factory = llm_factory # Сохраняем фабрику
def handleRecognizeBtn( def handleRecognizeBtn(self, audioFiles, model, device, compute_type, beamSize, vadFilter, minSilenceDurationMs, speechPadMs, temp0, temp1, temp2, wordTimestamps, noSpeechThreshold, conditionOnPreviousText, filename, outPath):
self, audioFiles, model, device, compute_type, beamSize, vadFilter, audioFile = self.ga.glue(audioFiles)
minSilenceDurationMs, speechPadMs, temp0, temp1, temp2, file = self.fh.saveFile(filename, audioFile, outPath)
wordTimestamps, noSpeechThreshold, conditionOnPreviousText, filename, outPath
):
try:
glued_audio_path = self.ga.glue(
audio_files=[f.name for f in audioFiles], # Передаем список путей
output_path=outPath,
output_filename=filename
)
except (FileNotFoundError, RuntimeError) as e:
# Если FFmpeg не найден или произошла ошибка, сообщаем пользователю
gr.Warning(str(e))
return "" # Возвращаем пустую строку в текстовое поле
# Передаем путь к склеенному файлу в FasterWhisper return self.FasterWhisper.recognize(model, device, compute_type, file, beamSize, vadFilter, minSilenceDurationMs, speechPadMs, temp0, temp1, temp2, wordTimestamps, noSpeechThreshold, conditionOnPreviousText)
return self.FasterWhisper.recognize(model, device, compute_type, str(glued_audio_path), beamSize, vadFilter, minSilenceDurationMs, speechPadMs, temp0, temp1, temp2, wordTimestamps, noSpeechThreshold, conditionOnPreviousText)
# Функция улучшения текста # Функция улучшения текста
def generateByCondition(self, api_key, llm_provider, def generateByCondition(self, api_key, llm_provider, llm_model, system_prompt, recognized_text, llm_temperature, is_pipeline_enabled, trigger, isSaveFile, filename, filenamePdf, output_path):
llm_model, system_prompt, recognized_text,
llm_temperature, is_pipeline_enabled, trigger,
isSaveFile, filename, filenamePdf, output_path):
try: try:
# Получаем нужный провайдер через фабрику # Получаем нужный провайдер через фабрику
provider = self.llm_factory(llm_provider, api_key) provider = self.llm_factory(llm_provider, api_key)
except ValueError as e: except ValueError as e:
# Если API ключ не предоставлен для нужного провайдера, выводим ошибку # Если API ключ не предоставлен для нужного провайдера, выводим ошибку
gr.Warning(str(e)) self.gr.Warning(str(e))
return gr.skip(), gr.skip() return self.gr.skip(), self.gr.skip()
def process(): def process():
result, md = provider.generate(llm_model, system_prompt, recognized_text, llm_temperature) result, md = provider.generate(llm_model, system_prompt, recognized_text, llm_temperature)
@@ -69,9 +53,7 @@ class GradioHandlers:
default_value = models[0] if models else None default_value = models[0] if models else None
# Возвращаем обновленный компонент. Используем 'gr' напрямую. # Возвращаем обновленный компонент. Используем 'gr' напрямую.
if provider == 'io.net': return gr.update(choices=models, value=default_value), gr.update(label='API key (required for io.net, Gemini)', value=IO_API_KEY, interactive=True) return gr.update(choices=models, value=default_value)
if provider == 'Gemini': return gr.update(choices=models, value=default_value), gr.update(label='API key (required for Oio.net, Gemini)', value=GEMINI_API_KEY, interactive=True)
if provider == 'gpt4free': return gr.update(choices=models, value=default_value), gr.update(label='API key (required for Oio.net, Gemini)', value="", interactive=True)
# Функция для динамического обновления кнопки # Функция для динамического обновления кнопки
def updateButton(self, isChecked): def updateButton(self, isChecked):

View File

@@ -100,118 +100,3 @@ uvicorn==0.35.0
webencodings==0.5.1 webencodings==0.5.1
websockets==15.0.1 websockets==15.0.1
zopfli==0.2.3.post1 zopfli==0.2.3.post1
aiofiles==24.1.0
aiohappyeyeballs==2.6.1
aiohttp==3.13.0
aiosignal==1.4.0
annotated-types==0.7.0
anyio==4.10.0
attrs==25.4.0
audioop-lts==0.2.2
av==15.1.0
beautifulsoup4==4.13.5
Brotli==1.1.0
bs4==0.0.2
certifi==2025.8.3
cffi==2.0.0
charset-normalizer==3.4.3
click==8.2.1
coloredlogs==15.0.1
colour==0.1.5
cssselect2==0.8.0
ctranslate2==4.6.0
distro==1.9.0
dotenv==0.9.9
exceptiongroup==1.3.0
fastapi==0.116.1
faster-whisper==1.2.0
ffmpeg-python==0.2.0
ffmpy==0.6.1
filelock==3.19.1
flatbuffers==25.2.10
flatlatex==0.15
fonttools==4.59.2
frozenlist==1.8.0
fsspec==2025.9.0
future==1.0.0
g4f==0.6.3.5
gradio==5.44.1
gradio_client==1.12.1
groovy==0.1.2
h11==0.16.0
hf-xet==1.1.9
httpcore==1.0.9
httpx==0.28.1
huggingface-hub==0.34.4
humanfriendly==10.0
idna==3.10
iso639-lang==2.6.3
Jinja2==3.1.6
jiter==0.10.0
joblib==1.5.2
langdetect==1.0.9
littleutils==0.2.4
markdown-it-py==3.0.0
markdown_pdf==1.9
MarkupSafe==3.0.2
mdurl==0.1.2
mpmath==1.3.0
multidict==6.7.0
nest-asyncio==1.6.0
nltk==3.9.1
numpy==2.2.6
onnxruntime==1.22.1
openai==1.106.1
orjson==3.11.3
outdated==0.2.2
packaging==25.0
pandas==2.3.2
pillow==11.3.0
propcache==0.4.0
protobuf==6.32.0
pycparser==2.22
pycryptodome==3.23.0
pydantic==2.11.7
pydantic_core==2.33.2
pydub==0.25.1
pydyf==0.11.0
Pygments==2.19.2
pylatexenc==2.10
pymultidictionary==1.3.2
PyMuPDF==1.26.4
pyperclip==1.9.0
pyphen==0.17.2
python-dateutil==2.9.0.post0
python-dotenv==1.1.1
python-multipart==0.0.20
pytz==2025.2
PyYAML==6.0.2
regex==2025.9.1
requests==2.32.5
rich==14.1.0
ruff==0.12.12
safehttpx==0.1.6
semantic-version==2.10.0
setuptools==80.9.0
shellingham==1.5.4
six==1.17.0
sniffio==1.3.1
soupsieve==2.8
starlette==0.47.3
sympy==1.14.0
tinycss2==1.4.0
tinyhtml5==2.0.0
tkmacosx==1.0.5
tokenizers==0.22.0
tomlkit==0.13.3
tqdm==4.67.1
typer==0.17.4
typing-inspection==0.4.1
typing_extensions==4.15.0
tzdata==2025.2
urllib3==2.5.0
uvicorn==0.35.0
webencodings==0.5.1
websockets==15.0.1
yarl==1.22.0
zopfli==0.2.3.post1

View File

@@ -1,11 +1,7 @@
from faster_whisper import WhisperModel from faster_whisper import WhisperModel
class FasterWhisper: class FasterWhisper:
def recognize(self, model, device, compute_type, def recognize(self, model, device, compute_type, audioFile, beamSize, vadFilter, minSilenceDurationMs, speechPadMs, temp0, temp1, temp2, wordTimestamps, noSpeechThreshold, conditionOnPreviousText):
audioFile, beamSize, vadFilter,
minSilenceDurationMs, speechPadMs,
temp0, temp1, temp2, wordTimestamps,
noSpeechThreshold, conditionOnPreviousText):
model = WhisperModel(model, device=device, compute_type=compute_type) # Задаем модель model = WhisperModel(model, device=device, compute_type=compute_type) # Задаем модель
segments, _ = model.transcribe( # Распознаем текст segments, _ = model.transcribe( # Распознаем текст
@@ -26,7 +22,6 @@ class FasterWhisper:
for seg in segments: for seg in segments:
text += f"[{self.format_timestamp(seg.start)} -> {self.format_timestamp(seg.end)}] {seg.text}" + '\n' text += f"[{self.format_timestamp(seg.start)} -> {self.format_timestamp(seg.end)}] {seg.text}" + '\n'
print(f"[{self.format_timestamp(seg.start)} -> {self.format_timestamp(seg.end)}] {seg.text}")
return text return text

19
test.py
View File

@@ -1,19 +0,0 @@
from handlers.convertMdToPdf import ConvertMdToPdf
import re
# Создаем экземпляр класса
converter = ConvertMdToPdf()
# Тестовые примеры дробей
test_cases = [
r"$\frac{1}{2}$", # простая дробь
r"$\dfrac{3}{4}$", # дробь с displaystyle
r"$\frac{a}{b} + \frac{c}{d}$", # сложение дробей
r"$$\frac{x^2}{y^3}$$", # блочная дробь
r"$\frac{\partial f}{\partial x}$" # частная производная
]
for latex in test_cases:
result = converter.replace_math(re.search(r'\$\$(.*?)\$\$|\$(.*?)\$', latex))
print(f"Input: {latex}")
print(f"Output: {result}")
print("---")