demo / app.py
Wajahat698's picture
Update app.py
a2e454e verified
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(
"""
<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()