Spaces:
Sleeping
Sleeping
| import os | |
| import streamlit as st | |
| from openai import OpenAI | |
| from tavily import TavilyClient | |
| # llama-index imports - removed ServiceContext usage | |
| from llama_index.core import ( | |
| VectorStoreIndex, | |
| SimpleDirectoryReader, | |
| StorageContext, | |
| load_index_from_storage, | |
| ) | |
| from llama_index.llms.openai import OpenAI as LlamaOpenAI | |
| from llama_index.embeddings.openai import OpenAIEmbedding | |
| from llama_index.core.prompts import PromptTemplate | |
| import time | |
| # ---------- LOAD KNOWLEDGE BASE (fixed) ---------- | |
| def load_index(): | |
| """ | |
| Loads an existing index from ./storage if present. | |
| If not present, builds a new VectorStoreIndex from the provided markdown file, | |
| using explicit llm and embed_model parameters (no ServiceContext). | |
| """ | |
| persist_dir = "./storage" | |
| if os.path.exists(persist_dir): | |
| # Load existing storage context and index (no service_context required) | |
| storage_context = StorageContext.from_defaults(persist_dir=persist_dir) | |
| return load_index_from_storage(storage_context) | |
| # Else build index from docs | |
| docs = SimpleDirectoryReader(input_files=["time_to_rethink_trust_book (3).md"]).load_data() | |
| # Create embedding & LLM objects explicitly | |
| embed_model = OpenAIEmbedding(model="text-embedding-3-small") | |
| llm = LlamaOpenAI(model="gpt-4-turbo") | |
| # Pass llm and embed_model explicitly to avoid global ServiceContext usage | |
| index = VectorStoreIndex.from_documents(docs, llm=llm, embed_model=embed_model) | |
| # persist storage | |
| index.storage_context.persist(persist_dir=persist_dir) | |
| return index | |
| # ---------- ENHANCED STYLING ---------- | |
| st.markdown( | |
| """ | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;600;700&display=swap'); | |
| /* Prevent horizontal scroll */ | |
| html, body { | |
| overflow-x: hidden !important; | |
| max-width: 100vw !important; | |
| } | |
| /* Base theme - clean white */ | |
| body, .stApp { | |
| background-color: #FFFFFF !important; | |
| color: #1F1F1F !important; | |
| font-family: 'Titillium Web', sans-serif; | |
| overflow-x: hidden !important; | |
| max-width: 100vw !important; | |
| } | |
| /* Main container styling */ | |
| .stApp { | |
| max-width: 900px; | |
| margin: 0 auto; | |
| padding-top: 100px !important; | |
| overflow-x: hidden !important; | |
| } | |
| /* Prevent all elements from causing horizontal scroll */ | |
| * { | |
| max-width: 100%; | |
| box-sizing: border-box; | |
| } | |
| /* Fixed Header Container */ | |
| .fixed-header { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| background-color: #FFFFFF; | |
| z-index: 999; | |
| border-bottom: 1px solid #E5E7EB; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.05); | |
| padding: 15px 0; | |
| overflow-x: hidden; | |
| } | |
| .header-content { | |
| max-width: 900px; | |
| margin: 0 auto; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 0 20px; | |
| } | |
| .header-logo { | |
| height: 50px; | |
| width: auto; | |
| object-fit: contain; | |
| max-width: 100%; | |
| } | |
| /* Trust Header - only on welcome screen */ | |
| .trust-header { | |
| font-family: 'Titillium Web', sans-serif; | |
| text-align: center; | |
| margin-top: 40px; | |
| margin-bottom: 10px; | |
| font-size: 36px; | |
| font-weight: 700; | |
| line-height: 1.2; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| } | |
| /* Icon bar */ | |
| .icon-bar { | |
| display: flex; | |
| justify-content: center; | |
| gap: 20px; | |
| margin-top: 30px; | |
| margin-bottom: 40px; | |
| flex-wrap: wrap; | |
| padding: 0 10px; | |
| overflow-x: hidden; | |
| } | |
| .icon-container { | |
| position: relative; | |
| display: inline-block; | |
| text-align: center; | |
| margin-bottom: 20px; | |
| } | |
| /* Shared icon styling */ | |
| .icon-img { | |
| object-fit: contain; | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| } | |
| /* Individually adjustable classes */ | |
| .ideate-icon, .write-icon, .find-icon, .analyse-icon, .chat-icon { | |
| width: 30px; | |
| height: 32px; | |
| padding: 1px; | |
| margin-top: 4px; | |
| } | |
| .icon-img:hover { | |
| transform: scale(1.1); | |
| } | |
| .tooltip-text { | |
| visibility: hidden; | |
| background-color: #222; | |
| color: #fff; | |
| text-align: left; | |
| border-radius: 6px; | |
| padding: 12px 14px; | |
| position: absolute; | |
| z-index: 10; | |
| top: 60px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| font-size: 13px; | |
| font-family: 'Titillium Web', sans-serif; | |
| width: 280px; | |
| max-width: 90vw; | |
| word-wrap: break-word; | |
| white-space: normal; | |
| box-shadow: 0 6px 16px rgba(0,0,0,0.3); | |
| } | |
| .icon-container:hover .tooltip-text, | |
| .icon-container:active .tooltip-text { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| /* Welcome prompt section */ | |
| .welcome-prompt { | |
| text-align: center; | |
| margin-top: 30px; | |
| margin-bottom: 30px; | |
| padding: 0 20px; | |
| overflow-wrap: break-word; | |
| word-wrap: break-word; | |
| } | |
| .prompt-title { | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| color: #1F1F1F; | |
| margin-bottom: 10px; | |
| font-family: 'Titillium Web', sans-serif; | |
| word-wrap: break-word; | |
| } | |
| .prompt-subtitle { | |
| font-size: 0.95rem; | |
| color: #6B7280; | |
| font-family: 'Titillium Web', sans-serif; | |
| word-wrap: break-word; | |
| } | |
| /* Chat input styling */ | |
| .stChatInputContainer { | |
| border-top: 1px solid #E5E7EB; | |
| padding-top: 1rem; | |
| margin-top: 2rem; | |
| background-color: #FFFFFF; | |
| overflow-x: hidden; | |
| } | |
| .stChatInput > div { | |
| border-radius: 24px !important; | |
| border: 2px solid #E5E7EB !important; | |
| box-shadow: 0 1px 2px rgba(0,0,0,0.05); | |
| max-width: 100%; | |
| } | |
| .stChatInput input { | |
| background-color: #FFFFFF !important; | |
| color: #000000 !important; | |
| border: none !important; | |
| padding: 0.75rem 1rem !important; | |
| font-size: 0.95rem !important; | |
| font-family: 'Titillium Web', sans-serif !important; | |
| max-width: 100%; | |
| } | |
| .stChatInput input::placeholder { | |
| color: #9CA3AF !important; | |
| } | |
| [data-testid="stChatInput"] textarea { | |
| color: #000000 !important; | |
| background-color: #FFFFFF !important; | |
| caret-color: #000000 !important; | |
| color-scheme: light !important; | |
| -moz-appearance: none !important; | |
| appearance: none !important; | |
| } | |
| /* Firefox placeholder */ | |
| [data-testid="stChatInput"] textarea::placeholder { | |
| color: #9CA3AF !important; | |
| opacity: 1 !important; | |
| } | |
| /* Chat messages container */ | |
| .chat-container { | |
| margin-top: 1rem; | |
| padding-bottom: 2rem; | |
| overflow-x: hidden; | |
| max-width: 100%; | |
| } | |
| /* Message row with avatar */ | |
| .message-row { | |
| display: flex; | |
| align-items: flex-start; | |
| margin: 1rem 0; | |
| gap: 12px; | |
| overflow-x: hidden; | |
| max-width: 100%; | |
| } | |
| .message-row.user { | |
| flex-direction: row-reverse; | |
| } | |
| .stChatInput textarea { | |
| background-color: #FFFFFF !important; | |
| color: #000000 !important; | |
| -moz-appearance: none; | |
| appearance: none; | |
| caret-color: #000000 !important; | |
| } | |
| /* Placeholder fix for Firefox */ | |
| .stChatInput textarea::placeholder { | |
| color: #9CA3AF !important; | |
| opacity: 1; | |
| } | |
| /* Avatar styling */ | |
| .avatar { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| overflow: hidden; | |
| } | |
| .avatar.bot { | |
| background-color: #10A37F; | |
| border: 2px solid #10A37F; | |
| padding: 4px; | |
| color: white; | |
| font-weight: 700; | |
| font-size: 14px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .avatar.bot img { | |
| display: none; /* Hide image if any */ | |
| } | |
| .avatar.user { | |
| background-color: #10A37F; | |
| color: white; | |
| font-weight: 600; | |
| font-size: 16px; | |
| } | |
| /* Message bubbles */ | |
| .user-message { | |
| background-color: #F3F4F6; | |
| padding: 1rem 1.25rem; | |
| border-radius: 18px; | |
| max-width: 75%; | |
| font-family: 'Titillium Web', sans-serif; | |
| line-height: 1.5; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| overflow-x: hidden; | |
| } | |
| .bot-message { | |
| background-color: #FFFFFF; | |
| padding: 1rem 1.25rem; | |
| border-radius: 18px; | |
| max-width: 75%; | |
| border: 1px solid #E5E7EB; | |
| font-family: 'Titillium Web', sans-serif; | |
| line-height: 1.5; | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| overflow-x: hidden; | |
| } | |
| /* Submit button styling */ | |
| button[kind="primary"] { | |
| background-color: #10A37F !important; | |
| color: #FFFFFF !important; | |
| border-radius: 20px !important; | |
| border: none !important; | |
| padding: 0.5rem 1rem !important; | |
| font-weight: 500 !important; | |
| font-family: 'Titillium Web', sans-serif !important; | |
| } | |
| button[kind="primary"]:hover { | |
| background-color: #0D8C6C !important; | |
| } | |
| /* Hide fullscreen button on images */ | |
| button[title="View fullscreen"] { | |
| display: none !important; | |
| } | |
| [data-testid="StyledFullScreenButton"] { | |
| display: none !important; | |
| } | |
| div[data-testid="stImage"] button { | |
| display: none !important; | |
| } | |
| /* Spinner */ | |
| .stSpinner > div { | |
| border-top-color: #10A37F !important; | |
| } | |
| /* Footer styling */ | |
| .footer-message { | |
| font-size: 0.85rem; | |
| color: #6B7280; | |
| font-style: italic; | |
| margin-top: 1rem; | |
| padding-top: 1rem; | |
| border-top: 1px solid #E5E7EB; | |
| } | |
| /* Hide Streamlit branding */ | |
| #MainMenu {visibility: hidden;} | |
| header {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| div[data-testid="stDecoration"] {display: none;} | |
| div[data-testid="stToolbar"] {display: none;} | |
| /* Ensure Streamlit containers don't overflow */ | |
| [data-testid="stVerticalBlock"] { | |
| overflow-x: hidden !important; | |
| max-width: 100%; | |
| } | |
| [data-testid="stHorizontalBlock"] { | |
| overflow-x: hidden !important; | |
| max-width: 100%; | |
| } | |
| /* Mobile responsive */ | |
| @media screen and (max-width: 600px) { | |
| .stApp { | |
| padding-top: 80px !important; | |
| padding-left: 10px; | |
| padding-right: 10px; | |
| } | |
| .fixed-header { | |
| padding: 10px 0; | |
| } | |
| .header-logo { | |
| height: 40px; | |
| } | |
| .trust-header { | |
| font-size: 24px; | |
| padding: 0 10px; | |
| } | |
| .ideate-icon, .write-icon, .find-icon, .analyse-icon, .chat-icon { | |
| width: 26px; | |
| height: 26px; | |
| } | |
| .tooltip-text { | |
| font-size: 10px; | |
| bottom: 10px; | |
| top: auto !important; | |
| left: 70%; | |
| transform: translateX(-50%); | |
| } | |
| .prompt-title { | |
| font-size: 1.2rem; | |
| } | |
| .prompt-subtitle { | |
| font-size: 0.85rem; | |
| } | |
| .user-message, .bot-message { | |
| max-width: 85%; | |
| padding: 0.75rem 1rem; | |
| } | |
| .avatar { | |
| width: 32px; | |
| height: 32px; | |
| } | |
| .message-row { | |
| gap: 8px; | |
| } | |
| } | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| SYSTEM_INSTRUCTION = """ | |
| You are an elite marketing copywriter. Your output must pass 100% of quality checks or it will be REJECTED and you must START OVER. | |
| ZERO TOLERANCE POLICY | |
| One banned word = REJECTED | |
| One “-ing” in any headline/subheading = REJECTED | |
| Organization name in headline or body (except in Finder bullets where "we" is allowed) = REJECTED | |
| No subheadings in blog = REJECTED | |
| Weak bridges between paragraphs = REJECTED | |
| Self-check text in output = REJECTED | |
| Placeholder text in Finder = REJECTED | |
| Vague claims without specifics = REJECTED | |
| Passive voice = REJECTED | |
| Missing audience benefit in Finder = REJECTED | |
| Only use 2024–2027 data (older data = REJECTED) | |
| 🚫 BANNED WORDS (DELETE IF FOUND IN OUTPUT) | |
| (CTRL+F search before submitting) | |
| Tier 1 | |
| empower, enhance, foster, leverage, drive, transform, enable, facilitate, pioneer, showcase, underscore, exemplify, spearhead, pivotal | |
| Tier 2 | |
| champion, revolutionize, unlock, unleash, elevate, amplify, beacon, realm, cornerstone, seamless, cutting-edge, robust, innovative solutions, landscape, synergy, paradigm, ecosystem, holistic, comprehensive, integrated, streamlined, optimized, groundbreaking, game-changing, next-generation, state-of-the-art, world-class, industry-leading, best-in-class | |
| Tier 3 | |
| stands as, serves as, acts as, positions as, represents, embodies, highlights (vague), integral to, central to, at the heart of, at the core of, testament to, hallmark of, epitome of | |
| Tier 4 | |
| commitment to excellence, dedicated to, passionate about, strive to, goes above and beyond, takes pride in, at the forefront, ahead of the curve, raise the bar, set the standard, lead by example, pave the way, making waves, making a difference, proven track record, time-tested, tried and true, came to fruition, set the stage for | |
| Tier 5 | |
| “Imagine a world…”, “In today’s landscape…”, “In an era…”, “has not gone unnoticed”, “it’s no secret”, “it’s clear that”, “This isn’t just…”, “doesn’t stop there”, “that’s just the beginning”, “we’re proud”, “we are proud”, “we’re excited”, “we’re thrilled”, “embark on a journey”, “transformative journey” | |
| Tier 6 (Vague verbs only allowed with metrics) | |
| strengthen, improve, support | |
| 📰 HEADLINES — STRICT RULES | |
| Must NOT contain “we” | |
| Must NOT contain organization names | |
| Must NOT contain any word ending in “ing” | |
| Must include a specific number | |
| Must NOT be vague | |
| BAD: “Achievements in Development at BCLP” | |
| GOOD: “How $5M Investment Cut Carbon Footprint by 15%” | |
| Subheadings follow identical rules | |
| 🔍 TRUSTBUILDER® FINDER — EXACTLY 15 POINTS | |
| 5 Organization + 5 People + 5 Offers/Services | |
| Absolute rules: | |
| EXACTLY 15 bullets | |
| NO placeholders | |
| NO self-check text | |
| Active voice only | |
| Exact numbers only | |
| MUST end with: “This [benefit] for [specific audience].” | |
| Sources MUST be clickable hyperlinks in this exact format: | |
| (Source: https://example.com | |
| ) | |
| FORMAT FOR EACH TRUSTBUILDER® | |
| [Complete statement with numbers, names, dates]. This [specific benefit with numbers if possible] for [specific detailed audience]. (Source: https://URL | |
| )* | |
| 📌 SOURCE RULE — CRITICAL | |
| Every TrustBuilder MUST include a clickable link like this: | |
| (Source: https://domain/path | |
| ) | |
| No alternate styles allowed. | |
| 📊 MANDATORY SPECIFICITY IN EVERY OUTPUT | |
| Every piece must include: | |
| 3+ exact amounts ($X) or exact measurable counts | |
| 2+ full dates (Month Year) | |
| 3+ quantities (e.g., 50 executives, 300K sq ft) | |
| 2+ full names + complete titles | |
| 2+ measurable % outcomes | |
| 🌊 BRIDGE SENTENCES | |
| Every paragraph’s last sentence MUST logically set up the next paragraph. | |
| Banned bridge patterns: | |
| “set the stage for” | |
| “underscores our commitment” | |
| “reflects our dedication” | |
| Use cause-effect, time-shift, or connection-based bridges. | |
| 🧩 WHEN PRODUCING TRUSTBUILDERS® | |
| Start output with: | |
| "Here are Development TrustBuilders® for [Organization]. Let me know if you want to refine the results or find more." | |
| Then deliver: | |
| Organization (5 bullets) | |
| … | |
| … | |
| People (5 bullets) | |
| … | |
| … | |
| Offers/Services (5 bullets) | |
| … | |
| … | |
| End with: | |
| For detailed analysis and brand copywriting based on TrustBuilders®, visit TrustLogic.center. | |
| 🪪 WHEN ANALYZING A BRAND | |
| Follow the entire Executive Summary → Buckets → Ratings → Table → Gaps → Keywords → Strategy format exactly as defined. | |
| 📝 WHEN WRITING BLOG/ARTICLE/REPORT | |
| Must contain min. 3 subheadings | |
| Subheadings follow same rules as headlines | |
| Numerical specificity required | |
| Bridges between every paragraph | |
| “We” voice allowed in body, but NOT in headlines/subheadings | |
| 📧 WHEN WRITING EMAIL | |
| Subject line: NO “-ing”, MUST include a number | |
| Exact specifics everywhere | |
| No banned words | |
| Bridges between sections | |
| 📱 WHEN WRITING SOCIAL POST | |
| No inline URLs | |
| No banned words | |
| No “-ing” hashtags | |
| Start with a specific-number hook | |
| 👤 WHEN WRITING PARTNER PROFILE | |
| No subheadings | |
| Include one direct quote | |
| Smooth narrative with bridges | |
| Exact specifics and metrics | |
| 🎯 FINAL VERIFICATION (MENTAL ONLY — DO NOT OUTPUT) | |
| No banned words | |
| No “-ing” in headlines | |
| No org name in headlines | |
| All TrustBuilders formatted correctly | |
| All sources clickable | |
| No placeholders | |
| Active voice only | |
| Specificity thresholds met | |
| 15 Finder points exactly | |
| Strong bridges everywhere | |
| """ | |
| KB_PROMPT_TEMPLATE = PromptTemplate( | |
| template="""You are an expert on TrustLogic methodology. Use the context below to answer accurately. | |
| Context: | |
| {context_str} | |
| Question: {query_str} | |
| Instructions: | |
| - Answer based on the context provided | |
| - Be specific and cite relevant TrustLogic concepts | |
| - If the context doesn't fully answer the question, say so | |
| - Keep your answer clear and professional | |
| - Never mention that you're using a "knowledge base" - just provide the answer naturally | |
| Answer:""" | |
| ) | |
| WEB_SEARCH_PROMPT = """You are an expert at finding TrustBuilders® for organizations. | |
| User's request: {query} | |
| Web Search Results: | |
| {web_results} | |
| Instructions: | |
| - Extract specific, measurable achievements from the search results | |
| - Format as TrustBuilders® with complete details | |
| - Include exact numbers, full names, complete titles, and dates | |
| - Every statement needs a source link | |
| - End each point with "This [benefit] for [audience]." | |
| - Use active voice only | |
| - If finding TrustBuilders, provide at least 5 per category (Organization, People, Offers/Services) | |
| - ALWAYS end your response with the trustlogic.center footer | |
| Answer:""" | |
| FOOTER_MESSAGE = "\n\n---\n*For detailed analysis and information, visit [trustlogic.center](https://trustlogic.center) for comprehensive copy generation and brand analysis.*" | |
| # ---------- LOAD KNOWLEDGE BASE ---------- | |
| # ---------- INITIALIZE SERVICES ---------- | |
| index = load_index() | |
| tavily = TavilyClient(api_key=os.getenv("TAVILY_API_KEY")) | |
| client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
| # ---------- CHAT STATE ---------- | |
| if "messages" not in st.session_state: | |
| st.session_state.messages = [] | |
| if "conversation_started" not in st.session_state: | |
| st.session_state.conversation_started = False | |
| # ---------- FIXED HEADER ---------- | |
| st.markdown(""" | |
| <div class="fixed-header"> | |
| <div class="header-content"> | |
| <img src="https://huggingface.co/spaces/Trust-logic-Test/demo/resolve/main/Trust%20Logic%20Logo_RGB_Standard.png" class="header-logo" alt="TrustLogic"> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # ---------- WELCOME SCREEN OR CHAT ---------- | |
| if not st.session_state.conversation_started: | |
| st.markdown(""" | |
| <div class="trust-header"> | |
| <span style="color:teal;">[</span> | |
| <span style="color:black;">Let's build some trust!</span> | |
| <span style="color:teal;">]</span> | |
| </div> | |
| <div class="icon-bar"> | |
| <div class="icon-container"> | |
| <img src="https://huggingface.co/spaces/trustlogic/temporary-trustlogic-batch/resolve/main/Screenshot%202025-08-14%20at%208.37.38%E2%80%AFPM.png" class="icon-img ideate-icon" alt="Ideate"> | |
| <div class="tooltip-text"> | |
| <strong>Ideate</strong><br> | |
| Ask me which Trust Buckets® are most important and brainstorm trust-building ideas for your target audience. Tell me your ideas and audience and brainstorm with me. | |
| </div> | |
| </div> | |
| <div class="icon-container"> | |
| <img src="https://huggingface.co/spaces/trustlogic/temporary-trustlogic-batch/resolve/main/write.png" class="icon-img write-icon" alt="Write"> | |
| <div class="tooltip-text"> | |
| <strong>Write</strong><br> | |
| Tell me what you want to write and your audience. Tell me which Trust Buckets® you want to fill – or ask me to propose the most important ones. | |
| </div> | |
| </div> | |
| <div class="icon-container"> | |
| <img src="https://huggingface.co/spaces/trustlogic/temporary-trustlogic-batch/resolve/main/find.png" class="icon-img find-icon" alt="Find"> | |
| <div class="tooltip-text"> | |
| <strong>Find</strong><br> | |
| You probably have a vast treasure trove of unused trust equity sitting on the internet. I am trained to find it and show you verifiable sources. Use the Quick Start Menu to make it easy. | |
| </div> | |
| </div> | |
| <div class="icon-container"> | |
| <img src="https://huggingface.co/spaces/trustlogic/temporary-trustlogic-batch/resolve/main/Analyse.png" class="icon-img analyse-icon" alt="Analyse"> | |
| <div class="tooltip-text"> | |
| <strong>Analyse</strong><br> | |
| I can analyse for you which Trust Buckets® a content piece fills. Paste the URL or copy into the chat field and ask me to tell you which Trust Buckets® are filled and how. You can also do this for competitor websites. I can also give you top-level scores. | |
| </div> | |
| </div> | |
| <div class="icon-container"> | |
| <img src="https://huggingface.co/spaces/trustlogic/temporary-trustlogic-batch/resolve/main/chat.png" class="icon-img chat-icon" alt="Chat"> | |
| <div class="tooltip-text"> | |
| <strong>Chat</strong><br> | |
| Tell me to take the role of your audience. Use the Quick Start Menu or describe your audience. Then let's ideate and create trust-optimised content together. | |
| </div> | |
| </div> | |
| </div> | |
| <div class="welcome-prompt"> | |
| <div class="prompt-title"> | |
| How can TrustAI® help me? | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| else: | |
| # Display conversation history | |
| st.markdown('<div class="chat-container">', unsafe_allow_html=True) | |
| for msg in st.session_state.messages: | |
| if msg["role"] == "user": | |
| st.markdown(f''' | |
| <div class="message-row user"> | |
| <div class="avatar user">U</div> | |
| <div class="user-message">{msg["content"]}</div> | |
| </div> | |
| ''', unsafe_allow_html=True) | |
| else: | |
| st.markdown(f''' | |
| <div class="message-row bot"> | |
| <div class="avatar bot"> | |
| AI | |
| </div> | |
| <div class="bot-message">{msg["content"]}</div> | |
| </div> | |
| ''', unsafe_allow_html=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # ---------- USER INPUT ---------- | |
| user_query = st.chat_input("I am an/a [Title]. What can I do with TrustLogic?") | |
| if not user_query: | |
| st.stop() | |
| # Mark conversation as started | |
| st.session_state.conversation_started = True | |
| # Add user message | |
| st.session_state.messages.append({"role": "user", "content": user_query}) | |
| # Display user message immediately (will disappear on rerun) | |
| st.markdown(f''' | |
| <div class="message-row user"> | |
| <div class="avatar user">U</div> | |
| <div class="user-message">{user_query}</div> | |
| </div> | |
| ''', unsafe_allow_html=True) | |
| # ---------- RESPONSE LOGIC ---------- | |
| response_text = "" | |
| with st.spinner("Analyzing with TrustLogic intelligence..."): | |
| # Knowledge base query | |
| kb_query_engine = index.as_query_engine( | |
| similarity_top_k=5, | |
| text_qa_template=KB_PROMPT_TEMPLATE | |
| ) | |
| kb_response = kb_query_engine.query(user_query) | |
| # Decide if KB is enough or use web search | |
| decision_prompt = f"""{SYSTEM_INSTRUCTION} | |
| Evaluate if this knowledge base response adequately answers the user's question: | |
| User Question: {user_query} | |
| Knowledge Base Response: {str(kb_response)} | |
| Decision criteria: | |
| - Is the response relevant and about TrustLogic methodology or trust-building? | |
| - Does it provide sufficient detail? | |
| - Is this a request to "find TrustBuilders" for a specific organization? (If YES → USE_WEB) | |
| Reply with only: USE_KB or USE_WEB""" | |
| decision = client.chat.completions.create( | |
| model="gpt-4o", | |
| messages=[{"role": "user", "content": decision_prompt}], | |
| ) | |
| decision_text = decision.choices[0].message.content.strip() | |
| if "USE_KB" in decision_text: | |
| response_text = str(kb_response) + FOOTER_MESSAGE | |
| else: | |
| # Replace your Tavily search section with this: | |
| try: | |
| # Try with more appropriate parameters | |
| results = tavily.search( | |
| query=user_query, | |
| max_results=20, # Reduced from 50 | |
| search_depth="advanced", # Explicitly set | |
| include_answer=True, | |
| include_raw_content=False # Can cause issues when True | |
| ) | |
| web_results_list = results.get("results", []) | |
| # Check if we actually got results | |
| if not web_results_list or len(web_results_list) == 0: | |
| # Fallback: try with even simpler parameters | |
| results = tavily.search( | |
| query=user_query, | |
| max_results=8, | |
| search_depth="basic" | |
| ) | |
| web_results_list = results.get("results", []) | |
| if not web_results_list: | |
| # Still no results - inform user properly | |
| response_text = f"I couldn't find specific web results for '{user_query}'. This might be due to the query being too specific or API limitations. Would you like me to try a different search approach?" + FOOTER_MESSAGE | |
| else: | |
| # Process results as you currently do | |
| web_results = "\n".join([ | |
| f"• **{r['title']}** — {r['url']}\n {r.get('content', '')[:300]}..." | |
| for r in web_results_list | |
| ]) | |
| prompt = WEB_SEARCH_PROMPT.format(query=user_query, web_results=web_results) | |
| stream = client.chat.completions.create( | |
| model="gpt-5", # Note: "gpt-5" doesn't exist yet | |
| messages=[ | |
| {"role": "system", "content": SYSTEM_INSTRUCTION}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| stream=True | |
| ) | |
| for chunk in stream: | |
| if chunk.choices[0].delta.content: | |
| response_text += chunk.choices[0].delta.content | |
| except Exception as e: | |
| response_text = f"I encountered an error while searching: {str(e)}. Let me try to help based on my knowledge base." + FOOTER_MESSAGE | |
| if "trustlogic.center" not in response_text.lower(): | |
| response_text += FOOTER_MESSAGE | |
| # Save response and trigger rerun to display cleanly | |
| st.session_state.messages.append({"role": "assistant", "content": response_text}) | |
| st.rerun() |