import gradio as gr import pandas as pd import os import random import re import pymorphy3 from openai import OpenAI XAI_API_KEY = os.getenv("XAI_API_KEY") client = OpenAI( api_key=XAI_API_KEY, base_url="https://api.x.ai/v1", ) def load_dropdown_data(file_path, sheet_name, column_name): data = pd.read_excel(file_path, sheet_name=sheet_name) return data[column_name].dropna().unique().tolist() file_path = "Исходные данные.xlsx" products_list, data_products = load_dropdown_data(file_path, "Продукты", "Наименование продукта"), pd.read_excel(file_path, sheet_name="Продукты") # Добавляем "Свой продукт" в начало списка products = ["Свой продукт"] + list(products_list) genders_data = pd.read_excel(file_path, sheet_name="Пол") generations_data = pd.read_excel(file_path, sheet_name="Поколение") psychotypes_data = pd.read_excel(file_path, sheet_name="Психотип") business_stages_data = pd.read_excel(file_path, sheet_name="Стадия бизнеса") industries_data = pd.read_excel(file_path, sheet_name="Отрасль") opfs_data = pd.read_excel(file_path, sheet_name="ОПФ") genders = genders_data["Пол"].dropna().unique().tolist() generations = generations_data["Поколение"].dropna().unique().tolist() psychotypes = psychotypes_data["Психотип"].dropna().unique().tolist() business_stages = business_stages_data["Стадия бизнеса"].dropna().unique().tolist() industries = industries_data["Отрасль"].dropna().unique().tolist() opfs = opfs_data["ОПФ"].dropna().unique().tolist() approaches_data = pd.read_excel(file_path, sheet_name="Подход") approach_dict = { "Указание на пользу": { "prefix": "Начни SMS с указания на пользу продукта. Используй глагол в побудительном наклонении. Не начинай с вопроса", "suffix": "Убедись, что SMS начинается с указания на пользу продукта и использования глагола в побудительном наклонении и не начинается с вопроса" }, "Вопрос": { "prefix": "Начни сообщение с вопроса, который указывает на пользу продукта для клиента", "suffix": "Убедись, что готовый текст начинается с вопроса, который указывает на пользу продукта для клиента" }, "Призыв к действию": { "prefix": "Начни SMS с призыва к действию с продуктом. Не начинай с вопроса", "suffix": "Убедись, что готовый текст начинается с призыва к действию с продуктом и не начинается с вопроса" } } def fill_product_details(selected_product, data): # Если выбран "Свой продукт", поля очищаем и даём редактировать if selected_product == "Свой продукт": return gr.update(value="", interactive=True), \ gr.update(value="", interactive=True), \ gr.update(value="", interactive=True), \ gr.update(value="", interactive=True) else: # Иначе заполняем из файла и делаем поля неактивными if selected_product and selected_product in data["Наименование продукта"].values: product_row = data[data["Наименование продукта"] == selected_product].iloc[0] return (gr.update(value=product_row.get("Описание предложения", ""), interactive=False), gr.update(value=product_row.get("Наименование продукта", ""), interactive=False), gr.update(value=product_row.get("Преимущества", ""), interactive=False), gr.update(value=product_row.get("Ключевое сообщение", ""), interactive=False)) else: return gr.update(value="", interactive=False), \ gr.update(value="", interactive=False), \ gr.update(value="", interactive=False), \ gr.update(value="", interactive=False) def get_approaches(gender, generation, psychotype, approaches_df): if approaches_df is None or approaches_df.empty: return "Подход не найден для выбранных параметров." filters = [] for param_name, param_value in [('Пол', gender), ('Поколение', generation), ('Психотип', psychotype)]: if not param_value or param_value == "Не выбрано": filters.append(approaches_df[param_name].isnull() | (approaches_df[param_name].fillna('') == '')) else: filters.append(approaches_df[param_name].fillna('') == param_value) combined_filter = filters[0] for f in filters[1:]: combined_filter &= f matching_rows = approaches_df[combined_filter] if matching_rows.empty: return "Подход не найден для выбранных параметров." approach_list = [] for approaches in matching_rows['Подход']: approach_names = [a.strip() for a in str(approaches).split(',')] approach_list.extend(approach_names) approach_list = list(set(approach_list)) return ', '.join(approach_list) def get_instructions_for_param(param_value, df, col): if not param_value or param_value == "Не выбрано": return None row = df[df[col] == param_value] if row.empty: return None instr1 = row.iloc[0].get("Инструкция 1", "") if not instr1.strip(): return None return instr1 def format_instruction_string(instr): terms = [t.strip() for t in instr.split(',') if t.strip()] return " или ".join(terms) if terms else "" def generate_display_prompts(description, product_name, benefits, key_message, chosen_approach, gender, generation, psychotype, business_stage, industry, opf): if chosen_approach == "Подход не найден для выбранных параметров.": return ("Для формирования промпта выберите хотя бы один личный персональный параметр для определения подхода", "Для формирования промпта выберите хотя бы один личный персональный параметр для определения подхода") approach_list = [a.strip() for a in chosen_approach.split(',') if a.strip()] prefix_parts = [] suffix_parts = [] for a in approach_list: if a in approach_dict: prefix_parts.append(approach_dict[a]["prefix"]) suffix_parts.append(approach_dict[a]["suffix"]) if len(prefix_parts) > 1: approach_prefix = " / ".join(prefix_parts) approach_suffix = " / ".join(suffix_parts) else: approach_prefix = prefix_parts[0] if prefix_parts else "" approach_suffix = suffix_parts[0] if suffix_parts else "" instructions_data = [ (gender, genders_data, "Пол"), (generation, generations_data, "Поколение"), (psychotype, psychotypes_data, "Психотип"), (business_stage, business_stages_data, "Стадия бизнеса"), (industry, industries_data, "Отрасль"), (opf, opfs_data, "ОПФ") ] chosen_params_instructions = [] for (param_value, df, col) in instructions_data: instr1 = get_instructions_for_param(param_value, df, col) if instr1: chosen_params_instructions.append(instr1) if not chosen_params_instructions: return ("Для формирования промпта выберите хотя бы один личный персональный параметр для определения подхода", "Для формирования промпта выберите хотя бы один личный персональный параметр для определения подхода") lines = [] for i, instr_line in enumerate(chosen_params_instructions, start=1): formatted_line = format_instruction_string(instr_line) lines.append(f"{i}. {formatted_line}.") mandatory_terms = "\n".join(lines) prompt = f"""Напиши три или четыре предложения. {approach_prefix}. Напиши рекламное SMS для следующего продукта: «{description}». Не изменяй название продукта: «{product_name}». Преимущества: «{benefits}». ОБЯЗАТЕЛЬНО используй в SMS КАЖДЫЙ из следующих терминов: {mandatory_terms} Убедись, что написал не меньше трех и не больше четырех предложений. {approach_suffix}. Убедись, что УМЕСТНО использовал КАЖДЫЙ необходимый термин. Убедись, что в SMS без изменений, синонимов и перестановок слов используется наименование продукта: «{product_name}». Убедись, что в SMS есть следующая ключевая информация: «{key_message}».""" return prompt, prompt def generate_model_prompt(description, product_name, benefits, key_message, gender, generation, psychotype, business_stage, industry, opf, single_approach): prefix = approach_dict[single_approach]["prefix"] suffix = approach_dict[single_approach]["suffix"] instructions_data = [ (gender, genders_data, "Пол"), (generation, generations_data, "Поколение"), (psychotype, psychotypes_data, "Психотип"), (business_stage, business_stages_data, "Стадия бизнеса"), (industry, industries_data, "Отрасль"), (opf, opfs_data, "ОПФ") ] chosen_params_instructions = [] for (param_value, df, col) in instructions_data: instr1 = get_instructions_for_param(param_value, df, col) if instr1: chosen_params_instructions.append(instr1) if chosen_params_instructions: lines = [] for i, instr_line in enumerate(chosen_params_instructions, start=1): formatted_line = format_instruction_string(instr_line) lines.append(f"{i}. {formatted_line}.") mandatory_terms = "\n".join(lines) else: mandatory_terms = None if mandatory_terms: model_prompt = f"""Напиши три или четыре предложения. {prefix}. Напиши рекламное SMS для следующего продукта: «{description}». Не изменяй название продукта: «{product_name}». Преимущества: «{benefits}». ОБЯЗАТЕЛЬНО используй в SMS КАЖДЫЙ из следующих терминов: {mandatory_terms} Убедись, что написал не меньше трех и не больше четырех предложений. {suffix}. Убедись, что УМЕСТНО использовал КАЖДЫЙ необходимый термин. Убедись, что в SMS без изменений, синонимов и перестановок слов используется наименование продукта: «{product_name}». Убедись, что в SMS есть следующая ключевая информация: «{key_message}».""" else: model_prompt = "" return model_prompt def call_model(model_prompt): completion = client.chat.completions.create( model="grok-2-1212", messages=[ {"role": "system", "content": "You are a world-class expert in creating personalized SMS who returns only the SMS and nothing else."}, {"role": "user", "content": model_prompt}, ], ) return completion.choices[0].message.content.strip() # Функция для корректировки def correct_dash_usage(text): morph = pymorphy3.MorphAnalyzer() text = re.sub(r'\s[-–—]\s', ' — ', text) text = re.sub(r'(?<=\d)[-–—](?=\d)', '–', text) text = re.sub(r'(?<=[a-zA-Zа-яА-Я0-9])[-–—](?=[a-zA-Zа-яА-Я0-9])', '-', text) text = re.sub(r'"([^\"]+)"', r'«\1»', text) if text.count('"') == 1: text = text.replace('"', '') if (text.startswith('"') and text.endswith('"')) or (text.startswith('«') and text.endswith('»')): text = text[1:-1].strip() text = re.sub(r'(\d+)[kкКK]', r'\1 000', text, flags=re.IGNORECASE) greeting_patterns = [ r"привет\b", r"здравствуй", r"добрый\s(день|вечер|утро)", r"дорогой\b", r"уважаемый\b", r"дорогая\b", r"уважаемая\b", r"господин\b", r"госпожа\b", r"друг\b", r"коллега\b", r"товарищ\b", r"приятель\b", r"подруга\b" ] def is_greeting_sentence(sentence): words = sentence.split() if len(words) < 5: for word in words: parsed = morph.parse(word.lower())[0] for pattern in greeting_patterns: if re.search(pattern, parsed.normal_form): return True return False sentences = re.split(r'(?<=[.!?])\s+', text) if sentences and is_greeting_sentence(sentences[0]): sentences = sentences[1:] text = ' '.join(sentences) def restore_yo(text): morph2 = pymorphy3.MorphAnalyzer() words = text.split() restored_words = [] for word in words: if word.isupper(): restored_words.append(word) continue if word.lower() == "все": restored_words.append(word) continue parsed = morph2.parse(word)[0] restored_word = parsed.word if word and word[0].isupper(): restored_word = restored_word.capitalize() restored_words.append(restored_word) return ' '.join(restored_words) text = restore_yo(text) text = re.sub(r'\bИп\b', 'ИП', text, flags=re.IGNORECASE) text = re.sub(r'\bОоо\b', 'ООО', text, flags=re.IGNORECASE) text = re.sub(r'\bРф\b', 'РФ', text, flags=re.IGNORECASE) text = re.sub(r'\bпользовуйтесь\b', 'пользуйтесь', text, flags=re.IGNORECASE) text = re.sub(r'\bею\b', 'ей', text, flags=re.IGNORECASE) text = re.sub(r'\bповышьте\b', 'повысьте', text, flags=re.IGNORECASE) text = re.sub(r'\bСбербизнес(?:а|е)?\b', 'СберБизнес', text, flags=re.IGNORECASE) text = re.sub(r'\bСбербанк\b', 'СберБанк', text, flags=re.IGNORECASE) text = re.sub(r'\bвашего ООО\b', 'вашей компании', text, flags=re.IGNORECASE) text = re.sub(r'\b0₽\b', '0 р', text, flags=re.IGNORECASE) text = re.sub(r'\b₽\b', 'р', text, flags=re.IGNORECASE) text = re.sub(r'\bруб\.(?=\W|$)', 'р', text, flags=re.IGNORECASE) text = re.sub(r'\bруб(?:ля|лей)\b', 'р', text, flags=re.IGNORECASE) text = re.sub(r'(\d+)\s+тысяч(?:а|и)?(?:\s+рублей)?', r'\1 000 р', text, flags=re.IGNORECASE) text = re.sub(r'(\d+)\s*тыс\.\s*руб\.', r'\1 000 р', text, flags=re.IGNORECASE) text = re.sub(r'(\d+)\s*тыс\.\s*р\.?', r'\1 000 р', text, flags=re.IGNORECASE) text = re.sub(r'(\d+)\s+миллиона\b|\bмиллионов\b', r'\1 млн', text, flags=re.IGNORECASE) text = re.sub(r'(\d+)\s*млн\s*руб\.', r'\1 млн р', text, flags=re.IGNORECASE) text = re.sub(r'(\d+)\s*р\b', r'\1 р', text) def remove_specific_sentences(text): sents = re.split(r'(?<=[.!?])\s+', text) filtered = [s for s in sents if not re.search(r'\bникаких\s+(посещений|визитов)\b', s, re.IGNORECASE)] return ' '.join(filtered) text = re.sub(r'\b(\d+)\s+000\s+000\s*р\b', r'\1 млн р', text, flags=re.IGNORECASE) text = re.sub(r' р р ', r' р ', text, flags=re.IGNORECASE) text = remove_specific_sentences(text) return text def clean_message(message): if not message.endswith(('.', '!', '?')): last_period = max(message.rfind('.'), message.rfind('!'), message.rfind('?')) if last_period != -1: message = message[:last_period + 1] return message def generate_message_with_retry(model_prompt): # До 10 попыток, чтобы длина была от 160 до 250 символов last_message = "" for _ in range(10): msg = call_model(model_prompt) msg = correct_dash_usage(msg) msg = clean_message(msg) length = len(msg) if 160 <= length <= 250: # Добавляем информацию о количестве знаков msg += f"\n\n------\nКоличество знаков: {length}" return msg last_message = msg # Если не удалось подобрать длину length = len(last_message) last_message += f"\n\n------\nКоличество знаков: {length}" return last_message def update_prompts_on_params_change(description, product_name, benefits, key_message, gender, generation, psychotype, business_stage, industry, opf): chosen_approach = get_approaches(gender, generation, psychotype, approaches_data) prompt_1, prompt_2 = generate_display_prompts(description, product_name, benefits, key_message, chosen_approach, gender, generation, psychotype, business_stage, industry, opf) return chosen_approach, prompt_1, prompt_2 def generate_personalized_sms(description, product_name, benefits, key_message, gender, generation, psychotype, business_stage, industry, opf, chosen_approach, prompt_1, prompt_2): # Если нет персональных параметров или подхода if "Для формирования промпта выберите хотя бы один личный персональный параметр" in prompt_1 or chosen_approach == "Подход не найден для выбранных параметров.": gr.Warning("Задайте хотя бы один личный персональный параметр для определения подхода, чтобы был сформирован промпт") return "", "" approach_list = [a.strip() for a in chosen_approach.split(',') if a.strip()] if not approach_list: gr.Warning("Задайте хотя бы один личный персональный параметр для определения подхода, чтобы был сформирован промпт") return "", "" chosen_single_approach_1 = random.choice(approach_list) if len(approach_list) > 1 else approach_list[0] chosen_single_approach_2 = random.choice(approach_list) if len(approach_list) > 1 else approach_list[0] model_prompt_1 = generate_model_prompt(description, product_name, benefits, key_message, gender, generation, psychotype, business_stage, industry, opf, chosen_single_approach_1) model_prompt_2 = generate_model_prompt(description, product_name, benefits, key_message, gender, generation, psychotype, business_stage, industry, opf, chosen_single_approach_2) sms_1 = generate_message_with_retry(model_prompt_1) sms_2 = generate_message_with_retry(model_prompt_2) return sms_1, sms_2 with gr.Blocks(theme="default") as demo: gr.Markdown("**Процент созданных SMS по выбранному продукту**") progress_bar_html = """
0%
""" gr.HTML(progress_bar_html) with gr.Row(): with gr.Column(scale=1): gr.Markdown("**Продукт**") product_dropdown = gr.Dropdown(label="Продукт", choices=products, value=products[0]) description = gr.Textbox(label="Описание предложения", lines=5, value="", interactive=True) product_name = gr.Textbox(label="Наименование продукта", lines=1, value="", interactive=True) benefits = gr.Textbox(label="Преимущества", lines=9, value="", interactive=True) key_message = gr.Textbox(label="Ключевое сообщение", lines=2, value="", interactive=True) product_dropdown.change( fn=lambda selected: fill_product_details(selected, data_products), inputs=[product_dropdown], outputs=[description, product_name, benefits, key_message] ) with gr.Column(scale=1): gr.Markdown("**Клиент**") gender_dropdown = gr.Dropdown(label="Пол", choices=["Не выбрано"]+genders, value=None) generation_dropdown = gr.Dropdown(label="Поколение", choices=["Не выбрано"]+generations, value=None) psychotype_dropdown = gr.Dropdown(label="Психотип", choices=["Не выбрано"]+psychotypes, value=None) business_stage_dropdown = gr.Dropdown(label="Стадия бизнеса", choices=["Не выбрано"]+business_stages, value=None) industry_dropdown = gr.Dropdown(label="Отрасль", choices=["Не выбрано"]+industries, value=None) opf_dropdown = gr.Dropdown(label="ОПФ", choices=["Не выбрано"]+opfs, value=None) chosen_approach = gr.Textbox(label="Выбранный подход", lines=1, value="", interactive=False) presence_in_db = gr.Textbox(label="Комментарий", lines=1, value="", interactive=False) with gr.Row(): return_params_btn = gr.Button("Вернуть параметры предыдущего запроса") set_unused_params_btn = gr.Button("Задать ранее невыставленные параметры") create_personal_sms_btn = gr.Button("Создать персонализированное SMS") with gr.Row(): with gr.Column(): model_1_name = gr.Textbox(label="Модель 1", value="Grok-2-1212", interactive=False) prompt_1 = gr.Textbox(label="Промпт 1", value="", interactive=False, lines=10) sms_1 = gr.Textbox(label="SMS 1", lines=3, value="", interactive=False) with gr.Column(): model_2_name = gr.Textbox(label="Модель 2", value="Grok-2-1212", interactive=False) prompt_2 = gr.Textbox(label="Промпт 2", value="", interactive=False, lines=10) sms_2 = gr.Textbox(label="SMS 2", lines=3, value="", interactive=False) with gr.Row(): prefer_sms_1_btn = gr.Button("Я предпочитаю это SMS") prefer_sms_2_btn = gr.Button("Я предпочитаю это SMS") regen_btn = gr.Button("Перегенерировать SMS (не нравится ни одно из SMS)") with gr.Row(): comment_sms_1 = gr.Textbox(label="Комментарий к SMS 1", lines=2, value="") comment_sms_2 = gr.Textbox(label="Комментарий к SMS 2", lines=2, value="") with gr.Row(): corrected_sms_1 = gr.Textbox(label="Откорректированное SMS 1", lines=3, value="") corrected_sms_2 = gr.Textbox(label="Откорректированное SMS 2", lines=3, value="") with gr.Row(): save_sms_1_btn = gr.Button("Сохранить в базу") save_sms_2_btn = gr.Button("Сохранить в базу") # Обновляем промпты при изменении параметров клиента client_params = [gender_dropdown, generation_dropdown, psychotype_dropdown, business_stage_dropdown, industry_dropdown, opf_dropdown] for cp in client_params: cp.change( fn=update_prompts_on_params_change, inputs=[description, product_name, benefits, key_message, gender_dropdown, generation_dropdown, psychotype_dropdown, business_stage_dropdown, industry_dropdown, opf_dropdown], outputs=[chosen_approach, prompt_1, prompt_2] ) # Генерация SMS по нажатию кнопки create_personal_sms_btn.click( fn=generate_personal_sms, inputs=[description, product_name, benefits, key_message, gender_dropdown, generation_dropdown, psychotype_dropdown, business_stage_dropdown, industry_dropdown, opf_dropdown, chosen_approach, prompt_1, prompt_2], outputs=[sms_1, sms_2] ) demo.queue().launch()