Spaces:
Runtime error
Runtime error
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +137 -41
src/streamlit_app.py
CHANGED
|
@@ -2,20 +2,11 @@ import os
|
|
| 2 |
import streamlit as st
|
| 3 |
from openai import OpenAI
|
| 4 |
|
| 5 |
-
#
|
| 6 |
-
# CONFIG
|
| 7 |
-
# ------------------------------
|
| 8 |
-
# Hugging Face: Settings -> Variables -> add OPENAI_API_KEY
|
| 9 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or st.secrets.get("OPENAI_API_KEY", None)
|
| 10 |
|
| 11 |
-
st.set_page_config(
|
| 12 |
-
page_title="Procelevate AI Agent",
|
| 13 |
-
page_icon="π€",
|
| 14 |
-
layout="wide"
|
| 15 |
-
)
|
| 16 |
-
|
| 17 |
st.title("π€ Procelevate Agent Suite")
|
| 18 |
-
st.write("PO Automation + Email Responder (Streamlit, HF Docker)")
|
| 19 |
|
| 20 |
if not OPENAI_API_KEY:
|
| 21 |
st.warning("β οΈ OPENAI_API_KEY not found. Set it in the Space secrets.")
|
|
@@ -23,7 +14,6 @@ client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
|
|
| 23 |
|
| 24 |
|
| 25 |
def call_llm(system_prompt: str, user_prompt: str) -> str:
|
| 26 |
-
"""Small helper to call OpenAI."""
|
| 27 |
if client is None:
|
| 28 |
return "βOPENAI_API_KEY not set."
|
| 29 |
resp = client.chat.completions.create(
|
|
@@ -32,22 +22,16 @@ def call_llm(system_prompt: str, user_prompt: str) -> str:
|
|
| 32 |
{"role": "system", "content": system_prompt},
|
| 33 |
{"role": "user", "content": user_prompt},
|
| 34 |
],
|
| 35 |
-
temperature=0.
|
| 36 |
)
|
| 37 |
return resp.choices[0].message.content
|
| 38 |
|
| 39 |
|
| 40 |
-
#
|
| 41 |
-
# PO GENERATOR
|
| 42 |
-
# ------------------------------
|
| 43 |
def generate_po(buyer_name, buyer_address, supplier_name, supplier_email, items_text, payment_terms, notes):
|
| 44 |
system_prompt = """You are a Purchase Order (PO) generation assistant for Procelevate.
|
| 45 |
-
|
| 46 |
-
Output in markdown with clear headings, table for line items, and totals.
|
| 47 |
-
If any field is missing, make reasonable placeholders."""
|
| 48 |
user_prompt = f"""
|
| 49 |
-
Create a Purchase Order with the following details:
|
| 50 |
-
|
| 51 |
Buyer:
|
| 52 |
- Name: {buyer_name}
|
| 53 |
- Address: {buyer_address}
|
|
@@ -56,45 +40,114 @@ Supplier:
|
|
| 56 |
- Name: {supplier_name}
|
| 57 |
- Email: {supplier_email}
|
| 58 |
|
| 59 |
-
Line Items
|
| 60 |
{items_text}
|
| 61 |
|
| 62 |
Payment Terms: {payment_terms}
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
At the end, show Subtotal, Taxes (assume 18% if not given), and Grand Total.
|
| 66 |
"""
|
| 67 |
return call_llm(system_prompt, user_prompt)
|
| 68 |
|
| 69 |
|
| 70 |
-
# ------------------------------
|
| 71 |
-
# EMAIL RESPONDER
|
| 72 |
-
# ------------------------------
|
| 73 |
def generate_email_reply(incoming_email, reply_goal, tone):
|
| 74 |
system_prompt = """You are an AI email responder for Procelevate Consulting & Academy.
|
| 75 |
-
|
| 76 |
-
Always add a polite closing with 'Regards, Procelevate Team' unless user says otherwise."""
|
| 77 |
user_prompt = f"""
|
| 78 |
Incoming email:
|
| 79 |
\"\"\"{incoming_email}\"\"\"
|
| 80 |
-
|
| 81 |
-
Goal of reply: {reply_goal}
|
| 82 |
Tone: {tone}
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
"""
|
| 86 |
return call_llm(system_prompt, user_prompt)
|
| 87 |
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
with tab1:
|
| 95 |
st.subheader("π Generate Purchase Order")
|
| 96 |
col1, col2 = st.columns(2)
|
| 97 |
-
|
| 98 |
with col1:
|
| 99 |
buyer_name = st.text_input("Buyer Name", value="Procelevate Consulting")
|
| 100 |
buyer_address = st.text_area("Buyer Address", value="Bengaluru, Karnataka, India", height=80)
|
|
@@ -110,7 +163,7 @@ with tab1:
|
|
| 110 |
height=120
|
| 111 |
)
|
| 112 |
|
| 113 |
-
if st.button("Generate PO"):
|
| 114 |
po_md = generate_po(
|
| 115 |
buyer_name,
|
| 116 |
buyer_address,
|
|
@@ -124,6 +177,7 @@ with tab1:
|
|
| 124 |
st.markdown("### β
Generated PO")
|
| 125 |
st.markdown(po_md)
|
| 126 |
|
|
|
|
| 127 |
with tab2:
|
| 128 |
st.subheader("π§ Generate Email Reply")
|
| 129 |
incoming_email = st.text_area(
|
|
@@ -141,9 +195,51 @@ with tab2:
|
|
| 141 |
index=0
|
| 142 |
)
|
| 143 |
|
| 144 |
-
if st.button("Generate Reply"):
|
| 145 |
reply_text = generate_email_reply(incoming_email, reply_goal, tone)
|
| 146 |
st.markdown("---")
|
| 147 |
st.markdown("### β
Reply Email")
|
| 148 |
st.markdown(reply_text)
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import streamlit as st
|
| 3 |
from openai import OpenAI
|
| 4 |
|
| 5 |
+
# ===== CONFIG =====
|
|
|
|
|
|
|
|
|
|
| 6 |
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or st.secrets.get("OPENAI_API_KEY", None)
|
| 7 |
|
| 8 |
+
st.set_page_config(page_title="Procelevate AI Agent", page_icon="π€", layout="wide")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
st.title("π€ Procelevate Agent Suite")
|
|
|
|
| 10 |
|
| 11 |
if not OPENAI_API_KEY:
|
| 12 |
st.warning("β οΈ OPENAI_API_KEY not found. Set it in the Space secrets.")
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
def call_llm(system_prompt: str, user_prompt: str) -> str:
|
|
|
|
| 17 |
if client is None:
|
| 18 |
return "βOPENAI_API_KEY not set."
|
| 19 |
resp = client.chat.completions.create(
|
|
|
|
| 22 |
{"role": "system", "content": system_prompt},
|
| 23 |
{"role": "user", "content": user_prompt},
|
| 24 |
],
|
| 25 |
+
temperature=0.25,
|
| 26 |
)
|
| 27 |
return resp.choices[0].message.content
|
| 28 |
|
| 29 |
|
| 30 |
+
# ===== EXISTING FUNCTIONS =====
|
|
|
|
|
|
|
| 31 |
def generate_po(buyer_name, buyer_address, supplier_name, supplier_email, items_text, payment_terms, notes):
|
| 32 |
system_prompt = """You are a Purchase Order (PO) generation assistant for Procelevate.
|
| 33 |
+
Output in markdown with headings, line-item table, and totals. Assume 18% tax if missing."""
|
|
|
|
|
|
|
| 34 |
user_prompt = f"""
|
|
|
|
|
|
|
| 35 |
Buyer:
|
| 36 |
- Name: {buyer_name}
|
| 37 |
- Address: {buyer_address}
|
|
|
|
| 40 |
- Name: {supplier_name}
|
| 41 |
- Email: {supplier_email}
|
| 42 |
|
| 43 |
+
Line Items:
|
| 44 |
{items_text}
|
| 45 |
|
| 46 |
Payment Terms: {payment_terms}
|
| 47 |
+
Notes: {notes}
|
|
|
|
|
|
|
| 48 |
"""
|
| 49 |
return call_llm(system_prompt, user_prompt)
|
| 50 |
|
| 51 |
|
|
|
|
|
|
|
|
|
|
| 52 |
def generate_email_reply(incoming_email, reply_goal, tone):
|
| 53 |
system_prompt = """You are an AI email responder for Procelevate Consulting & Academy.
|
| 54 |
+
Write a concise reply in the requested tone. End with 'Regards, Procelevate Team'."""
|
|
|
|
| 55 |
user_prompt = f"""
|
| 56 |
Incoming email:
|
| 57 |
\"\"\"{incoming_email}\"\"\"
|
| 58 |
+
Goal: {reply_goal}
|
|
|
|
| 59 |
Tone: {tone}
|
| 60 |
+
"""
|
| 61 |
+
return call_llm(system_prompt, user_prompt)
|
| 62 |
+
|
| 63 |
|
| 64 |
+
# ===== NEW: QUOTATION β PR β PO PIPELINE =====
|
| 65 |
+
|
| 66 |
+
def extract_text_from_upload(uploaded_file):
|
| 67 |
+
# simplest path: handle text files directly
|
| 68 |
+
if uploaded_file is None:
|
| 69 |
+
return ""
|
| 70 |
+
if uploaded_file.type == "text/plain":
|
| 71 |
+
return uploaded_file.read().decode("utf-8")
|
| 72 |
+
|
| 73 |
+
# for PDFs we need a parser; if pdfplumber not installed, just say so
|
| 74 |
+
if uploaded_file.type == "application/pdf":
|
| 75 |
+
try:
|
| 76 |
+
import pdfplumber
|
| 77 |
+
text_parts = []
|
| 78 |
+
with pdfplumber.open(uploaded_file) as pdf:
|
| 79 |
+
for page in pdf.pages:
|
| 80 |
+
text_parts.append(page.extract_text() or "")
|
| 81 |
+
return "\n".join(text_parts)
|
| 82 |
+
except Exception as e:
|
| 83 |
+
return f"PDF uploaded but could not parse: {e}"
|
| 84 |
+
|
| 85 |
+
# fallback
|
| 86 |
+
return uploaded_file.read().decode("utf-8", errors="ignore")
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def create_pr_from_quotation(quotation_text, buyer_org):
|
| 90 |
+
system_prompt = """You are a purchasing assistant. Given a supplier quotation text, extract:
|
| 91 |
+
- supplier_name
|
| 92 |
+
- line_items (description, qty, unit_price if present)
|
| 93 |
+
- currency (guess if missing)
|
| 94 |
+
Return a clean JSON-like structure representing a Purchase Requisition (PR) for internal approval."""
|
| 95 |
+
user_prompt = f"""
|
| 96 |
+
Organisation: {buyer_org}
|
| 97 |
+
Quotation text:
|
| 98 |
+
\"\"\"{quotation_text}\"\"\"
|
| 99 |
+
|
| 100 |
+
Return PR as JSON with keys:
|
| 101 |
+
pr_title, supplier_name, currency, line_items, total_if_available.
|
| 102 |
"""
|
| 103 |
return call_llm(system_prompt, user_prompt)
|
| 104 |
|
| 105 |
|
| 106 |
+
def create_approval_email(pr_json_text, approver_email="approver@example.com"):
|
| 107 |
+
system_prompt = """You are an approval workflow assistant. Write an approval request email for a PR.
|
| 108 |
+
Email must include: PR title, supplier, amount, and a clear approve/reject instruction.
|
| 109 |
+
Don't actually send it, just draft."""
|
| 110 |
+
user_prompt = f"""
|
| 111 |
+
PR details (JSON):
|
| 112 |
+
{pr_json_text}
|
| 113 |
+
|
| 114 |
+
Approver email: {approver_email}
|
| 115 |
+
Write the approval email.
|
| 116 |
+
"""
|
| 117 |
+
return call_llm(system_prompt, user_prompt)
|
| 118 |
+
|
| 119 |
|
| 120 |
+
def create_po_from_pr(pr_json_text, buyer_name, buyer_address, supplier_email):
|
| 121 |
+
system_prompt = """You are a PO generator. Convert the approved PR (JSON) into a Purchase Order in markdown.
|
| 122 |
+
Include buyer, supplier, line items table, totals, and payment terms (assume Net 30)."""
|
| 123 |
+
user_prompt = f"""
|
| 124 |
+
Buyer name: {buyer_name}
|
| 125 |
+
Buyer address: {buyer_address}
|
| 126 |
+
Supplier email: {supplier_email}
|
| 127 |
+
PR JSON:
|
| 128 |
+
{pr_json_text}
|
| 129 |
+
"""
|
| 130 |
+
return call_llm(system_prompt, user_prompt)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def create_supplier_email(po_markdown, supplier_email):
|
| 134 |
+
system_prompt = """You are a business email writer. Write an email to supplier attaching/sending PO details.
|
| 135 |
+
Keep it formal, short. Mention PO number if present."""
|
| 136 |
+
user_prompt = f"""
|
| 137 |
+
Supplier: {supplier_email}
|
| 138 |
+
PO content:
|
| 139 |
+
{po_markdown}
|
| 140 |
+
"""
|
| 141 |
+
return call_llm(system_prompt, user_prompt)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# ===== UI TABS =====
|
| 145 |
+
tab1, tab2, tab3 = st.tabs(["π PO Automation", "π§ Email Responder", "π¦ Quotation β PR β PO"])
|
| 146 |
+
|
| 147 |
+
# --- TAB 1: Existing PO form ---
|
| 148 |
with tab1:
|
| 149 |
st.subheader("π Generate Purchase Order")
|
| 150 |
col1, col2 = st.columns(2)
|
|
|
|
| 151 |
with col1:
|
| 152 |
buyer_name = st.text_input("Buyer Name", value="Procelevate Consulting")
|
| 153 |
buyer_address = st.text_area("Buyer Address", value="Bengaluru, Karnataka, India", height=80)
|
|
|
|
| 163 |
height=120
|
| 164 |
)
|
| 165 |
|
| 166 |
+
if st.button("Generate PO", key="gen_po_basic"):
|
| 167 |
po_md = generate_po(
|
| 168 |
buyer_name,
|
| 169 |
buyer_address,
|
|
|
|
| 177 |
st.markdown("### β
Generated PO")
|
| 178 |
st.markdown(po_md)
|
| 179 |
|
| 180 |
+
# --- TAB 2: Email responder ---
|
| 181 |
with tab2:
|
| 182 |
st.subheader("π§ Generate Email Reply")
|
| 183 |
incoming_email = st.text_area(
|
|
|
|
| 195 |
index=0
|
| 196 |
)
|
| 197 |
|
| 198 |
+
if st.button("Generate Reply", key="gen_reply"):
|
| 199 |
reply_text = generate_email_reply(incoming_email, reply_goal, tone)
|
| 200 |
st.markdown("---")
|
| 201 |
st.markdown("### β
Reply Email")
|
| 202 |
st.markdown(reply_text)
|
| 203 |
+
|
| 204 |
+
# --- TAB 3: Quotation β PR β PO ---
|
| 205 |
+
with tab3:
|
| 206 |
+
st.subheader("π¦ Quotation β PR β PO")
|
| 207 |
+
st.write("Upload supplier quotation, let the model draft PR, generate approval mail, and convert to PO after approval.")
|
| 208 |
+
|
| 209 |
+
quotation_file = st.file_uploader("Upload quotation (PDF or .txt)", type=["pdf", "txt"])
|
| 210 |
+
buyer_org = st.text_input("Buyer Org / Company", value="Procelevate Consulting")
|
| 211 |
+
supplier_email_q = st.text_input("Supplier Email (for final PO)", value="supplier@example.com")
|
| 212 |
+
buyer_address_q = st.text_input("Buyer Address", value="Bengaluru, Karnataka, India")
|
| 213 |
+
|
| 214 |
+
if st.button("1οΈβ£ Extract β Create PR"):
|
| 215 |
+
quotation_text = extract_text_from_upload(quotation_file)
|
| 216 |
+
st.markdown("**Extracted text (preview):**")
|
| 217 |
+
st.code(quotation_text[:800] or "No text extracted.", language="text")
|
| 218 |
+
|
| 219 |
+
pr_json_text = create_pr_from_quotation(quotation_text, buyer_org)
|
| 220 |
+
st.markdown("### β
Draft PR (JSON-like)")
|
| 221 |
+
st.code(pr_json_text, language="json")
|
| 222 |
+
|
| 223 |
+
st.session_state["latest_pr"] = pr_json_text
|
| 224 |
+
st.success("PR drafted. Now generate approval email below.")
|
| 225 |
+
|
| 226 |
+
if "latest_pr" in st.session_state:
|
| 227 |
+
if st.button("2οΈβ£ Generate Approval Email"):
|
| 228 |
+
approval_email = create_approval_email(st.session_state["latest_pr"])
|
| 229 |
+
st.markdown("### β
Approval Email (send this via Outlook/Gmail/n8n):")
|
| 230 |
+
st.markdown(approval_email)
|
| 231 |
+
st.session_state["approval_email"] = approval_email
|
| 232 |
+
|
| 233 |
+
if "latest_pr" in st.session_state:
|
| 234 |
+
if st.button("3οΈβ£ Simulate Approval β Generate PO"):
|
| 235 |
+
po_md = create_po_from_pr(st.session_state["latest_pr"], buyer_org, buyer_address_q, supplier_email_q)
|
| 236 |
+
st.markdown("### β
Generated PO (from approved PR)")
|
| 237 |
+
st.markdown(po_md)
|
| 238 |
+
st.session_state["po_md"] = po_md
|
| 239 |
+
|
| 240 |
+
if "po_md" in st.session_state:
|
| 241 |
+
if st.button("4οΈβ£ Prepare Supplier Email"):
|
| 242 |
+
supplier_mail = create_supplier_email(st.session_state["po_md"], supplier_email_q)
|
| 243 |
+
st.markdown("### β
Supplier Email (copy & send)")
|
| 244 |
+
st.markdown(supplier_mail)
|
| 245 |
+
st.info("To auto-send this, connect SMTP or n8n webhook.")
|