Spaces:
Sleeping
Sleeping
| # Main application entry point | |
| import hmac | |
| import os | |
| import logging | |
| from pathlib import Path | |
| import streamlit as st | |
| import base64 | |
| from typing import List, Dict, Any, Tuple, Optional | |
| from config import ( | |
| GROQ_API_KEY, | |
| APP_PASSWORD, | |
| LLM_MODELS, | |
| DEFAULT_LLM_MODEL, | |
| UI_TEXTS, | |
| DATABASE_CONFIG, | |
| ) | |
| from document_processor import extract_pdf_pages, extract_pdf_from_path, process_documents, chunks_to_store_format | |
| from vector_store import get_vector_store, reset_vector_store | |
| from retrieval import get_retrieval_engine, reset_retrieval_engine | |
| from llm_generator import get_llm_generator, reset_llm_generator | |
| from embeddings import reset_embedder | |
| from integration import QueryHandler, QueryMode, QueryResult, get_query_handler, reset_query_handler | |
| from utils import get_metrics, record_query | |
| logger = logging.getLogger(__name__) | |
| st.set_page_config( | |
| page_title=UI_TEXTS["title"], | |
| layout="wide", | |
| initial_sidebar_state="collapsed" | |
| ) | |
| STYLES_PATH = Path(__file__).parent / "static" / "styles.css" | |
| BASE_DOCUMENTS_PATH = Path(__file__).parent.parent / "documents" | |
| def init_database_components() -> Dict[str, Any]: | |
| """Initialize database components if enabled. Cached to run once per session.""" | |
| if not DATABASE_CONFIG.get("enabled", False): | |
| return {"status": "disabled", "message": "Database integration disabled"} | |
| try: | |
| from database import init_database, get_database_status | |
| result = init_database() | |
| if result is None: | |
| return {"status": "disabled", "message": "Database initialization returned None"} | |
| if result.get("status") == "error": | |
| logger.error(f"Database init error: {result.get('message')}") | |
| return result | |
| logger.info(f"Database initialized: {result.get('status')}") | |
| return result | |
| except Exception as e: | |
| logger.error(f"Database initialization failed: {e}") | |
| return {"status": "error", "message": str(e)} | |
| def get_database_status_info() -> Dict[str, Any]: | |
| """Get current database status for UI display.""" | |
| if not DATABASE_CONFIG.get("enabled", False): | |
| return {"enabled": False, "status": "disabled"} | |
| try: | |
| from database import get_database_status | |
| status = get_database_status() | |
| return {"enabled": True, **status} | |
| except Exception as e: | |
| return {"enabled": True, "status": "error", "message": str(e)} | |
| def load_css() -> str: | |
| """Load CSS from external file.""" | |
| if STYLES_PATH.exists(): | |
| return STYLES_PATH.read_text() | |
| return "" | |
| def apply_custom_css(): | |
| """Apply custom CSS styling.""" | |
| css = load_css() | |
| if css: | |
| st.markdown(f"<style>{css}</style>", unsafe_allow_html=True) | |
| def check_password() -> bool: | |
| """Verify user password using timing-safe comparison.""" | |
| if "password_correct" not in st.session_state: | |
| st.session_state.password_correct = False | |
| if st.session_state.password_correct: | |
| return True | |
| if not APP_PASSWORD: | |
| return True | |
| password = st.text_input("Sifre", type="password", key="password_input") | |
| if password: | |
| if hmac.compare_digest(password, APP_PASSWORD): | |
| st.session_state.password_correct = True | |
| st.rerun() | |
| else: | |
| st.error("Yanlis sifre") | |
| return False | |
| return False | |
| def init_session_state(): | |
| """Initialize all session state variables.""" | |
| defaults = { | |
| "documents_processed": False, | |
| "chunk_count": 0, | |
| "chat_history": [], | |
| "embedder_loaded": False, | |
| "sources": [], | |
| "selected_model": DEFAULT_LLM_MODEL, | |
| "use_reranking": True, | |
| "base_docs_loaded": False, | |
| "loaded_documents": [], | |
| "query_mode": QueryMode.AUTO, | |
| "database_enabled": DATABASE_CONFIG.get("enabled", False), | |
| "database_initialized": False, | |
| "database_tables": [], | |
| "agent_available": False, | |
| } | |
| for key, value in defaults.items(): | |
| if key not in st.session_state: | |
| st.session_state[key] = value | |
| def get_base_documents() -> List[Path]: | |
| """Get list of base PDF documents from documents folder.""" | |
| if not BASE_DOCUMENTS_PATH.exists(): | |
| return [] | |
| return list(BASE_DOCUMENTS_PATH.glob("*.pdf")) | |
| def render_pdf_viewer(): | |
| """Render a PDF viewer for documents in the base documents folder.""" | |
| st.header("Doküman Görüntüleyici") | |
| docs = get_base_documents() | |
| if not docs: | |
| st.info("Documents klasöründe PDF bulunamadı.") | |
| return | |
| # Prefer deterministic order | |
| docs = sorted(docs, key=lambda p: p.name.lower()) | |
| selected_doc = st.selectbox( | |
| "PDF Seç", | |
| docs, | |
| format_func=lambda p: p.name | |
| ) | |
| try: | |
| pdf_bytes = selected_doc.read_bytes() | |
| base64_pdf = base64.b64encode(pdf_bytes).decode("utf-8") | |
| pdf_display = f""" | |
| <iframe | |
| src="data:application/pdf;base64,{base64_pdf}" | |
| width="100%" | |
| height="800px" | |
| style="border: 1px solid #ddd; border-radius: 8px;" | |
| type="application/pdf"> | |
| </iframe> | |
| """ | |
| st.markdown(pdf_display, unsafe_allow_html=True) | |
| except Exception as e: | |
| st.error(f"PDF görüntüleme hatası: {e}") | |
| def load_default_documents(): | |
| """Load default documents from documents folder into vector store.""" | |
| if st.session_state.get("base_docs_loaded", False): | |
| return | |
| base_docs = get_base_documents() | |
| if not base_docs: | |
| st.session_state.base_docs_loaded = True | |
| return | |
| vector_store = get_vector_store() | |
| stats = vector_store.get_collection_stats() | |
| # If documents already exist in store, restore session state from persisted data | |
| if stats["points_count"] > 0: | |
| st.session_state.base_docs_loaded = True | |
| st.session_state.documents_processed = True | |
| st.session_state.chunk_count = stats["points_count"] | |
| return | |
| total_chunks = 0 | |
| loaded_docs = [] | |
| for doc_path in base_docs: | |
| pages = extract_pdf_from_path(str(doc_path), doc_path.name) | |
| chunks = process_documents(pages) | |
| if chunks: | |
| texts, metadatas = chunks_to_store_format(chunks) | |
| vector_store.add_documents(texts, metadatas) | |
| total_chunks += len(chunks) | |
| loaded_docs.append({"name": doc_path.name, "chunks": len(chunks), "pages": len(pages)}) | |
| st.session_state.base_docs_loaded = True | |
| st.session_state.documents_processed = total_chunks > 0 | |
| st.session_state.chunk_count = total_chunks | |
| st.session_state.loaded_documents = loaded_docs | |
| def process_uploaded_files(files) -> int: | |
| """Process uploaded PDF files and add to vector store.""" | |
| if not files: | |
| return 0 | |
| vector_store = get_vector_store() | |
| total_chunks = 0 | |
| # Get already loaded sources to check for duplicates | |
| loaded_sources = vector_store.get_loaded_sources() | |
| for file in files: | |
| doc_name = file.name | |
| # Skip if document already exists | |
| if doc_name in loaded_sources: | |
| st.warning(f"'{doc_name}' already exists in the database, skipping.") | |
| continue | |
| pages = extract_pdf_pages(file, doc_name) | |
| chunks = process_documents(pages) | |
| if chunks: | |
| texts, metadatas = chunks_to_store_format(chunks) | |
| vector_store.add_documents(texts, metadatas) | |
| total_chunks += len(chunks) | |
| loaded_sources.add(doc_name) # Track newly added doc | |
| st.success(f"Added '{doc_name}': {len(chunks)} chunks") | |
| return total_chunks | |
| def reset_application(): | |
| reset_vector_store() | |
| reset_embedder() | |
| reset_retrieval_engine() | |
| reset_llm_generator() | |
| reset_query_handler() | |
| from integration.entity_context import reset_entity_store | |
| from integration.context_manager import reset_context_manager | |
| reset_entity_store() | |
| reset_context_manager() | |
| st.session_state.documents_processed = False | |
| st.session_state.chunk_count = 0 | |
| st.session_state.chat_history = [] | |
| st.session_state.sources = [] | |
| st.session_state.base_docs_loaded = False | |
| st.session_state.loaded_documents = [] | |
| st.session_state.query_mode = QueryMode.AUTO | |
| def render_settings_tab(): | |
| """Render the settings and document management in a tab (formerly sidebar).""" | |
| st.header("Sistem Ayarları ve Dokümanlar") | |
| col_settings, col_docs = st.columns([1, 2]) | |
| with col_settings: | |
| st.subheader("Model Ayarları") | |
| if not GROQ_API_KEY: | |
| st.error(UI_TEXTS["error_api_key"]) | |
| st.markdown(f"**{UI_TEXTS['model_select']}**") | |
| model_names = list(LLM_MODELS.keys()) | |
| current_model_id = st.session_state.get("selected_model", DEFAULT_LLM_MODEL) | |
| current_index = 0 | |
| for i, name in enumerate(model_names): | |
| if LLM_MODELS[name] == current_model_id: | |
| current_index = i | |
| break | |
| selected_name = st.selectbox( | |
| "Model", | |
| model_names, | |
| index=current_index, | |
| label_visibility="collapsed" | |
| ) | |
| st.session_state.selected_model = LLM_MODELS[selected_name] | |
| st.markdown("---") | |
| st.markdown(f"**{UI_TEXTS['rerank_toggle']}**") | |
| st.session_state.use_reranking = st.toggle( | |
| "Rerank Aktif", | |
| value=st.session_state.use_reranking, | |
| help=UI_TEXTS["rerank_help"] | |
| ) | |
| if st.session_state.use_reranking: | |
| st.success(UI_TEXTS["search_advanced"]) | |
| else: | |
| st.info(UI_TEXTS["search_standard"]) | |
| st.markdown("---") | |
| st.markdown(f"**{UI_TEXTS['data_source_label']}**") | |
| # Build mode options based on what's available | |
| mode_options = { | |
| UI_TEXTS["data_source_auto"]: QueryMode.AUTO, | |
| UI_TEXTS["data_source_documents"]: QueryMode.DOCUMENTS, | |
| } | |
| # Only show database modes if database is initialized | |
| if st.session_state.get("database_initialized", False): | |
| mode_options[UI_TEXTS["data_source_database"]] = QueryMode.DATABASE | |
| mode_options[UI_TEXTS["data_source_hybrid"]] = QueryMode.HYBRID | |
| selected_mode_name = st.selectbox( | |
| "Kaynak", | |
| list(mode_options.keys()), | |
| index=0, | |
| label_visibility="collapsed", | |
| ) | |
| st.session_state.query_mode = mode_options[selected_mode_name] | |
| # Database status indicator | |
| if st.session_state.get("database_enabled", False): | |
| if st.session_state.get("database_initialized", False): | |
| st.success("Veritabanı: Bağlı") | |
| tables = st.session_state.get("database_tables", []) | |
| if tables: | |
| with st.expander("Tablolar", expanded=False): | |
| for table in tables: | |
| st.text(f"- {table}") | |
| else: | |
| st.warning("Veritabanı: Bağlantı başarısız") | |
| else: | |
| st.info("Veritabanı: Devre dışı") | |
| st.markdown("---") | |
| if st.button("Tüm Sistemi Sıfırla (Reset)", type="secondary", use_container_width=True): | |
| reset_application() | |
| st.rerun() | |
| with col_docs: | |
| st.subheader("Doküman Yönetimi") | |
| uploaded_files = st.file_uploader( | |
| UI_TEXTS["upload_label"], | |
| type=["pdf"], | |
| accept_multiple_files=True, | |
| help=UI_TEXTS["upload_help"] | |
| ) | |
| if uploaded_files: | |
| if st.button(UI_TEXTS["process_button"], type="primary"): | |
| with st.spinner(UI_TEXTS["processing"]): | |
| chunk_count = process_uploaded_files(uploaded_files) | |
| st.session_state.chunk_count += chunk_count | |
| st.session_state.documents_processed = True | |
| st.success(UI_TEXTS["processing_complete"].format(count=len(uploaded_files))) | |
| st.divider() | |
| if st.session_state.documents_processed: | |
| st.metric("Toplam Vektör Parçacığı (Chunks)", st.session_state.chunk_count) | |
| if st.session_state.loaded_documents: | |
| with st.expander("Yüklü Doküman Listesi", expanded=True): | |
| for doc in st.session_state.loaded_documents: | |
| st.text(f"📄 {doc['name']} ({doc['chunks']} chunks)") | |
| def generate_response( | |
| question: str, | |
| use_reranking: bool, | |
| chat_history: List[Dict[str, str]], | |
| model_id: str = DEFAULT_LLM_MODEL, | |
| query_mode: QueryMode = QueryMode.DOCUMENTS, | |
| ) -> Tuple[str, List[Dict[str, Any]], Dict[str, Any]]: | |
| handler = get_query_handler() | |
| result = handler.execute(question, mode=query_mode, chat_history=chat_history, model_id=model_id) | |
| record_query( | |
| query=question, | |
| mode=result.mode.value, | |
| response_time=result.execution_time, | |
| success=result.success, | |
| error=result.error, | |
| ) | |
| debug_info = { | |
| "mode": result.mode.value, | |
| "execution_time": result.execution_time, | |
| "intent": result.intent, | |
| "sql_query": result.sql_query, | |
| "db_results_count": len(result.db_results) if result.db_results else 0, | |
| "success": result.success, | |
| "model_id": model_id, | |
| "routing": result.routing_metadata, | |
| "is_database": result.mode in (QueryMode.DATABASE, QueryMode.HYBRID) and result.db_results, | |
| } | |
| sources = result.sources if result.sources else [] | |
| return result.response, sources, debug_info | |
| def render_database_tab(): | |
| import pandas as pd | |
| from database.db_adapter import get_database_adapter | |
| st.header("Veritabanı") | |
| if not DATABASE_CONFIG.get("enabled", False): | |
| st.info("Veritabanı entegrasyonu kapalı (DATABASE_CONFIG.enabled=false).") | |
| return | |
| try: | |
| adapter = get_database_adapter() | |
| if adapter is None or not adapter.is_connected: | |
| st.warning("Veritabanı bağlantısı kurulamadı.") | |
| return | |
| table_rows = adapter.execute_query( | |
| "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name" | |
| ) | |
| tables = [r["name"] for r in table_rows] | |
| if not tables: | |
| st.info("Veritabanında tablo bulunamadı.") | |
| return | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| selected_table = st.selectbox("Tablo seç", tables) | |
| with col2: | |
| limit = st.number_input("Limit", min_value=10, max_value=2000, value=200, step=50) | |
| if selected_table not in tables: | |
| st.error("Geçersiz tablo seçimi.") | |
| return | |
| try: | |
| count_rows = adapter.execute_query( | |
| f"SELECT COUNT(*) AS cnt FROM {selected_table}" | |
| ) | |
| if count_rows: | |
| st.caption(f"Toplam kayıt: {int(count_rows[0]['cnt'])}") | |
| except Exception: | |
| pass | |
| rows = adapter.execute_query( | |
| f"SELECT * FROM {selected_table} LIMIT :lim", | |
| {"lim": int(limit)} | |
| ) | |
| if rows: | |
| df = pd.DataFrame(rows) | |
| st.dataframe(df, use_container_width=True) | |
| else: | |
| st.info("Tabloda kayıt bulunamadı.") | |
| except Exception as e: | |
| st.error(f"DB görüntüleme hatası: {e}") | |
| def render_sources(sources: List[Dict[str, Any]], use_reranking: bool): | |
| """Render source citations in expandable section.""" | |
| if not sources: | |
| return | |
| with st.expander(UI_TEXTS["sources_label"], expanded=False): | |
| for i, src in enumerate(sources, 1): | |
| st.markdown(f'<div class="source-box">', unsafe_allow_html=True) | |
| col1, col2 = st.columns([3, 1]) | |
| with col1: | |
| page_num = src.get('page_number', src.get('metadata', {}).get('page_number', -1)) | |
| page_info = f", Sayfa {page_num}" if page_num and page_num > 0 else "" | |
| source_name = src.get('source', src.get('metadata', {}).get('source', 'Bilinmeyen')) | |
| index = src.get('index', i) | |
| st.markdown(f"**{index}. {source_name}{page_info}**") | |
| with col2: | |
| score = src.get('score', 0.0) | |
| if use_reranking and "original_score" in src: | |
| st.markdown( | |
| f'<span class="score-badge">Rerank: {score:.3f}</span>', | |
| unsafe_allow_html=True | |
| ) | |
| else: | |
| st.markdown( | |
| f'<span class="score-badge">Score: {score:.3f}</span>', | |
| unsafe_allow_html=True | |
| ) | |
| text = src.get("text", src.get("content", "")) | |
| st.text(text[:500] + "..." if len(text) > 500 else text) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| st.markdown('<div style="margin-bottom: 16px;"></div>', unsafe_allow_html=True) | |
| def render_debug_info(debug_info: Dict[str, Any]): | |
| """Render debug information with query rewriting details.""" | |
| if "mode" in debug_info: | |
| mode = debug_info.get("mode", "documents") | |
| mode_label = { | |
| "documents": UI_TEXTS["doc_query_indicator"], | |
| "database": UI_TEXTS["db_query_indicator"], | |
| "hybrid": UI_TEXTS["hybrid_query_indicator"], | |
| "auto": UI_TEXTS["data_source_auto"], | |
| }.get(mode, mode) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.metric(UI_TEXTS["data_source_label"], mode_label) | |
| with col2: | |
| exec_time = debug_info.get("execution_time", 0) | |
| st.metric(UI_TEXTS["execution_time_label"], f"{exec_time:.2f}s") | |
| if debug_info.get("sql_query"): | |
| with st.expander(UI_TEXTS["sql_query_label"], expanded=False): | |
| st.code(debug_info["sql_query"], language="sql") | |
| if debug_info.get("db_results_count", 0) > 0: | |
| st.info(f"{UI_TEXTS['db_results_label']}: {debug_info['db_results_count']} kayit") | |
| st.divider() | |
| rewrite_info = debug_info.get("rewrite_info") | |
| if rewrite_info and debug_info.get("query_rewritten"): | |
| st.subheader(UI_TEXTS["debug_query_rewriting"]) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| st.markdown(f"**{UI_TEXTS['debug_original_query']}:**") | |
| st.info(debug_info.get("query", "")) | |
| with col2: | |
| st.markdown(f"**{UI_TEXTS['debug_search_query']}:**") | |
| st.success(debug_info.get("search_query", "")) | |
| col3, col4, col5 = st.columns(3) | |
| with col3: | |
| is_follow_up = rewrite_info.get("is_follow_up", False) | |
| st.metric(UI_TEXTS["debug_is_follow_up"], "Evet" if is_follow_up else "Hayir") | |
| with col4: | |
| method = rewrite_info.get("method", "none") | |
| st.metric(UI_TEXTS["debug_rewrite_method"], method.upper()) | |
| with col5: | |
| time_ms = rewrite_info.get("rewrite_time_ms", 0) | |
| st.metric(UI_TEXTS["debug_rewrite_time"], f"{time_ms} ms") | |
| if debug_info.get("use_dual_query"): | |
| st.divider() | |
| st.subheader(UI_TEXTS["debug_dual_query"]) | |
| stats = debug_info.get("dual_query_stats", {}) | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| st.metric(UI_TEXTS["debug_dual_query_original_count"], stats.get("original_count", 0)) | |
| with col2: | |
| st.metric(UI_TEXTS["debug_dual_query_rewritten_count"], stats.get("rewritten_count", 0)) | |
| with col3: | |
| st.metric(UI_TEXTS["debug_dual_query_merged_count"], stats.get("merged_count", 0)) | |
| with col4: | |
| st.metric(UI_TEXTS["debug_dual_query_strategy"], stats.get("merge_strategy", "score").upper()) | |
| st.divider() | |
| st.subheader("Retrieval Detayları") | |
| filtered_info = {k: v for k, v in debug_info.items() if k != "rewrite_info"} | |
| st.json(filtered_info) | |
| def render_chat(): | |
| """Render the chat interface.""" | |
| for msg in st.session_state.chat_history: | |
| with st.chat_message(msg["role"]): | |
| st.markdown(msg["content"]) | |
| if msg["role"] == "assistant" and "sources" in msg: | |
| render_sources(msg["sources"], st.session_state.use_reranking) | |
| if prompt := st.chat_input(UI_TEXTS["query_placeholder"]): | |
| st.session_state.chat_history.append({"role": "user", "content": prompt}) | |
| with st.chat_message("user"): | |
| st.markdown(prompt) | |
| with st.chat_message("assistant"): | |
| with st.spinner(UI_TEXTS["thinking"]): | |
| response, sources, debug_info = generate_response( | |
| question=prompt, | |
| use_reranking=st.session_state.use_reranking, | |
| chat_history=st.session_state.chat_history[:-1], | |
| model_id=st.session_state.selected_model, | |
| query_mode=st.session_state.query_mode, | |
| ) | |
| st.markdown(response) | |
| render_sources(sources, st.session_state.use_reranking) | |
| with st.expander("Arama Detayları", expanded=False): | |
| render_debug_info(debug_info) | |
| st.session_state.chat_history.append({ | |
| "role": "assistant", | |
| "content": response, | |
| "sources": sources, | |
| "is_database": debug_info.get("is_database", False), | |
| }) | |
| def render_tutorial(): | |
| """Render tutorial for new users.""" | |
| with st.expander("Nasil Kullanilir?", expanded=True): | |
| st.markdown(""" | |
| **1. API Anahtari** | |
| - GROQ_API_KEY ortam degiskenini ayarlayin | |
| **2. Doküman Yükleme** | |
| - 'Ayarlar' sekmesinden PDF dosyalarınızı yükleyin | |
| - "Dokümanları İşle" butonuna tıklayın | |
| **3. Soru Sorma** | |
| - 'Sohbet' sekmesindeki metin kutusuna sorunuzu yazin | |
| - Örnek: "Atlas nedir?", "Muhasebe modülü ne yapar?" | |
| """) | |
| def main(): | |
| """Main application entry point.""" | |
| if not check_password(): | |
| st.stop() | |
| apply_custom_css() | |
| init_session_state() | |
| # Initialize database components if enabled | |
| db_result = init_database_components() | |
| if db_result.get("status") not in ("disabled", "error"): | |
| st.session_state.database_initialized = True | |
| st.session_state.database_tables = db_result.get("tables", []) | |
| else: | |
| st.session_state.database_initialized = False | |
| if db_result.get("status") == "error": | |
| logger.warning(f"Database not available: {db_result.get('message')}") | |
| # Initialize agent engine and wire to query handler | |
| st.session_state.agent_available = False | |
| if not st.session_state.base_docs_loaded: | |
| with st.spinner(UI_TEXTS["base_docs_loading"]): | |
| load_default_documents() | |
| tab_chat, tab_settings, tab_database, tab_scope, tab_pdf = st.tabs(["Sohbet", "Ayarlar & Dokümanlar", "Veritabanı", "Kapsam (PoC)", "Doküman Görüntüle"]) | |
| with tab_chat: | |
| st.title(UI_TEXTS["title"]) | |
| if not st.session_state.documents_processed: | |
| st.info(UI_TEXTS["no_docs"]) | |
| render_tutorial() | |
| else: | |
| render_chat() | |
| with tab_settings: | |
| render_settings_tab() | |
| with tab_database: | |
| render_database_tab() | |
| with tab_scope: | |
| st.title("AtlasAI PoC – Kapsam") | |
| st.markdown("## Veritabanı Üzerinden Cevaplanabilen Sorular") | |
| st.markdown(""" | |
| ### Teklif Statüsü | |
| - 26000046 teklifinin statüsü nedir? | |
| ### Ödeme Onayı | |
| - 26000046 teklifinin ödeme onayı verildi mi? | |
| ### Peşinat | |
| - 26000046 teklifinde peşinat tamamlandı mı? | |
| ### Kredi Kararı | |
| - 26000046 teklifinin kredi kararı nedir? | |
| ### Kredi No'dan Lead ID Sorgusu | |
| - 26000046 numaralı teklif ile ilişkili lead ID nedir? | |
| ### Lead ID'den Kredi No Sorgusu | |
| - L-P009502428 lead ID'sine sahip kaydın kredi numarası nedir? | |
| ### Araç Plakasından Rehin Bilgisi Sorgusu | |
| - 34MB001 plakalı araç için rehin bilgisi nedir? | |
| ### Sigorta | |
| - 266071006966 sigortasinin durumu nedir? | |
| - 266071006963 numarali policenin aktif sigortasi var mi? | |
| """) | |
| st.markdown("## PoC Teknik Sınırlar") | |
| st.info(""" | |
| - Sistem read-only modda çalışmaktadır. | |
| - Yalnızca tanımlı view'ler üzerinden SELECT üretilir. | |
| - Hybrid modda DB ve doküman birlikte kullanılabilir. | |
| """) | |
| with tab_pdf: | |
| render_pdf_viewer() | |
| if __name__ == "__main__": | |
| main() |