Spaces:
Running
Running
File size: 9,414 Bytes
a90f223 dde763f a90f223 0c0a4f7 68ae700 17f9660 5ee4946 0c0a4f7 5ee4946 68ae700 0c0a4f7 a90f223 5ee4946 a90f223 0c0a4f7 a90f223 0c0a4f7 a90f223 0c0a4f7 a90f223 0c0a4f7 a90f223 0c0a4f7 a90f223 0c0a4f7 a90f223 5ee4946 a90f223 5ee4946 a90f223 5720bc1 a90f223 5ee4946 a90f223 5ee4946 a90f223 5ee4946 a90f223 5ee4946 a90f223 0c0a4f7 a90f223 5ee4946 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# StageΒ 1Β UXΒ Shell β FormPilot (no backend yet)
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
import streamlit as st
import pandas as pd
from pathlib import Path
from io import BytesIO
import os
from dotenv import load_dotenv
load_dotenv()
from qdrant_client import QdrantClient
from langchain_community.vectorstores import Qdrant
from langchain_openai import OpenAIEmbeddings
from rag.qa_chain import get_answer
from rag.ocr_azure import parse_passport_azure
if "AZURE_DOC_KEY" not in os.environ:
st.warning("β οΈ OCR disabled β set AZURE_DOC_KEY & AZURE_DOC_ENDPOINT")
st.set_page_config(
page_title="FormPilot β Immigration Paralegal Copilot",
page_icon="π‘οΈ",
layout="wide",
)
# Add custom CSS for better display of extracted information
st.markdown("""
<style>
.extracted-info {
background: #f7f7f7;
padding: 12px;
border-radius: 5px;
border-left: 3px solid #4CAF50;
margin-bottom: 15px;
}
.field-label {
font-weight: bold;
color: #333;
}
.field-value {
color: #1E88E5;
}
.success-value {
color: #4CAF50;
font-weight: bold;
}
.missing-value {
color: #F44336;
font-style: italic;
}
.verification-checklist {
background-color: #f9f9f9;
padding: 15px;
border-radius: 8px;
margin-top: 20px;
}
</style>
""", unsafe_allow_html=True)
# ------------------------------------------------------------
# Helper: generate checklist table with extracted information
# ------------------------------------------------------------
def build_checklist(profile=None):
fields = [
("Iβ485 Part 1 β Full Name", "pending"),
("Iβ485 Part 1 β AβNumber", "pending"),
("Iβ485 Part 1 β Date of Birth", "pending"),
("Iβ485 Part 2 β Basis of Application", "pending"),
]
df = pd.DataFrame(fields, columns=["Field", "Status"])
# Update with extracted information if available
if profile:
# Full Name
if profile.get('FirstName') and profile.get('LastName'):
full_name = f"{profile.get('FirstName')} {profile.get('LastName')}"
df.loc[df["Field"].str.contains("Full Name"), "Status"] = full_name
# A-Number
if profile.get('ANumber'):
df.loc[df["Field"].str.contains("AβNumber"), "Status"] = profile.get('ANumber')
# Date of Birth
if profile.get('DateOfBirth'):
df.loc[df["Field"].str.contains("Date of Birth"), "Status"] = profile.get('DateOfBirth')
return df
# ------------------------------------------------------------
# Helper: Format extracted information for display
# ------------------------------------------------------------
def display_extracted_info(profile):
if not profile:
return st.info("No information extracted from documents.")
st.markdown('<div class="verification-checklist">', unsafe_allow_html=True)
# Display First Name
st.markdown(
f'<div class="extracted-info">'
f'<span class="field-label">First Name:</span> '
f'<span class="{"success-value" if profile.get("FirstName") else "missing-value"}">'
f'{profile.get("FirstName", "Not found")}</span>'
f'</div>',
unsafe_allow_html=True
)
# Display Last Name
st.markdown(
f'<div class="extracted-info">'
f'<span class="field-label">Last Name:</span> '
f'<span class="{"success-value" if profile.get("LastName") else "missing-value"}">'
f'{profile.get("LastName", "Not found")}</span>'
f'</div>',
unsafe_allow_html=True
)
# Display Date of Birth
st.markdown(
f'<div class="extracted-info">'
f'<span class="field-label">Date of Birth:</span> '
f'<span class="{"success-value" if profile.get("DateOfBirth") else "missing-value"}">'
f'{profile.get("DateOfBirth", "Not found")}</span>'
f'</div>',
unsafe_allow_html=True
)
# Display A-Number (if available)
st.markdown(
f'<div class="extracted-info">'
f'<span class="field-label">A-Number:</span> '
f'<span class="{"success-value" if profile.get("ANumber") else "missing-value"}">'
f'{profile.get("ANumber", "Not applicable")}</span>'
f'</div>',
unsafe_allow_html=True
)
st.markdown('</div>', unsafe_allow_html=True)
# ------------------------------------------------------------
# SidebarΒ β navigation + brand
# ------------------------------------------------------------
with st.sidebar:
st.title("βοΈ+π+π‘οΈ+πΒ FormPilot")
if "stage" not in st.session_state:
st.session_state.stage = "home"
st.markdown("---")
if st.button("π Β Home"):
st.session_state.stage = "home"
if st.button("πΒ Draft Packet"):
if "uploaded_files" in st.session_state:
st.session_state.stage = "draft"
# ------------------------------------------------------------
# StageΒ AΒ β Upload page
# ------------------------------------------------------------
if st.session_state.stage == "home":
st.header("New Case β Build Iβ485 Package")
uploaded_files = st.file_uploader(
"Upload client documents (passport, visa, etc.)",
type=["pdf", "jpg", "jpeg", "png"],
accept_multiple_files=True,
)
form_choice = st.selectbox(
"Select USCIS Form to prepare",
options=["Iβ485 (Adjustment of Status)"],
)
# Store uploads in session so we can view later
if st.button("π Build Package"):
if not uploaded_files:
st.warning("Please upload at least one document.")
else:
st.session_state.uploaded_files = uploaded_files
st.session_state.form_choice = form_choice
st.session_state.stage = "draft"
st.rerun()
st.info(
"""
**What happens next?**
In StageΒ 2+ the AI will ingest your uploads, retrieve instructions,
and draft the packet. For now, we jump to a placeholder Draft view.
"""
)
# ------------------------------------------------------------
# StageΒ BΒ β Draft Packet page
# ------------------------------------------------------------
elif st.session_state.stage == "draft":
st.header("Draft Packet (Stage 3 β Retrieval MVP))")
# Two-column layout: preview & checklist
col_left, col_right = st.columns([2, 1])
with col_left:
st.subheader("πΒ PDF Preview")
st.write(
"A preview of the filled Iβ485 will appear here once AI preβfill is ready."
)
st.image(
"https://placehold.co/600x800?text=PDF+Preview",
caption="Static placeholder preview",
)
with col_right:
st.subheader("β
Document Information")
profile = {}
# Try to get profile from session state first
if "profile" in st.session_state:
profile = st.session_state.profile
# Otherwise, try to process the first uploaded file
elif "uploaded_files" in st.session_state and st.session_state.uploaded_files:
with st.spinner("Extracting information..."):
data = st.session_state.uploaded_files[0].getvalue()
try:
profile = parse_passport_azure(data)
st.session_state.profile = profile
except Exception as e:
st.error(f"Error extracting information: {str(e)}")
# Display nicely formatted extracted information
if profile and (profile.get("FirstName") or profile.get("LastName") or profile.get("DateOfBirth")):
display_extracted_info(profile)
# Show checklist with auto-filled fields
st.subheader("Form Field Checklist")
df = build_checklist(profile)
st.dataframe(df, hide_index=True, width=350)
# Show the raw JSON for debugging
with st.expander("Raw Extracted Data"):
st.json(profile)
else:
st.warning("No profile information extracted from documents.")
df = build_checklist()
st.dataframe(df, hide_index=True, width=350)
st.markdown("---")
st.subheader("Ask about Iβ485 instructions")
q = st.text_input("Your question", key="qa")
if q:
with st.spinner("Retrieving..."):
ans, cites = get_answer(q)
st.success(ans)
st.caption("Sources: " + ", ".join(sorted(cites)))
# ------------------------------------------------------------
# (Optional) Future stage for OCR parsing
# ------------------------------------------------------------
# Example usage of doc_parse_stub
if False:
for file in st.session_state.get("uploaded_files", []):
data = file.read()
parsed = parse_passport(data)
st.write(parsed)
|