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) ---------- @st.cache_resource 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( """ """, 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("""
""", unsafe_allow_html=True) # ---------- WELCOME SCREEN OR CHAT ---------- if not st.session_state.conversation_started: st.markdown("""
[ Let's build some trust! ]
Ideate
Ideate
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.
Write
Write
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.
Find
Find
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.
Analyse
Analyse
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.
Chat
Chat
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.
How can TrustAI® help me?
""", unsafe_allow_html=True) else: # Display conversation history st.markdown('
', unsafe_allow_html=True) for msg in st.session_state.messages: if msg["role"] == "user": st.markdown(f'''
U
{msg["content"]}
''', unsafe_allow_html=True) else: st.markdown(f'''
AI
{msg["content"]}
''', unsafe_allow_html=True) st.markdown('
', 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'''
U
{user_query}
''', 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()