Spaces:
Sleeping
Sleeping
| import os | |
| import gradio as gr | |
| from llama_index.llms.openrouter import OpenRouter | |
| from llama_index.core.llms import ChatMessage | |
| from llama_index.embeddings.huggingface import HuggingFaceEmbedding | |
| from llama_index.core import Settings, StorageContext, load_index_from_storage | |
| import nest_asyncio | |
| nest_asyncio.apply() | |
| # === Глобальная инициализация === | |
| embed_model = HuggingFaceEmbedding(model_name='intfloat/multilingual-e5-large-instruct') | |
| Settings.embed_model = embed_model | |
| Settings.llm = OpenRouter( | |
| api_key="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", | |
| model="deepseek/deepseek-r1-0528-qwen3-8b:free", | |
| max_tokens=10000, | |
| context_window=20000, | |
| ) | |
| system_prompt = """ | |
| Ты — эксперт по адаптации к изменениям климата. | |
| У тебя есть база знаний с кейсами и нормативными документами. | |
| Пользователь вводит запрос, связанный с климатическим риском в регионе или отрасли. | |
| Твоя задача — на основе информации из базы знаний предложить 2–3 релевантных адаптационных мероприятия, | |
| которые помогут снизить климатический риск, о котором спрашивает пользователь. | |
| ### Требования к ответу: | |
| 1. Представь результат **в виде Markdown-таблицы** с колонками: | |
| - Наименование мероприятий | |
| - Митигационный эффект | |
| - Адаптационный эффект | |
| - Актуальность для региона (указать с учётом контекста запроса). Если регион не указан, считай, что задается вопрос по Тюменской области | |
| - Ответственная организация (из региона) | |
| 2. Если источник данных, на которых ты основываешь ответ, известен (это URL и краткое название кейса), | |
| добавь их **ниже таблицы** в виде списка ссылок: | |
| `**Опорные источники:** [1] Наименовавание мероприятий - URL, [2] Наименовавание мероприятий - URL` | |
| 3. Пиши кратко, по существу, с акцентом на реальные, практические меры. | |
| 4. Если информация отсутствует — предложи логичные адаптационные меры на основе Приказа Минэкономразвития России от 13 мая 2021 г. № 267 «Об утверждении методических рекомендаций и показателей по вопросам адаптации к изменениям климата». | |
| Пример формата ответа: | |
| | Наименование мероприятий | Митигационный эффект | Адаптационный эффект | Актуальность для Тобольского района | Ответственная организация | | |
| |---------------------------|----------------------|----------------------|------------------------------------|----------------------------| | |
| | Развитие городского электротранспорта | снижение эмиссии | повышение устойчивости транспортной инфраструктуры | актуально | городские власти | | |
| | Перевод транспорта на газомоторное топливо | снижение эмиссии | рациональное использование ресурсов | реализуется частично | транспортные организации | | |
| **Опорные источники:** [1] Наименовавание мероприятий - https://example.com/case_12 | |
| Приводи только те источники, которые используешь для формирования таблицы непосредственно. URL приводи строго такое же, как указано в базе знаний. Наименование мероприятий бери из базы знаний | |
| Ответственную организацию в таблице указывай актуальную для региона, который пользователь указал в запросе | |
| """ | |
| def get_facts(user_question: str) -> str: | |
| try: | |
| if not user_question.strip(): | |
| return "Ошибка: пожалуйста, введите ваш запрос." | |
| storage_context = StorageContext.from_defaults(persist_dir="./storage") | |
| index = load_index_from_storage(storage_context) | |
| retriever = index.as_retriever(similarity_top_k=4) | |
| nodes = retriever.retrieve(user_question) | |
| context = "\n\n".join([node.get_content() for node in nodes]) if nodes else "Не найдено релевантных документов." | |
| full_system_prompt = system_prompt + f"\n\nКонтекст:\n{context}" | |
| messages = [ | |
| ChatMessage(role="system", content=full_system_prompt), | |
| ChatMessage(role="user", content="Пользовательский запрос: " + user_question) | |
| ] | |
| response = Settings.llm.chat(messages) | |
| return response.message.content | |
| except Exception as e: | |
| return f"Ошибка:\n{str(e)}" | |
| # === Кастомный CSS из style.css === | |
| custom_css = """ | |
| /* Импорт шрифта Inter */ | |
| @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap"); | |
| body { | |
| font-family: "Inter", sans-serif; | |
| margin: 0 !important; | |
| padding: 0 !important; | |
| display: flex !important; | |
| flex-direction: row !important; | |
| justify-content: space-between !important; | |
| height: 100vh !important; | |
| } | |
| .sidebar { | |
| width: 210px; | |
| height: 100vh; | |
| background-color: #f9f9f9; | |
| box-sizing: border-box; | |
| padding: 20px 16px !important; | |
| display: flex !important; | |
| flex-direction: column !important; | |
| justify-content: space-between !important; | |
| } | |
| .main { | |
| width: calc(100% - 210px) !important; | |
| height: 100vh !important; | |
| box-sizing: border-box; | |
| padding: 16px !important; | |
| position: relative !important; | |
| } | |
| .aside_img { | |
| height: 20px; | |
| cursor: pointer; | |
| } | |
| .logo { | |
| height: 40px; | |
| } | |
| .header_img { | |
| height: 30px; | |
| cursor: pointer; | |
| } | |
| .search { | |
| height: 40px; | |
| border: none; | |
| border-radius: 15px; | |
| box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25); | |
| box-sizing: border-box; | |
| font-size: 16px; | |
| width: 100% !important; | |
| padding-left: 10px !important; | |
| } | |
| .search_icon { | |
| position: absolute !important; | |
| right: 15px !important; | |
| bottom: 10px !important; | |
| height: 20px !important; | |
| cursor: pointer !important; | |
| } | |
| .scroll_container { | |
| overflow-y: auto; | |
| height: 70vh; | |
| margin-top: 12px; | |
| } | |
| .scroll_header { | |
| font-size: 18px; | |
| font-weight: 500; | |
| color: #333333; | |
| margin-top: 12px !important; | |
| margin-bottom: 0 !important; | |
| } | |
| .fade_text { | |
| white-space: nowrap; | |
| overflow: hidden; | |
| font-size: 16px; | |
| font-weight: 300; | |
| position: relative; | |
| width: 100% !important; | |
| } | |
| .fade_text::after { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| right: 0; | |
| width: 50px; | |
| height: 100%; | |
| background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, #f9f9f9 100%); | |
| } | |
| .input_button { | |
| height: 56px; | |
| background-color: #fff; | |
| border: none; | |
| border-radius: 15px; | |
| box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.25); | |
| display: flex !important; | |
| justify-content: center !important; | |
| align-items: center !important; | |
| gap: 16px !important; | |
| } | |
| .input_button button { | |
| background-color: #fff !important; | |
| border: none !important; | |
| font-size: 16px !important; | |
| font-weight: bold !important; | |
| color: #6e6e6e !important; | |
| padding: 0 !important; | |
| margin: 0 !important; | |
| } | |
| .welcome { | |
| width: 80%; | |
| max-width: 930px; | |
| margin: auto; | |
| position: absolute !important; | |
| top: 59%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| } | |
| #prompt_field textarea { | |
| font-family: "Inter", sans-serif !important; | |
| height: 180px !important; | |
| max-height: 180px !important; | |
| width: 90% !important; | |
| border: none !important; | |
| border-radius: 15px !important; | |
| box-shadow: 0px 2px 7px 0px rgba(0, 0, 0, 0.25) !important; | |
| font-size: 16px !important; | |
| resize: none !important; | |
| padding: 16px !important; | |
| padding-bottom: 70px !important; | |
| } | |
| #prompt_field textarea:focus { | |
| outline: none !important; | |
| } | |
| .prompt_buttons { | |
| width: 87% !important; | |
| background-color: white !important; | |
| border-radius: 0 !important; | |
| position: absolute !important; | |
| bottom: 0 !important; | |
| padding: 8px 8px !important; | |
| display: flex !important; | |
| justify-content: flex-end !important; | |
| } | |
| .prompt_buttons::before { | |
| content: ""; | |
| position: absolute; | |
| top: -10px; | |
| left: 0; | |
| width: 100%; | |
| height: 15px; | |
| background: linear-gradient(to top, rgb(255, 255, 255), rgba(255, 255, 255, 0.6)); | |
| pointer-events: none; | |
| } | |
| .prompt_buttons button { | |
| height: 40px !important; | |
| width: 40px !important; | |
| background: none !important; | |
| border: none !important; | |
| cursor: pointer !important; | |
| padding: 0 !important; | |
| } | |
| .user_prompt { | |
| background-color: #efefef; | |
| border-radius: 15px; | |
| padding: 12px !important; | |
| margin-bottom: 16px !important; | |
| width: 75% !important; | |
| margin-left: auto !important; | |
| } | |
| .answer { | |
| border: none; | |
| border-radius: 15px; | |
| box-shadow: 0px 2px 7px 0px rgba(0, 0, 0, 0.25); | |
| padding: 12px !important; | |
| width: 75% !important; | |
| margin-right: auto !important; | |
| } | |
| .prompt_chat { | |
| font-size: 20px !important; | |
| margin: 0 !important; | |
| } | |
| /* Скрыть стандартные элементы Gradio */ | |
| #component-0, #component-1, #component-2 { | |
| display: none !important; | |
| } | |
| /* Скрыть заголовки и кнопки по умолчанию */ | |
| .gradio-container { | |
| background: white !important; | |
| } | |
| #submit_prompt { | |
| background: none !important; | |
| border: none !important; | |
| font-size: 0 !important; | |
| width: 40px !important; | |
| height: 40px !important; | |
| cursor: pointer !important; | |
| position: relative !important; | |
| } | |
| #submit_prompt::after { | |
| content: "➤"; | |
| font-size: 24px !important; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: black; | |
| } | |
| """ | |
| # === Gradio UI === | |
| with gr.Blocks(css=custom_css, theme=gr.themes.Default()) as demo: | |
| # Сайдбар | |
| with gr.Column(elem_classes="sidebar"): | |
| with gr.Row(): | |
| gr.Image(value="icons/arrow.png", elem_classes="aside_img", interactive=False) | |
| with gr.Row(): | |
| gr.Image(value="icons/bin.png", elem_classes="aside_img", interactive=False) | |
| gr.Image(value="icons/chat.png", elem_classes="aside_img ms-2", interactive=False) | |
| with gr.Row(): | |
| gr.Textbox(placeholder="Поиск", elem_classes="search") | |
| gr.Image(value="icons/search.png", elem_classes="search_icon", interactive=False) | |
| with gr.Column(elem_classes="scroll_container"): | |
| gr.Markdown("### 17 сентября", elem_classes="scroll_header") | |
| gr.Markdown("План мероприятий для предовтращения пожаров", elem_classes="fade_text") | |
| gr.Markdown("### 15 сентября", elem_classes="scroll_header") | |
| gr.Markdown("Оценка опасности подъема уровня выбросов", elem_classes="fade_text") | |
| gr.Markdown("### 7 сентября", elem_classes="scroll_header") | |
| gr.Markdown("Построение адаптивных мероприятий для павод", elem_classes="fade_text") | |
| with gr.Row(elem_classes="input_button"): | |
| gr.Image(value="icons/question.png", elem_classes="input_img", interactive=False) | |
| gr.Button("Помощь", elem_id="help-btn") | |
| # Основная область | |
| with gr.Column(elem_classes="main"): | |
| with gr.Row(): | |
| gr.Image(value="icons/logo.png", elem_classes="logo", interactive=False) | |
| with gr.Row(): | |
| gr.Image(value="icons/menu.png", elem_classes="header_img", interactive=False) | |
| gr.Image(value="icons/account.png", elem_classes="header_img", interactive=False) | |
| with gr.Column(elem_classes="welcome"): | |
| user_input = gr.Textbox( | |
| label="", | |
| lines=5, | |
| max_lines=5, | |
| placeholder="Введите запрос", | |
| elem_id="prompt_field" | |
| ) | |
| with gr.Row(elem_classes="prompt_buttons"): | |
| submit_btn = gr.Button("➤", elem_id="submit_prompt") | |
| answer_output = gr.Markdown(elem_classes="answer", value="") | |
| # Обработка | |
| submit_btn.click(fn=get_facts, inputs=user_input, outputs=answer_output) | |
| demo.launch() |