| import streamlit as st |
| from hazm import Normalizer, SentenceTokenizer |
| import os |
| import docx |
| from langchain.chat_models import init_chat_model |
| from langchain.messages import SystemMessage, HumanMessage, AIMessage |
| from sentence_transformers import SentenceTransformer |
| from rapidfuzz import fuzz |
| import concurrent.futures |
| import time |
| |
| import numpy as np |
| from hazm import * |
| import re |
| import nltk |
| import json |
| import requests |
| from sklearn.metrics.pairwise import cosine_similarity |
| model11 = SentenceTransformer("all-MiniLM-L6-v2") |
|
|
| EMBEDDING_MODEL = "text-embedding-3-large" |
|
|
|
|
| nltk.download('punkt') |
| st.set_page_config( |
| page_title="رزم یار", |
| page_icon="⚔️", |
| layout="wide" |
| ) |
|
|
| st.markdown(""" |
| <style> |
| .stAppHeader.st-emotion-cache-12fmjuu.e4hpqof0 { |
| background-color: rgba(46,59,46, 0.8) !important; |
| color: #2e3b2e !important; |
| font-family: 'Vazirmatn', Tahoma, sans-serif !important; |
| padding: 20px !important; |
| border-radius: 10px !important; |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3) !important; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
| st.markdown(""" |
| <style> |
| @font-face { |
| font-family: 'Roboto'; |
| src: url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap') format('woff2'); |
| font-weight: 400; |
| font-style: normal; |
| } |
| html, body, [class*="css"] { |
| font-family: 'Roboto', Tahoma, sans-serif !important; |
| font-weight: 400 !important; |
| direction: rtl; |
| text-align: right; |
| color: #ffffff; |
| } |
| .stApp { |
| background: linear-gradient(to left, #4b5e40, #2e3b2e); |
| color: #ffffff; |
| } |
| [data-testid="stSidebar"] { |
| width: 260px !important; |
| background-color: #1a2b1e; |
| border: none !important; |
| padding-top: 20px; |
| } |
| .menu-item { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 12px 20px; |
| font-size: 16px; |
| font-weight: 600; |
| color: #d4d4d4; |
| cursor: pointer; |
| transition: background-color 0.3s ease; |
| } |
| .menu-item:hover { |
| background-color: #2e3b2e; |
| color: #b8860b; |
| } |
| .menu-item img { |
| width: 25px; |
| height: 25px; |
| } |
| .stButton>button { |
| background-color: #b8860b !important; |
| color: #1a2b1e !important; |
| font-family: 'Roboto', Tahoma, sans-serif; |
| font-weight: 700 !important; |
| border-radius: 10px; |
| padding: 12px 24px; |
| border: none; |
| transition: all 0.3s ease; |
| font-size: 16px; |
| width: 100%; |
| margin: 10px 0; |
| } |
| .stButton>button:hover { |
| background-color: #8b6508 !important; |
| transform: translateY(-2px); |
| box-shadow: 0 4px 8px rgba(0,0,0,0.3); |
| } |
| .header-text { |
| text-align: center; |
| margin: 20px 0; |
| background-color: rgba(26, 43, 30, 0.9); |
| padding: 25px; |
| border-radius: 15px; |
| box-shadow: 0 6px 12px rgba(0,0,0,0.4); |
| font-family: 'Roboto', Tahoma, sans-serif; |
| } |
| .subtitle { |
| font-size: 18px; |
| color: #d4d4d4; |
| font-weight: 600; |
| margin-top: 10px; |
| } |
| .chat-message { |
| flex-wrap: wrap; |
| background-color: rgba(26, 43, 30, 0.95); |
| border: 2px solid #b8860b; |
| border-radius: 15px; |
| padding: 20px; |
| margin: 15px 0; |
| box-shadow: 0 6px 12px rgba(0,0,0,0.3); |
| animation: fadeIn 0.6s ease; |
| font-size: 18px; |
| color: #d4d4d4; |
| font-weight: 600; |
| display: flex; |
| flex-wrap: wrap; |
| align-items: center; |
| gap: 15px; |
| } |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .stTextInput>div>input, .stTextArea textarea { |
| background-color: rgba(26, 43, 30, 0.95) !important; |
| border-radius: 10px !important; |
| border: 1px solid #b8860b !important; |
| padding: 12px !important; |
| font-family: 'Roboto', Tahoma; |
| font-weight: 500; |
| font-size: 16px; |
| color: #d4d4d4 !important; |
| } |
| hr { |
| border: 1px solid #b8860b; |
| margin: 15px 0; |
| } |
| [data-testid="stSidebar"] > div { |
| border: none !important; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| if "authenticated" not in st.session_state: |
| st.session_state.authenticated = False |
|
|
| if not st.session_state.authenticated: |
| st.markdown('<style>.stTextInput > div[data-baseweb="input"] + div, .stTextInput div:has(div[role="alert"]) { display: none !important; }</style>', unsafe_allow_html=True) |
| st.markdown(""" |
| <style> |
| input { |
| background-color: #2e3b2e; |
| color: gold; |
| border: 1px solid gold; |
| border-radius: 10px; |
| padding: 10px; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
| st.markdown(""" |
| <style> |
| html, body, [class*="css"] { |
| font-family: 'Vazir', sans-serif; |
| } |
| label { |
| font-size: 20px !important; |
| color: #ffffff !important; |
| font-weight: 800 !important; |
| margin-bottom: 10px !important; |
| display: block; |
| } |
| input[type="text"], |
| input[type="password"], |
| input[type="text"]:focus, |
| input[type="password"]:focus, |
| input[type="text"]:hover, |
| input[type="password"]:hover { |
| background-color: #ffffff !important; |
| color: #000000 !important; |
| font-size: 18px !important; |
| font-family: 'Vazir', sans-serif !important; |
| } |
| ::placeholder { |
| color: #bbbbbb !important; |
| opacity: 0.8 !important; |
| font-size: 16px; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| username = st.text_input("نام کاربری:", placeholder="شناسه خود را وارد کنید", |
| label_visibility="visible") |
| password = st.text_input("رمز عبور:", placeholder="رمز عبور ", type="password", |
| label_visibility="visible") |
| st.markdown(""" |
| <style> |
| div.stButton > button { |
| background-image: url("https://upload.wikimedia.org/wikipedia/commons/5/59/US_Army_Universal_Camouflage_Pattern.jpg"); |
| background-size: cover; |
| background-repeat: no-repeat; |
| background-position: center; |
| color: #f5deb3; |
| font-family: 'Vazir', sans-serif; |
| font-size: 20px; |
| font-weight: bold; |
| padding: 14px 35px; |
| border: 2px solid #d4af37; |
| border-radius: 14px; |
| box-shadow: 0 0 18px rgba(0,0,0,0.6); |
| transition: all 0.3s ease-in-out; |
| } |
| div.stButton > button:hover { |
| filter: brightness(1.2); |
| box-shadow: 0 0 22px #b8860b; |
| transform: scale(1.03); |
| } |
| div.stButton > button:active { |
| transform: scale(0.97); |
| box-shadow: 0 0 12px #000; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| if st.button("ورود"): |
| if username == "admin" and password == "123": |
| st.session_state.authenticated = True |
| st.rerun() |
| else: |
| st.markdown(""" |
| <div style="background-color: rgba(241, 196, 15, 0.6); color: #2e3b2e; padding: 10px; border-radius: 10px; border: 2px solid #2e3b2e; margin-top: 20px; text-align: center; backdrop-filter: blur(5px);"> |
| نام کاربری یا رمز عبور اشتباه است. |
| </div> |
| """, unsafe_allow_html=True) |
| st.stop() |
|
|
| with st.sidebar: |
| st.image("log.png", use_container_width=True) |
|
|
| menu_items = [ |
| ("گزارش عملیاتی", "https://cdn-icons-png.flaticon.com/512/3596/3596165.png", "https://m17idd-reporting.hf.space"), |
| ("تاریخچه ماموریتها", "https://cdn-icons-png.flaticon.com/512/709/709496.png", None), |
| ("تحلیل دادههای نظامی", "https://cdn-icons-png.flaticon.com/512/1828/1828932.png", "https://m17idd-test.hf.space"), |
| ("مدیریت منابع", "https://cdn-icons-png.flaticon.com/512/681/681494.png", None), |
| ("دستیار فرماندهی", "https://cdn-icons-png.flaticon.com/512/3601/3601646.png", None), |
| ("تنظیمات امنیتی", "https://cdn-icons-png.flaticon.com/512/2099/2099058.png", None), |
| ("پشتیبانی فنی", "https://cdn-icons-png.flaticon.com/512/597/597177.png", None), |
| ] |
|
|
| st.markdown(""" |
| <link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v30.1.0/dist/font-face.css" rel="stylesheet" type="text/css" /> |
| """, unsafe_allow_html=True) |
|
|
| for idx, (text, icon, link) in enumerate(menu_items): |
| content = f""" |
| <div class="menu-item" style="display: flex; align-items: center; margin-bottom: 10px;"> |
| <img src="{icon}" width="20" height="20" style="margin-left: 10px;" /> |
| <span style="color: white; font-family: 'Vazir', sans-serif; font-weight: bold;">{text}</span> |
| </div> |
| """ |
|
|
| if link: |
| content = f'<a href="{link}" target="_blank" style="text-decoration: none;">{content}</a>' |
|
|
| st.markdown(content, unsafe_allow_html=True) |
|
|
| if idx in [1, 3, 5]: |
| st.markdown("<hr style='border-top: 1px solid #555;'/>", unsafe_allow_html=True) |
|
|
| |
| st.markdown(""" |
| <style> |
| .header-text { |
| text-align: center; |
| margin: 50px 0; |
| background: #2e3b2e; |
| padding: 60px 30px; |
| border-radius: 25px; |
| box-shadow: 0 12px 24px rgba(0, 0, 0, 0.8); |
| animation: slideIn 2s ease-in-out, fadeIn 3s ease-in-out; |
| background-size: cover; |
| background-position: center; |
| position: relative; |
| } |
| @keyframes fadeIn { |
| 0% { opacity: 0; transform: translateY(30px); } |
| 100% { opacity: 1; transform: translateY(0); } |
| } |
| @keyframes slideIn { |
| 0% { transform: translateX(-50%); opacity: 0; } |
| 100% { transform: translateX(0); opacity: 1; } |
| } |
| .header-text h1 { |
| font-family: 'Vazir', sans-serif; |
| font-size: 62px; |
| color: #d89b00; |
| margin: 0; |
| font-weight: 900; |
| letter-spacing: 4px; |
| text-shadow: 4px 4px 15px rgba(0, 0, 0, 0.9); |
| transform: scale(1.08); |
| animation: glow 2s ease-in-out infinite alternate; |
| } |
| .subtitle { |
| font-family: 'Vazir', sans-serif; |
| font-size: 24px; |
| color: #f8f8f8; |
| font-weight: 700; |
| margin-top: 15px; |
| letter-spacing: 2px; |
| text-shadow: 3px 3px 10px rgba(0,0,0,0.8); |
| animation: fadeInSubtitle 2s ease-in-out; |
| } |
| @keyframes fadeInSubtitle { |
| 0% { opacity: 0; transform: translateY(20px); } |
| 100% { opacity: 1; transform: translateY(0); } |
| } |
| .stButton>button { |
| background-color: #e67e22 !important; |
| color: #4b5320 !important; |
| font-family: 'Vazir', sans-serif; |
| font-weight: 700 !important; |
| border-radius: 20px; |
| padding: 15px 30px; |
| border: none; |
| transition: all 0.3s ease; |
| font-size: 18px; |
| width: 100%; |
| margin: 20px 0; |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); |
| } |
| .stButton>button:hover { |
| background-color: #f39c12 !important; |
| transform: translateY(-4px); |
| box-shadow: 0 8px 20px rgba(0, 0, 0, 0.6); |
| } |
| .stApp { |
| background: #2e3b2e; |
| color: white; |
| font-family: 'Vazir', sans-serif; |
| } |
| </style> |
| <div class="header-text"> |
| <h1>رزمیار ارتش</h1> |
| <div class="subtitle">دستیارهوشمند ارتش جمهوری اسلامی ایران</div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| llm = init_chat_model( |
| "gpt-5-mini", |
| base_url="https://api.apifree.ai/gpt-5-mini", |
| api_key="sk-p6nSSQGZ3174esjkw7YcFpcXj6XNp", |
| max_tokens=4096, |
| temperature=0.7, |
| ) |
|
|
| EMBEDDING_FILE = "embeddings.json" |
|
|
| @st.cache_data |
| def load_embeddings(file_path): |
| with open(file_path, "r", encoding="utf-8") as f: |
| return json.load(f) |
|
|
| def get_query_embedding_apifree(query: str): |
| url = "https://api.apifree.ai/" |
|
|
| headers = { |
| "Authorization": "Bearer sk-p6nSSQGZ3174esjkw7YcFpcXj6XNp", |
| "Content-Type": "application/json" |
| } |
|
|
| payload = { |
| "model": "gpt-5-mini", |
| "input": query |
| } |
|
|
| response = requests.post(url, headers=headers, json=payload) |
| response.raise_for_status() |
|
|
| return response.json()["data"][0]["embedding"] |
|
|
| def find_most_similar_chunks(query_embedding, data, top_n=20): |
| query_vec = np.array(query_embedding).reshape(1, -1) |
| similarities = [] |
| for item in data: |
| chunk_vec = np.array(item["embedding"]).reshape(1, -1) |
| sim = cosine_similarity(query_vec, chunk_vec)[0][0] |
| similarities.append((item["chunk"], sim)) |
| similarities.sort(key=lambda x: x[1], reverse=True) |
| return [chunk for chunk, _ in similarities[:top_n]] |
|
|
| def clean_text(text): |
| import re |
| return re.sub(r'[^آ-یa-zA-Z0-9۰-۹,.،؟!؛\s]+', '', text) |
|
|
| query = st.chat_input("چطور میتونم کمک کنم؟") |
|
|
| if "chat_history" not in st.session_state: |
| st.session_state.chat_history = [] |
|
|
| if query: |
| thinking = st.empty() |
| thinking.markdown("⏳ در حال پردازش...") |
|
|
| try: |
| query_embedding = get_query_embedding_apifree(query) |
| data = load_embeddings(EMBEDDING_FILE) |
| top_chunks = find_most_similar_chunks(query_embedding, data, top_n=20) |
|
|
| context = "\n".join(top_chunks) |
| prompt = f""" |
| به سؤال زیر فقط بر اساس اطلاعات موجود در خطوط مرتبط پاسخ بده |
| از تحلیل، مقدمهچینی، توضیح مراحل تفکر، یا حدس شخصی خودداری کن |
| اگر اطلاعات کافی برای پاسخ دقیق در خطوط مرتبط وجود نداشت، فقط در آن صورت |
| میتوانی از دانش عمومی خود استفاده کنی تا یک پاسخ حرفهای و دقیق ارائه دهی |
| پاسخ باید نهایی، روان، و در حدود 256 تا 1024 کاراکتر باشد اگر در دیتا موجود نبود نزدیک ترین پاسخ به متن |
| |
| سوال: |
| {query} |
| |
| خطوط مرتبط: |
| {top_chunks} |
| |
| پاسخ نهایی: |
| """ |
|
|
| response = llm.invoke([ |
| SystemMessage( |
| content=" تو یک دستیار دقیق هستی که فقط با اطلاعات موجود در متن پاسخ میدهی و اگر در متن موجود نبود از شبیه ترین پاسخ به دیتای متن " |
| ), |
| HumanMessage(content=prompt) |
| ]) |
| final_answer = clean_text(response.content.strip()) |
|
|
| except Exception as e: |
| final_answer = f"❗ خطا: {str(e)}" |
|
|
| thinking.empty() |
|
|
| st.session_state.chat_history.append(("🧑", query)) |
| st.session_state.chat_history.append(("🤖", final_answer)) |
|
|
| st.markdown(""" |
| <style> |
| @import url('https://cdn.fontcdn.ir/Font/Persian/Vazir/Vazir.css'); |
| div.chat-message { |
| font-family: 'Vazir', sans-serif; |
| font-size: 16px; |
| color: white; |
| background-color: #0d4d31; |
| padding: 10px; |
| border-radius: 10px; |
| margin-bottom: 5px; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| st.markdown("---") |
| for sender, message in st.session_state.chat_history: |
| st.markdown(f'<div class="chat-message"><strong>{sender}</strong>: {message}</div>', unsafe_allow_html=True) |
|
|