Spaces:
Sleeping
Sleeping
Upload streamlit_app.py
Browse files- streamlit_app.py +717 -540
streamlit_app.py
CHANGED
|
@@ -12,8 +12,6 @@ import plotly.graph_objects as go
|
|
| 12 |
from collections import Counter
|
| 13 |
import nltk
|
| 14 |
import string
|
| 15 |
-
|
| 16 |
-
from canonical_mapping import canonicalize_found_terms
|
| 17 |
from datetime import datetime
|
| 18 |
import warnings
|
| 19 |
import logging
|
|
@@ -914,9 +912,9 @@ class ChromaVectorStore:
|
|
| 914 |
'is_video_professional': job_categories.get('is_video_professional', False),
|
| 915 |
'is_photo_professional': job_categories.get('is_photo_professional', False),
|
| 916 |
'is_creative_professional': job_categories.get('is_creative_professional', False),
|
| 917 |
-
'adobe_apps': creative_analysis.get('adobe_apps', []),
|
| 918 |
-
'non_adobe_apps': creative_analysis.get('non_adobe_apps', []),
|
| 919 |
-
'ai_tools': creative_analysis.get('ai_tools', []),
|
| 920 |
'has_adobe': len(creative_analysis.get('adobe_apps', [])) > 0,
|
| 921 |
'has_non_adobe': len(creative_analysis.get('non_adobe_apps', [])) > 0,
|
| 922 |
'has_ai_tools': len(creative_analysis.get('ai_tools', [])) > 0,
|
|
@@ -1032,40 +1030,14 @@ class ChromaVectorStore:
|
|
| 1032 |
|
| 1033 |
embeddings_list = embeddings.tolist()
|
| 1034 |
|
| 1035 |
-
|
| 1036 |
# Create document IDs
|
| 1037 |
doc_ids = [f"doc_{i+j}" for j in range(len(batch_texts))]
|
| 1038 |
-
|
| 1039 |
-
# Sanitize metadata: Chroma expects primitive metadata values (str,int,float,bool,None)
|
| 1040 |
-
sanitized_metadatas = []
|
| 1041 |
-
for md in batch_metadatas:
|
| 1042 |
-
clean_md = {}
|
| 1043 |
-
if not md:
|
| 1044 |
-
sanitized_metadatas.append(clean_md)
|
| 1045 |
-
continue
|
| 1046 |
-
for k, v in md.items():
|
| 1047 |
-
# Convert lists to comma-separated strings
|
| 1048 |
-
if isinstance(v, list):
|
| 1049 |
-
clean_md[k] = ', '.join(str(x) for x in v)
|
| 1050 |
-
# Convert dicts to JSON string
|
| 1051 |
-
elif isinstance(v, dict):
|
| 1052 |
-
try:
|
| 1053 |
-
clean_md[k] = json.dumps(v)
|
| 1054 |
-
except Exception:
|
| 1055 |
-
clean_md[k] = str(v)
|
| 1056 |
-
# Accept primitive types
|
| 1057 |
-
elif isinstance(v, (str, int, float, bool)) or v is None:
|
| 1058 |
-
clean_md[k] = v
|
| 1059 |
-
else:
|
| 1060 |
-
# Fallback to string representation
|
| 1061 |
-
clean_md[k] = str(v)
|
| 1062 |
-
sanitized_metadatas.append(clean_md)
|
| 1063 |
-
|
| 1064 |
# Add to Chroma collection
|
| 1065 |
self.collection.add(
|
| 1066 |
embeddings=embeddings_list,
|
| 1067 |
documents=batch_texts,
|
| 1068 |
-
metadatas=
|
| 1069 |
ids=doc_ids
|
| 1070 |
)
|
| 1071 |
|
|
@@ -1116,7 +1088,7 @@ class ChromaVectorStore:
|
|
| 1116 |
distances = results.get('distances', [[]])[0]
|
| 1117 |
|
| 1118 |
for i, (doc, metadata, distance) in enumerate(zip(documents, metadatas, distances)):
|
| 1119 |
-
similarity_score = max(0.0,
|
| 1120 |
|
| 1121 |
# Convert string lists back to actual lists for certain fields
|
| 1122 |
if isinstance(metadata.get('adobe_apps'), str):
|
|
@@ -1806,87 +1778,7 @@ def generate_qa_answers(analysis_results: Dict, processed_df: pd.DataFrame) -> D
|
|
| 1806 |
except Exception as e:
|
| 1807 |
logger.error(f"Error generating Q&A answers: {str(e)}")
|
| 1808 |
return answers
|
| 1809 |
-
|
| 1810 |
class EnhancedOpenAIProcessor:
|
| 1811 |
-
def query_with_enhanced_rag(self, question: str, vector_store, dataset_analysis: Dict = None) -> str:
|
| 1812 |
-
"""Process queries with enhanced RAG optimized for q.txt questions"""
|
| 1813 |
-
|
| 1814 |
-
if not self.is_available():
|
| 1815 |
-
return f"OpenAI client not available: {self._initialization_error}"
|
| 1816 |
-
|
| 1817 |
-
# Ensure dataset_analysis is a dict to avoid NoneType errors
|
| 1818 |
-
dataset_analysis = dataset_analysis or {}
|
| 1819 |
-
|
| 1820 |
-
try:
|
| 1821 |
-
# Retrieve relevant documents
|
| 1822 |
-
retrieved_docs = []
|
| 1823 |
-
try:
|
| 1824 |
-
retrieved_docs = vector_store.search(question, k=10)
|
| 1825 |
-
logger.info(f"Retrieved {len(retrieved_docs)} documents for query")
|
| 1826 |
-
except Exception as search_error:
|
| 1827 |
-
logger.warning(f"Vector search failed: {search_error}")
|
| 1828 |
-
|
| 1829 |
-
# Create enhanced system prompt
|
| 1830 |
-
system_prompt = self.create_enhanced_system_prompt(dataset_analysis)
|
| 1831 |
-
|
| 1832 |
-
# Prepare RAG context
|
| 1833 |
-
rag_context = self.prepare_rag_context(retrieved_docs, question, dataset_analysis)
|
| 1834 |
-
|
| 1835 |
-
# Create user message
|
| 1836 |
-
user_message = f"Question: {question}"
|
| 1837 |
-
if rag_context:
|
| 1838 |
-
user_message += f"\n\nRelevant Data:\n{rag_context}"
|
| 1839 |
-
|
| 1840 |
-
# Token management
|
| 1841 |
-
system_tokens = self.count_tokens(system_prompt)
|
| 1842 |
-
user_tokens = self.count_tokens(user_message)
|
| 1843 |
-
total_input_tokens = system_tokens + user_tokens
|
| 1844 |
-
|
| 1845 |
-
if total_input_tokens > self.max_input_tokens:
|
| 1846 |
-
# Reduce RAG context if needed
|
| 1847 |
-
excess_tokens = total_input_tokens - self.max_input_tokens
|
| 1848 |
-
if rag_context:
|
| 1849 |
-
current_context_tokens = self.count_tokens(rag_context)
|
| 1850 |
-
reduced_tokens = max(200, current_context_tokens - excess_tokens)
|
| 1851 |
-
rag_context = self._truncate_text(rag_context, reduced_tokens)
|
| 1852 |
-
user_message = f"Question: {question}\n\nRelevant Data:\n{rag_context}"
|
| 1853 |
-
|
| 1854 |
-
# Calculate available completion tokens
|
| 1855 |
-
final_input_tokens = self.count_tokens(system_prompt) + self.count_tokens(user_message)
|
| 1856 |
-
available_tokens = self.max_context_length - final_input_tokens - 100
|
| 1857 |
-
completion_tokens = min(self.max_completion_tokens, max(500, available_tokens))
|
| 1858 |
-
|
| 1859 |
-
# Make API call
|
| 1860 |
-
try:
|
| 1861 |
-
response = self.client.chat.completions.create(
|
| 1862 |
-
model="gpt-3.5-turbo",
|
| 1863 |
-
messages=[
|
| 1864 |
-
{"role": "system", "content": system_prompt},
|
| 1865 |
-
{"role": "user", "content": user_message}
|
| 1866 |
-
],
|
| 1867 |
-
max_tokens=completion_tokens,
|
| 1868 |
-
temperature=0.2, # Lower temperature for more consistent analysis
|
| 1869 |
-
timeout=30
|
| 1870 |
-
)
|
| 1871 |
-
|
| 1872 |
-
answer = response.choices[0].message.content
|
| 1873 |
-
logger.info(f"Successfully processed query: {question[:50]}...")
|
| 1874 |
-
return answer
|
| 1875 |
-
|
| 1876 |
-
except openai.APIError as api_error:
|
| 1877 |
-
error_msg = f"OpenAI API Error: {str(api_error)}"
|
| 1878 |
-
logger.error(error_msg)
|
| 1879 |
-
return f"API Error: {error_msg}"
|
| 1880 |
-
|
| 1881 |
-
except Exception as api_error:
|
| 1882 |
-
error_msg = f"API call failed: {str(api_error)}"
|
| 1883 |
-
logger.error(error_msg)
|
| 1884 |
-
return f"Error: {error_msg}"
|
| 1885 |
-
|
| 1886 |
-
except Exception as e:
|
| 1887 |
-
logger.error(f"Error in enhanced RAG query: {e}")
|
| 1888 |
-
return f"Processing error: {str(e)}"
|
| 1889 |
-
|
| 1890 |
def __init__(self, api_key: str):
|
| 1891 |
self._api_key = api_key
|
| 1892 |
self.client = None
|
|
@@ -2091,6 +1983,85 @@ When answering questions, structure responses with:
|
|
| 2091 |
truncated = test_text
|
| 2092 |
|
| 2093 |
return truncated + "\n[Content truncated for length...]" if truncated else text[:max_tokens*4]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2094 |
|
| 2095 |
class EnhancedCreativeJobProcessor:
|
| 2096 |
"""Main processor class optimized for creative job analysis with Chroma persistence and auto-detection"""
|
|
@@ -2468,105 +2439,119 @@ class EnhancedCreativeJobProcessor:
|
|
| 2468 |
|
| 2469 |
return False
|
| 2470 |
|
| 2471 |
-
def
|
| 2472 |
-
"""
|
|
|
|
|
|
|
| 2473 |
|
| 2474 |
-
|
| 2475 |
-
|
| 2476 |
|
| 2477 |
-
|
| 2478 |
-
|
| 2479 |
|
| 2480 |
-
|
| 2481 |
-
|
| 2482 |
-
if not api_key:
|
| 2483 |
-
st.error("β **OpenAI API Key Missing**: Please configure OPENAI_API_KEY in your secrets or environment variables")
|
| 2484 |
-
st.info("For Streamlit Cloud, add it to your app secrets. For local development, set it as an environment variable.")
|
| 2485 |
-
st.stop()
|
| 2486 |
|
| 2487 |
-
|
| 2488 |
-
|
| 2489 |
-
|
| 2490 |
-
|
| 2491 |
-
|
| 2492 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2493 |
|
| 2494 |
-
|
| 2495 |
-
|
| 2496 |
-
st.
|
| 2497 |
-
elif CHROMA_AVAILABLE:
|
| 2498 |
-
st.info("πΎ In-memory mode - ChromaDB available but persistence disabled")
|
| 2499 |
else:
|
| 2500 |
-
st.warning("β οΈ
|
| 2501 |
|
| 2502 |
-
|
| 2503 |
-
|
| 2504 |
-
|
| 2505 |
-
|
| 2506 |
-
|
| 2507 |
-
st.
|
| 2508 |
-
st.
|
| 2509 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2510 |
|
| 2511 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2512 |
|
| 2513 |
-
|
| 2514 |
-
|
| 2515 |
-
|
| 2516 |
-
|
| 2517 |
-
st.caption(f"Path: {CHROMA_PERSIST_PATH}")
|
| 2518 |
else:
|
| 2519 |
-
st.
|
| 2520 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2521 |
if CHROMA_AVAILABLE:
|
| 2522 |
-
st.success("β
ChromaDB
|
| 2523 |
else:
|
| 2524 |
-
st.error("β ChromaDB
|
| 2525 |
-
|
| 2526 |
-
|
| 2527 |
-
|
| 2528 |
-
|
| 2529 |
-
# Dependency status
|
| 2530 |
-
dep_status = []
|
| 2531 |
-
if USEARCH_AVAILABLE:
|
| 2532 |
-
dep_status.append("β
USearch (Fallback)")
|
| 2533 |
else:
|
| 2534 |
-
|
| 2535 |
-
|
| 2536 |
-
|
| 2537 |
-
|
|
|
|
|
|
|
| 2538 |
else:
|
| 2539 |
-
|
| 2540 |
-
|
| 2541 |
-
|
| 2542 |
-
|
|
|
|
| 2543 |
else:
|
| 2544 |
-
|
| 2545 |
-
|
| 2546 |
-
|
| 2547 |
-
|
| 2548 |
-
|
| 2549 |
-
|
| 2550 |
-
st.warning(status)
|
| 2551 |
-
else:
|
| 2552 |
-
st.error(status)
|
| 2553 |
-
|
| 2554 |
-
st.markdown("---")
|
| 2555 |
-
st.header("π Analysis Settings")
|
| 2556 |
-
|
| 2557 |
-
k_results = st.slider("Retrieved Documents", 5, 15, 10)
|
| 2558 |
-
show_debug = st.checkbox("Show Debug Information", False)
|
| 2559 |
-
|
| 2560 |
-
# Database management controls
|
| 2561 |
-
st.markdown("---")
|
| 2562 |
-
st.header("ποΈ Database Management")
|
| 2563 |
-
|
| 2564 |
-
# Only show if Chroma is available
|
| 2565 |
-
if CHROMA_AVAILABLE:
|
| 2566 |
-
force_rebuild = st.checkbox("Force Index Rebuild",
|
| 2567 |
-
help="Force rebuilding the RAG index even if existing data is found")
|
| 2568 |
|
| 2569 |
-
# Initialize session state
|
| 2570 |
if 'processor' not in st.session_state:
|
| 2571 |
st.session_state.processor = EnhancedCreativeJobProcessor()
|
| 2572 |
|
|
@@ -2579,276 +2564,235 @@ def main():
|
|
| 2579 |
if 'auto_detected_mode' not in st.session_state:
|
| 2580 |
st.session_state.auto_detected_mode = False
|
| 2581 |
|
| 2582 |
-
|
| 2583 |
-
|
| 2584 |
-
try:
|
| 2585 |
-
st.session_state.openai_processor = EnhancedOpenAIProcessor(api_key)
|
| 2586 |
-
if st.session_state.openai_processor.is_available():
|
| 2587 |
-
st.sidebar.success("β
OpenAI API Ready")
|
| 2588 |
-
else:
|
| 2589 |
-
st.sidebar.error(f"β OpenAI Error: {st.session_state.openai_processor._initialization_error}")
|
| 2590 |
-
except Exception as e:
|
| 2591 |
-
st.sidebar.error(f"β OpenAI Error: {str(e)}")
|
| 2592 |
|
| 2593 |
-
|
| 2594 |
-
|
| 2595 |
-
if st.session_state.processor.check_auto_detected_index():
|
| 2596 |
-
st.session_state.auto_detected_mode = True
|
| 2597 |
-
st.session_state.rag_ready = True
|
| 2598 |
-
|
| 2599 |
-
# Show option to proceed with queries immediately
|
| 2600 |
-
col1, col2 = st.columns(2)
|
| 2601 |
-
with col1:
|
| 2602 |
-
if st.button("π Start Querying Immediately", type="primary", help="Use existing index for queries"):
|
| 2603 |
-
if st.session_state.processor.use_auto_detected_index():
|
| 2604 |
-
st.balloons()
|
| 2605 |
-
st.success("π Ready for intelligent queries!")
|
| 2606 |
-
# Jump to query section by setting flag
|
| 2607 |
-
st.session_state.rag_ready = True
|
| 2608 |
-
st.experimental_rerun()
|
| 2609 |
-
|
| 2610 |
-
with col2:
|
| 2611 |
-
if st.button("π Upload New Dataset", help="Replace existing index with new data"):
|
| 2612 |
-
# Attempt to prepare the environment for uploading a new dataset by clearing existing index.
|
| 2613 |
-
try:
|
| 2614 |
-
proc = st.session_state.get('processor', None)
|
| 2615 |
-
if proc and hasattr(proc, 'vector_store'):
|
| 2616 |
-
vs = proc.vector_store
|
| 2617 |
-
# Prefer calling a clear_collection method on the vector store if available.
|
| 2618 |
-
cleared = False
|
| 2619 |
-
if hasattr(vs, 'clear_collection'):
|
| 2620 |
-
try:
|
| 2621 |
-
cleared = vs.clear_collection()
|
| 2622 |
-
except Exception:
|
| 2623 |
-
cleared = False
|
| 2624 |
-
elif hasattr(vs, 'chroma_store') and vs.chroma_store:
|
| 2625 |
-
try:
|
| 2626 |
-
cleared = vs.chroma_store.clear_collection()
|
| 2627 |
-
except Exception:
|
| 2628 |
-
cleared = False
|
| 2629 |
-
if cleared:
|
| 2630 |
-
st.success("β
Existing index cleared. You can now upload a new dataset.")
|
| 2631 |
-
else:
|
| 2632 |
-
st.info("β οΈ Proceeding to upload. The existing index (if any) will be overwritten when you build the new index.")
|
| 2633 |
-
# Switch out of auto-detected mode and show the normal upload workflow
|
| 2634 |
-
st.session_state.auto_detected_mode = False
|
| 2635 |
-
st.session_state.rag_ready = False
|
| 2636 |
-
st.experimental_rerun()
|
| 2637 |
-
except Exception as e:
|
| 2638 |
-
st.error(f"Error preparing upload: {e}")
|
| 2639 |
-
|
| 2640 |
-
# Show different interfaces based on mode
|
| 2641 |
-
if st.session_state.auto_detected_mode and st.session_state.rag_ready:
|
| 2642 |
-
# Auto-detected mode - show query interface directly
|
| 2643 |
-
show_query_interface(st.session_state.processor, st.session_state.openai_processor, k_results, show_debug)
|
| 2644 |
-
else:
|
| 2645 |
-
# Normal mode - show file upload and processing
|
| 2646 |
-
show_normal_workflow(st.session_state.processor, st.session_state.openai_processor, k_results, show_debug, locals().get('force_rebuild', False))
|
| 2647 |
-
|
| 2648 |
-
def show_query_interface(processor, openai_processor, k_results, show_debug):
|
| 2649 |
-
"""Show the query interface for RAG-enhanced analysis"""
|
| 2650 |
|
| 2651 |
-
|
| 2652 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2653 |
|
| 2654 |
-
#
|
|
|
|
| 2655 |
stats = processor.vector_store.get_stats()
|
| 2656 |
-
col1, col2, col3, col4 = st.columns(4)
|
| 2657 |
-
with col1:
|
| 2658 |
-
doc_count = stats.get('documents_count', 0)
|
| 2659 |
-
st.info(f"π Documents: {doc_count}")
|
| 2660 |
-
with col2:
|
| 2661 |
-
st.info(f"π― Retrieval: Top {k_results}")
|
| 2662 |
-
with col3:
|
| 2663 |
-
backend = stats.get('backend', 'unknown')
|
| 2664 |
-
st.info(f"β‘ Backend: {backend}")
|
| 2665 |
-
with col4:
|
| 2666 |
-
if stats.get('persistent', False):
|
| 2667 |
-
st.success("πΎ Persistent")
|
| 2668 |
-
else:
|
| 2669 |
-
st.warning("πΎ Memory")
|
| 2670 |
|
| 2671 |
-
|
| 2672 |
-
|
| 2673 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2674 |
|
| 2675 |
-
|
| 2676 |
-
|
|
|
|
|
|
|
| 2677 |
st.markdown("""
|
| 2678 |
-
**
|
| 2679 |
-
-
|
| 2680 |
-
-
|
| 2681 |
-
-
|
| 2682 |
-
|
| 2683 |
-
|
| 2684 |
-
- How many records describe a designer role?
|
| 2685 |
-
- Find all designer roles and summarize their creative job requirements
|
| 2686 |
-
- What are the top job titles among designer roles?
|
| 2687 |
-
|
| 2688 |
-
**Cross-Disciplinary Requirements:**
|
| 2689 |
-
- Which jobs are not video jobs but still require video editing tools? What video tools are they?
|
| 2690 |
-
- Which jobs are not photo jobs but still require photo editing tools? What photo tools are they?
|
| 2691 |
-
- Which jobs are not design jobs but still require design editing tools? What design tools are they?
|
| 2692 |
|
| 2693 |
-
|
| 2694 |
-
|
| 2695 |
-
|
| 2696 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2697 |
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2698 |
|
| 2699 |
-
#
|
| 2700 |
-
|
| 2701 |
-
|
| 2702 |
-
placeholder="e.g., How many designer roles require Adobe software vs non-Adobe alternatives?",
|
| 2703 |
-
height=100
|
| 2704 |
-
)
|
| 2705 |
|
| 2706 |
-
|
| 2707 |
-
show_retrieved = st.checkbox("Show Retrieved Context", value=True)
|
| 2708 |
|
| 2709 |
-
|
| 2710 |
-
if
|
| 2711 |
-
st.
|
| 2712 |
-
|
| 2713 |
-
|
| 2714 |
-
with st.spinner("Performing intelligent analysis..."):
|
| 2715 |
-
try:
|
| 2716 |
-
response = openai_processor.query_with_enhanced_rag(
|
| 2717 |
-
question,
|
| 2718 |
-
processor.vector_store,
|
| 2719 |
-
processor.dataset_analysis
|
| 2720 |
-
)
|
| 2721 |
-
|
| 2722 |
-
st.subheader("π Analysis Results")
|
| 2723 |
-
st.write(response)
|
| 2724 |
-
|
| 2725 |
-
# Show retrieved context if requested
|
| 2726 |
-
if show_retrieved:
|
| 2727 |
-
with st.expander("π Retrieved Context"):
|
| 2728 |
-
retrieved_docs = processor.vector_store.search(question, k=k_results)
|
| 2729 |
-
if retrieved_docs:
|
| 2730 |
-
for i, doc in enumerate(retrieved_docs, 1):
|
| 2731 |
-
st.write(f"**Document {i}** (Score: {doc['score']:.3f})")
|
| 2732 |
-
st.write(doc['text'])
|
| 2733 |
-
|
| 2734 |
-
metadata = doc.get('metadata', {})
|
| 2735 |
-
if metadata:
|
| 2736 |
-
info_parts = []
|
| 2737 |
-
if metadata.get('company'):
|
| 2738 |
-
info_parts.append(f"Company: {metadata['company']}")
|
| 2739 |
-
|
| 2740 |
-
# Handle both string and list formats for software
|
| 2741 |
-
adobe_apps = metadata.get('adobe_apps', [])
|
| 2742 |
-
if isinstance(adobe_apps, str):
|
| 2743 |
-
adobe_apps = [app.strip() for app in adobe_apps.split(',') if app.strip()]
|
| 2744 |
-
if adobe_apps:
|
| 2745 |
-
info_parts.append(f"Adobe: {', '.join(adobe_apps)}")
|
| 2746 |
-
|
| 2747 |
-
non_adobe_apps = metadata.get('non_adobe_apps', [])
|
| 2748 |
-
if isinstance(non_adobe_apps, str):
|
| 2749 |
-
non_adobe_apps = [app.strip() for app in non_adobe_apps.split(',') if app.strip()]
|
| 2750 |
-
if non_adobe_apps:
|
| 2751 |
-
info_parts.append(f"Non-Adobe: {', '.join(non_adobe_apps)}")
|
| 2752 |
-
|
| 2753 |
-
if info_parts:
|
| 2754 |
-
st.caption(" | ".join(info_parts))
|
| 2755 |
-
st.markdown("---")
|
| 2756 |
-
else:
|
| 2757 |
-
st.write("No relevant documents retrieved")
|
| 2758 |
-
|
| 2759 |
-
except Exception as e:
|
| 2760 |
-
st.error(f"Query processing error: {str(e)}")
|
| 2761 |
-
if show_debug:
|
| 2762 |
-
st.exception(e)
|
| 2763 |
|
| 2764 |
-
|
| 2765 |
-
|
| 2766 |
-
|
|
|
|
|
|
|
| 2767 |
|
| 2768 |
-
with
|
| 2769 |
-
|
| 2770 |
-
|
| 2771 |
-
|
| 2772 |
-
|
| 2773 |
-
|
| 2774 |
-
# Try to get quick stats from vector store
|
| 2775 |
-
st.info("Analyzing designer roles from index...")
|
| 2776 |
|
| 2777 |
-
with
|
| 2778 |
-
if
|
| 2779 |
-
|
| 2780 |
-
|
| 2781 |
-
|
| 2782 |
-
|
| 2783 |
-
|
|
|
|
| 2784 |
|
| 2785 |
-
with
|
| 2786 |
-
|
| 2787 |
-
|
| 2788 |
-
|
| 2789 |
-
|
| 2790 |
-
|
| 2791 |
-
|
|
|
|
| 2792 |
|
| 2793 |
-
|
| 2794 |
-
st.markdown("---")
|
| 2795 |
-
if st.button("π Upload New Dataset", help="Replace current index with new data"):
|
| 2796 |
-
# Attempt to clear existing index so user can upload a new dataset that will overwrite it.
|
| 2797 |
-
try:
|
| 2798 |
-
proc = st.session_state.get('processor', None)
|
| 2799 |
-
if proc and hasattr(proc, 'vector_store'):
|
| 2800 |
-
vs = proc.vector_store
|
| 2801 |
-
cleared = False
|
| 2802 |
-
if hasattr(vs, 'clear_collection'):
|
| 2803 |
-
try:
|
| 2804 |
-
cleared = vs.clear_collection()
|
| 2805 |
-
except Exception:
|
| 2806 |
-
cleared = False
|
| 2807 |
-
elif hasattr(vs, 'chroma_store') and vs.chroma_store:
|
| 2808 |
-
try:
|
| 2809 |
-
cleared = vs.chroma_store.clear_collection()
|
| 2810 |
-
except Exception:
|
| 2811 |
-
cleared = False
|
| 2812 |
-
if cleared:
|
| 2813 |
-
st.success("β
Existing index cleared. You can now upload a new dataset.")
|
| 2814 |
-
else:
|
| 2815 |
-
st.info("β οΈ Proceeding to upload. The existing index (if any) will be overwritten when you build the new index.")
|
| 2816 |
-
st.session_state.auto_detected_mode = False
|
| 2817 |
-
st.session_state.rag_ready = False
|
| 2818 |
-
st.experimental_rerun()
|
| 2819 |
-
except Exception as e:
|
| 2820 |
-
st.error(f"Error preparing upload: {e}")
|
| 2821 |
-
|
| 2822 |
-
def show_normal_workflow(processor, openai_processor, k_results, show_debug, force_rebuild):
|
| 2823 |
-
"""Show the normal workflow for uploading and processing new data"""
|
| 2824 |
|
| 2825 |
-
# Display
|
| 2826 |
-
|
| 2827 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2828 |
|
| 2829 |
-
|
| 2830 |
-
|
| 2831 |
-
|
| 2832 |
-
|
| 2833 |
-
|
| 2834 |
-
|
| 2835 |
-
|
| 2836 |
-
|
| 2837 |
-
|
| 2838 |
-
|
| 2839 |
-
|
| 2840 |
-
|
| 2841 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2842 |
if processor.vector_store.use_chroma and processor.vector_store.chroma_store:
|
| 2843 |
if processor.vector_store.chroma_store.clear_collection():
|
| 2844 |
-
st.success("β
|
| 2845 |
st.session_state.rag_ready = False
|
| 2846 |
-
st.
|
| 2847 |
-
|
| 2848 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2849 |
|
| 2850 |
# File upload section
|
| 2851 |
-
st.
|
|
|
|
| 2852 |
uploaded_file = st.file_uploader(
|
| 2853 |
"Choose a CSV file containing creative job data",
|
| 2854 |
type="csv",
|
|
@@ -2856,11 +2800,10 @@ def show_normal_workflow(processor, openai_processor, k_results, show_debug, for
|
|
| 2856 |
)
|
| 2857 |
|
| 2858 |
if uploaded_file is not None:
|
| 2859 |
-
#
|
| 2860 |
if processor.vector_store.collection_exists():
|
| 2861 |
-
st.warning("β οΈ **Data Overwrite Warning**: Uploading a new CSV will replace the existing RAG index
|
| 2862 |
-
|
| 2863 |
-
if not st.checkbox("I understand that existing data will be overwritten"):
|
| 2864 |
st.stop()
|
| 2865 |
|
| 2866 |
# Load data
|
|
@@ -2882,7 +2825,8 @@ def show_normal_workflow(processor, openai_processor, k_results, show_debug, for
|
|
| 2882 |
st.dataframe(processor.df.head(), use_container_width=True)
|
| 2883 |
|
| 2884 |
# Processing workflow
|
| 2885 |
-
st.
|
|
|
|
| 2886 |
|
| 2887 |
col1, col2, col3 = st.columns(3)
|
| 2888 |
|
|
@@ -2891,6 +2835,7 @@ def show_normal_workflow(processor, openai_processor, k_results, show_debug, for
|
|
| 2891 |
with st.spinner("Cleaning and processing data..."):
|
| 2892 |
if processor.clean_and_process():
|
| 2893 |
st.success("β
Data processed!")
|
|
|
|
| 2894 |
else:
|
| 2895 |
st.error("β Processing failed")
|
| 2896 |
|
|
@@ -2900,6 +2845,7 @@ def show_normal_workflow(processor, openai_processor, k_results, show_debug, for
|
|
| 2900 |
with st.spinner("Analyzing creative job patterns..."):
|
| 2901 |
if processor.analyze_dataset():
|
| 2902 |
st.success("β
Analysis complete!")
|
|
|
|
| 2903 |
else:
|
| 2904 |
st.error("β Analysis failed")
|
| 2905 |
else:
|
|
@@ -2914,12 +2860,7 @@ def show_normal_workflow(processor, openai_processor, k_results, show_debug, for
|
|
| 2914 |
if processor.build_rag_index(force_rebuild):
|
| 2915 |
st.session_state.rag_ready = True
|
| 2916 |
st.success("β
RAG system ready!")
|
| 2917 |
-
|
| 2918 |
-
# Show persistence confirmation
|
| 2919 |
-
stats = processor.vector_store.get_stats()
|
| 2920 |
-
if stats.get('persistent', False):
|
| 2921 |
-
st.success("πΎ Index saved to persistent storage!")
|
| 2922 |
-
|
| 2923 |
else:
|
| 2924 |
st.error("β RAG build failed")
|
| 2925 |
else:
|
|
@@ -2933,78 +2874,70 @@ def show_normal_workflow(processor, openai_processor, k_results, show_debug, for
|
|
| 2933 |
st.session_state.rag_ready = True
|
| 2934 |
st.balloons()
|
| 2935 |
st.success("π Complete workflow successful!")
|
| 2936 |
-
|
| 2937 |
-
# Show persistence status
|
| 2938 |
-
stats = processor.vector_store.get_stats()
|
| 2939 |
-
if stats.get('persistent', False):
|
| 2940 |
-
st.info(f"πΎ Data persisted using {stats.get('backend')} backend")
|
| 2941 |
else:
|
| 2942 |
st.error("β Workflow failed")
|
| 2943 |
|
| 2944 |
-
# Processing status
|
| 2945 |
-
|
|
|
|
| 2946 |
|
| 2947 |
-
#
|
| 2948 |
if processor.dataset_analysis is not None:
|
| 2949 |
-
|
| 2950 |
-
|
| 2951 |
-
# Show RAG interface if ready
|
| 2952 |
-
if st.session_state.rag_ready and processor.is_ready_for_queries():
|
| 2953 |
-
if openai_processor and openai_processor.is_available():
|
| 2954 |
-
st.markdown("---")
|
| 2955 |
-
show_query_interface(processor, openai_processor, k_results, show_debug)
|
| 2956 |
-
else:
|
| 2957 |
-
st.header("β οΈ RAG System Setup Required")
|
| 2958 |
-
st.error("OpenAI processor not available. Please check your API key configuration.")
|
| 2959 |
|
| 2960 |
# Export functionality
|
| 2961 |
if processor.processed_df is not None:
|
| 2962 |
-
|
|
|
|
| 2963 |
|
| 2964 |
-
def
|
| 2965 |
-
"""Show processing status indicators"""
|
|
|
|
|
|
|
| 2966 |
status_cols = st.columns(5)
|
|
|
|
| 2967 |
with status_cols[0]:
|
| 2968 |
if processor.processed_df is not None:
|
| 2969 |
-
st.
|
| 2970 |
else:
|
| 2971 |
-
st.warning("β³
|
| 2972 |
|
| 2973 |
with status_cols[1]:
|
| 2974 |
if processor.dataset_analysis is not None:
|
| 2975 |
-
st.
|
| 2976 |
else:
|
| 2977 |
-
st.warning("β³
|
| 2978 |
|
| 2979 |
with status_cols[2]:
|
| 2980 |
if st.session_state.rag_ready:
|
| 2981 |
-
st.
|
| 2982 |
else:
|
| 2983 |
-
st.warning("β³
|
| 2984 |
|
| 2985 |
with status_cols[3]:
|
| 2986 |
-
if openai_processor and openai_processor.is_available():
|
| 2987 |
-
st.
|
| 2988 |
else:
|
| 2989 |
st.warning("β³ API Key Needed")
|
| 2990 |
|
| 2991 |
with status_cols[4]:
|
| 2992 |
-
# Persistence status
|
| 2993 |
stats = processor.vector_store.get_stats()
|
| 2994 |
if stats.get('persistent', False):
|
| 2995 |
-
st.
|
| 2996 |
else:
|
| 2997 |
-
st.
|
| 2998 |
|
| 2999 |
-
def
|
| 3000 |
-
"""Show
|
| 3001 |
-
st.
|
| 3002 |
|
| 3003 |
analysis_summary = processor.get_analysis_summary()
|
| 3004 |
|
| 3005 |
if analysis_summary:
|
| 3006 |
# Key metrics
|
| 3007 |
-
st.subheader("
|
| 3008 |
metric_cols = st.columns(4)
|
| 3009 |
|
| 3010 |
with metric_cols[0]:
|
|
@@ -3017,7 +2950,7 @@ def show_analysis_results(processor):
|
|
| 3017 |
st.metric("Photo Professionals", f"{analysis_summary['role_counts']['photo_professionals']:,}")
|
| 3018 |
|
| 3019 |
# Software analysis
|
| 3020 |
-
st.subheader("
|
| 3021 |
software_cols = st.columns(4)
|
| 3022 |
|
| 3023 |
with software_cols[0]:
|
|
@@ -3033,7 +2966,7 @@ def show_analysis_results(processor):
|
|
| 3033 |
col1, col2 = st.columns(2)
|
| 3034 |
|
| 3035 |
with col1:
|
| 3036 |
-
st.subheader("
|
| 3037 |
if analysis_summary['top_adobe_apps']:
|
| 3038 |
adobe_df = pd.DataFrame(analysis_summary['top_adobe_apps'], columns=['Software', 'Mentions'])
|
| 3039 |
st.dataframe(adobe_df, use_container_width=True)
|
|
@@ -3041,7 +2974,7 @@ def show_analysis_results(processor):
|
|
| 3041 |
st.info("No Adobe applications found in dataset")
|
| 3042 |
|
| 3043 |
with col2:
|
| 3044 |
-
st.subheader("
|
| 3045 |
if analysis_summary['top_non_adobe_apps']:
|
| 3046 |
non_adobe_df = pd.DataFrame(analysis_summary['top_non_adobe_apps'], columns=['Software', 'Mentions'])
|
| 3047 |
st.dataframe(non_adobe_df, use_container_width=True)
|
|
@@ -3049,7 +2982,7 @@ def show_analysis_results(processor):
|
|
| 3049 |
st.info("No non-Adobe applications found in dataset")
|
| 3050 |
|
| 3051 |
# Cross-disciplinary analysis
|
| 3052 |
-
st.subheader("
|
| 3053 |
cross_cols = st.columns(3)
|
| 3054 |
|
| 3055 |
with cross_cols[0]:
|
|
@@ -3058,37 +2991,15 @@ def show_analysis_results(processor):
|
|
| 3058 |
st.metric("Non-Photo Jobs with Photo Tools", analysis_summary['cross_disciplinary']['non_photo_with_photo_tools'])
|
| 3059 |
with cross_cols[2]:
|
| 3060 |
st.metric("Non-Design Jobs with Design Tools", analysis_summary['cross_disciplinary']['non_design_with_design_tools'])
|
| 3061 |
-
|
| 3062 |
-
# Show persistence information
|
| 3063 |
-
if analysis_summary.get('persistence_info'):
|
| 3064 |
-
with st.expander("π§ Technical Details"):
|
| 3065 |
-
persistence_info = analysis_summary['persistence_info']
|
| 3066 |
-
|
| 3067 |
-
info_cols = st.columns(4)
|
| 3068 |
-
with info_cols[0]:
|
| 3069 |
-
st.metric("Backend", persistence_info.get('backend', 'unknown'))
|
| 3070 |
-
with info_cols[1]:
|
| 3071 |
-
if persistence_info.get('persistent', False):
|
| 3072 |
-
st.success("Persistent Storage")
|
| 3073 |
-
else:
|
| 3074 |
-
st.info("Memory Storage")
|
| 3075 |
-
with info_cols[2]:
|
| 3076 |
-
if persistence_info.get('auto_detected'):
|
| 3077 |
-
st.success("Auto-Detected")
|
| 3078 |
-
else:
|
| 3079 |
-
st.info("Manually Built")
|
| 3080 |
-
with info_cols[3]:
|
| 3081 |
-
if persistence_info.get('dataset_fingerprint'):
|
| 3082 |
-
st.caption(f"Dataset ID: {persistence_info['dataset_fingerprint'][:8]}...")
|
| 3083 |
|
| 3084 |
-
def
|
| 3085 |
-
"""Show export functionality"""
|
| 3086 |
-
st.
|
| 3087 |
|
| 3088 |
export_col1, export_col2, export_col3, export_col4 = st.columns(4)
|
| 3089 |
|
| 3090 |
with export_col1:
|
| 3091 |
-
if st.button("π Download Processed Data"):
|
| 3092 |
csv = processor.processed_df.to_csv(index=False)
|
| 3093 |
st.download_button(
|
| 3094 |
label="Download CSV",
|
|
@@ -3098,7 +3009,7 @@ def show_export_options(processor):
|
|
| 3098 |
)
|
| 3099 |
|
| 3100 |
with export_col2:
|
| 3101 |
-
if st.button("π Download Analysis Results"):
|
| 3102 |
if processor.dataset_analysis:
|
| 3103 |
json_data = json.dumps(processor.dataset_analysis, indent=2, default=str)
|
| 3104 |
st.download_button(
|
|
@@ -3109,7 +3020,7 @@ def show_export_options(processor):
|
|
| 3109 |
)
|
| 3110 |
|
| 3111 |
with export_col3:
|
| 3112 |
-
if st.button("π€ Download Q&A Answers"):
|
| 3113 |
if processor.qa_answers:
|
| 3114 |
json_data = json.dumps(processor.qa_answers, indent=2, default=str)
|
| 3115 |
st.download_button(
|
|
@@ -3120,7 +3031,7 @@ def show_export_options(processor):
|
|
| 3120 |
)
|
| 3121 |
|
| 3122 |
with export_col4:
|
| 3123 |
-
if st.button("π§ Download System Info"):
|
| 3124 |
system_info = {
|
| 3125 |
'persistence_status': processor.get_persistence_status(),
|
| 3126 |
'analysis_summary': processor.get_analysis_summary(),
|
|
@@ -3134,29 +3045,295 @@ def show_export_options(processor):
|
|
| 3134 |
mime="application/json"
|
| 3135 |
)
|
| 3136 |
|
| 3137 |
-
|
| 3138 |
-
|
| 3139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3140 |
|
| 3141 |
-
|
|
|
|
| 3142 |
|
| 3143 |
-
|
| 3144 |
-
|
| 3145 |
-
|
| 3146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3147 |
else:
|
| 3148 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3149 |
|
| 3150 |
-
|
| 3151 |
-
|
| 3152 |
-
|
| 3153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3154 |
else:
|
| 3155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3156 |
|
| 3157 |
-
with
|
| 3158 |
-
st.
|
| 3159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3160 |
|
| 3161 |
if __name__ == "__main__":
|
| 3162 |
main()
|
|
|
|
| 12 |
from collections import Counter
|
| 13 |
import nltk
|
| 14 |
import string
|
|
|
|
|
|
|
| 15 |
from datetime import datetime
|
| 16 |
import warnings
|
| 17 |
import logging
|
|
|
|
| 912 |
'is_video_professional': job_categories.get('is_video_professional', False),
|
| 913 |
'is_photo_professional': job_categories.get('is_photo_professional', False),
|
| 914 |
'is_creative_professional': job_categories.get('is_creative_professional', False),
|
| 915 |
+
'adobe_apps': ','.join(creative_analysis.get('adobe_apps', [])),
|
| 916 |
+
'non_adobe_apps': ','.join(creative_analysis.get('non_adobe_apps', [])),
|
| 917 |
+
'ai_tools': ','.join(creative_analysis.get('ai_tools', [])),
|
| 918 |
'has_adobe': len(creative_analysis.get('adobe_apps', [])) > 0,
|
| 919 |
'has_non_adobe': len(creative_analysis.get('non_adobe_apps', [])) > 0,
|
| 920 |
'has_ai_tools': len(creative_analysis.get('ai_tools', [])) > 0,
|
|
|
|
| 1030 |
|
| 1031 |
embeddings_list = embeddings.tolist()
|
| 1032 |
|
|
|
|
| 1033 |
# Create document IDs
|
| 1034 |
doc_ids = [f"doc_{i+j}" for j in range(len(batch_texts))]
|
| 1035 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1036 |
# Add to Chroma collection
|
| 1037 |
self.collection.add(
|
| 1038 |
embeddings=embeddings_list,
|
| 1039 |
documents=batch_texts,
|
| 1040 |
+
metadatas=batch_metadatas,
|
| 1041 |
ids=doc_ids
|
| 1042 |
)
|
| 1043 |
|
|
|
|
| 1088 |
distances = results.get('distances', [[]])[0]
|
| 1089 |
|
| 1090 |
for i, (doc, metadata, distance) in enumerate(zip(documents, metadatas, distances)):
|
| 1091 |
+
similarity_score = max(0.0, 1.0 - distance)
|
| 1092 |
|
| 1093 |
# Convert string lists back to actual lists for certain fields
|
| 1094 |
if isinstance(metadata.get('adobe_apps'), str):
|
|
|
|
| 1778 |
except Exception as e:
|
| 1779 |
logger.error(f"Error generating Q&A answers: {str(e)}")
|
| 1780 |
return answers
|
|
|
|
| 1781 |
class EnhancedOpenAIProcessor:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1782 |
def __init__(self, api_key: str):
|
| 1783 |
self._api_key = api_key
|
| 1784 |
self.client = None
|
|
|
|
| 1983 |
truncated = test_text
|
| 1984 |
|
| 1985 |
return truncated + "\n[Content truncated for length...]" if truncated else text[:max_tokens*4]
|
| 1986 |
+
|
| 1987 |
+
def query_with_enhanced_rag(self, question: str, vector_store, dataset_analysis: Dict = None) -> str:
|
| 1988 |
+
"""Process queries with enhanced RAG optimized for q.txt questions"""
|
| 1989 |
+
|
| 1990 |
+
if not self.is_available():
|
| 1991 |
+
return f"OpenAI client not available: {self._initialization_error}"
|
| 1992 |
+
|
| 1993 |
+
# Ensure dataset_analysis is a dict to avoid NoneType errors
|
| 1994 |
+
dataset_analysis = dataset_analysis or {}
|
| 1995 |
+
|
| 1996 |
+
try:
|
| 1997 |
+
# Retrieve relevant documents
|
| 1998 |
+
retrieved_docs = []
|
| 1999 |
+
try:
|
| 2000 |
+
retrieved_docs = vector_store.search(question, k=10)
|
| 2001 |
+
logger.info(f"Retrieved {len(retrieved_docs)} documents for query")
|
| 2002 |
+
except Exception as search_error:
|
| 2003 |
+
logger.warning(f"Vector search failed: {search_error}")
|
| 2004 |
+
|
| 2005 |
+
# Create enhanced system prompt
|
| 2006 |
+
system_prompt = self.create_enhanced_system_prompt(dataset_analysis)
|
| 2007 |
+
|
| 2008 |
+
# Prepare RAG context
|
| 2009 |
+
rag_context = self.prepare_rag_context(retrieved_docs, question, dataset_analysis)
|
| 2010 |
+
|
| 2011 |
+
# Create user message
|
| 2012 |
+
user_message = f"Question: {question}"
|
| 2013 |
+
if rag_context:
|
| 2014 |
+
user_message += f"\n\nRelevant Data:\n{rag_context}"
|
| 2015 |
+
|
| 2016 |
+
# Token management
|
| 2017 |
+
system_tokens = self.count_tokens(system_prompt)
|
| 2018 |
+
user_tokens = self.count_tokens(user_message)
|
| 2019 |
+
total_input_tokens = system_tokens + user_tokens
|
| 2020 |
+
|
| 2021 |
+
if total_input_tokens > self.max_input_tokens:
|
| 2022 |
+
# Reduce RAG context if needed
|
| 2023 |
+
excess_tokens = total_input_tokens - self.max_input_tokens
|
| 2024 |
+
if rag_context:
|
| 2025 |
+
current_context_tokens = self.count_tokens(rag_context)
|
| 2026 |
+
reduced_tokens = max(200, current_context_tokens - excess_tokens)
|
| 2027 |
+
rag_context = self._truncate_text(rag_context, reduced_tokens)
|
| 2028 |
+
user_message = f"Question: {question}\n\nRelevant Data:\n{rag_context}"
|
| 2029 |
+
|
| 2030 |
+
# Calculate available completion tokens
|
| 2031 |
+
final_input_tokens = self.count_tokens(system_prompt) + self.count_tokens(user_message)
|
| 2032 |
+
available_tokens = self.max_context_length - final_input_tokens - 100
|
| 2033 |
+
completion_tokens = min(self.max_completion_tokens, max(500, available_tokens))
|
| 2034 |
+
|
| 2035 |
+
# Make API call
|
| 2036 |
+
try:
|
| 2037 |
+
response = self.client.chat.completions.create(
|
| 2038 |
+
model="gpt-3.5-turbo",
|
| 2039 |
+
messages=[
|
| 2040 |
+
{"role": "system", "content": system_prompt},
|
| 2041 |
+
{"role": "user", "content": user_message}
|
| 2042 |
+
],
|
| 2043 |
+
max_tokens=completion_tokens,
|
| 2044 |
+
temperature=0.2, # Lower temperature for more consistent analysis
|
| 2045 |
+
timeout=30
|
| 2046 |
+
)
|
| 2047 |
+
|
| 2048 |
+
answer = response.choices[0].message.content
|
| 2049 |
+
logger.info(f"Successfully processed query: {question[:50]}...")
|
| 2050 |
+
return answer
|
| 2051 |
+
|
| 2052 |
+
except openai.APIError as api_error:
|
| 2053 |
+
error_msg = f"OpenAI API Error: {str(api_error)}"
|
| 2054 |
+
logger.error(error_msg)
|
| 2055 |
+
return f"API Error: {error_msg}"
|
| 2056 |
+
|
| 2057 |
+
except Exception as api_error:
|
| 2058 |
+
error_msg = f"API call failed: {str(api_error)}"
|
| 2059 |
+
logger.error(error_msg)
|
| 2060 |
+
return f"Error: {error_msg}"
|
| 2061 |
+
|
| 2062 |
+
except Exception as e:
|
| 2063 |
+
logger.error(f"Error in enhanced RAG query: {e}")
|
| 2064 |
+
return f"Processing error: {str(e)}"
|
| 2065 |
|
| 2066 |
class EnhancedCreativeJobProcessor:
|
| 2067 |
"""Main processor class optimized for creative job analysis with Chroma persistence and auto-detection"""
|
|
|
|
| 2439 |
|
| 2440 |
return False
|
| 2441 |
|
| 2442 |
+
def initialize_session_state():
|
| 2443 |
+
"""Initialize session state variables for multi-view application"""
|
| 2444 |
+
if 'current_view' not in st.session_state:
|
| 2445 |
+
st.session_state.current_view = 'master' # master, admin, client
|
| 2446 |
|
| 2447 |
+
if 'processor' not in st.session_state:
|
| 2448 |
+
st.session_state.processor = EnhancedCreativeJobProcessor()
|
| 2449 |
|
| 2450 |
+
if 'openai_processor' not in st.session_state:
|
| 2451 |
+
st.session_state.openai_processor = None
|
| 2452 |
|
| 2453 |
+
if 'rag_ready' not in st.session_state:
|
| 2454 |
+
st.session_state.rag_ready = False
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2455 |
|
| 2456 |
+
if 'auto_detected_mode' not in st.session_state:
|
| 2457 |
+
st.session_state.auto_detected_mode = False
|
| 2458 |
+
|
| 2459 |
+
if 'query_history' not in st.session_state:
|
| 2460 |
+
st.session_state.query_history = []
|
| 2461 |
+
|
| 2462 |
+
if 'last_query' not in st.session_state:
|
| 2463 |
+
st.session_state.last_query = ""
|
| 2464 |
+
|
| 2465 |
+
if 'last_response' not in st.session_state:
|
| 2466 |
+
st.session_state.last_response = ""
|
| 2467 |
+
|
| 2468 |
+
def show_master_view():
|
| 2469 |
+
"""Master view for selecting between Admin and Client modes"""
|
| 2470 |
+
st.title("π¨ Creative Job RAG Analyzer")
|
| 2471 |
+
st.markdown("### Choose Your Mode")
|
| 2472 |
+
|
| 2473 |
+
# Check for existing RAG index
|
| 2474 |
+
processor = st.session_state.processor
|
| 2475 |
+
stats = processor.vector_store.get_stats()
|
| 2476 |
|
| 2477 |
+
if stats.get('documents_count', 0) > 0:
|
| 2478 |
+
st.success(f"β
Existing RAG Index Found: {stats['documents_count']:,} documents")
|
| 2479 |
+
st.info(f"Backend: {stats['backend']} | Persistent: {'Yes' if stats.get('persistent') else 'No'}")
|
|
|
|
|
|
|
| 2480 |
else:
|
| 2481 |
+
st.warning("β οΈ No RAG index found. Please create one in Admin Mode first.")
|
| 2482 |
|
| 2483 |
+
st.markdown("---")
|
| 2484 |
+
|
| 2485 |
+
col1, col2 = st.columns(2)
|
| 2486 |
+
|
| 2487 |
+
with col1:
|
| 2488 |
+
st.markdown("### π§ Admin Mode")
|
| 2489 |
+
st.markdown("""
|
| 2490 |
+
**For Administrators:**
|
| 2491 |
+
- Upload creative job datasets (CSV)
|
| 2492 |
+
- Build and manage RAG indexes
|
| 2493 |
+
- View dataset statistics
|
| 2494 |
+
- Configure vector storage
|
| 2495 |
+
""")
|
| 2496 |
|
| 2497 |
+
if st.button("Enter Admin Mode", type="primary", use_container_width=True):
|
| 2498 |
+
st.session_state.current_view = 'admin'
|
| 2499 |
+
st.rerun()
|
| 2500 |
+
|
| 2501 |
+
with col2:
|
| 2502 |
+
st.markdown("### π¬ Client Mode")
|
| 2503 |
+
st.markdown("""
|
| 2504 |
+
**For Users:**
|
| 2505 |
+
- Query the creative jobs database
|
| 2506 |
+
- Get AI-powered insights
|
| 2507 |
+
- Analyze industry trends
|
| 2508 |
+
- Follow-up questions supported
|
| 2509 |
+
""")
|
| 2510 |
|
| 2511 |
+
if stats.get('documents_count', 0) > 0:
|
| 2512 |
+
if st.button("Enter Client Mode", type="primary", use_container_width=True):
|
| 2513 |
+
st.session_state.current_view = 'client'
|
| 2514 |
+
st.rerun()
|
|
|
|
| 2515 |
else:
|
| 2516 |
+
st.button("Enter Client Mode", type="primary", use_container_width=True, disabled=True)
|
| 2517 |
+
st.caption("β οΈ Create RAG index in Admin Mode first")
|
| 2518 |
+
|
| 2519 |
+
# System information
|
| 2520 |
+
st.markdown("---")
|
| 2521 |
+
st.markdown("### π System Information")
|
| 2522 |
+
|
| 2523 |
+
info_col1, info_col2, info_col3, info_col4 = st.columns(4)
|
| 2524 |
+
|
| 2525 |
+
with info_col1:
|
| 2526 |
if CHROMA_AVAILABLE:
|
| 2527 |
+
st.success("β
ChromaDB")
|
| 2528 |
else:
|
| 2529 |
+
st.error("β ChromaDB")
|
| 2530 |
+
|
| 2531 |
+
with info_col2:
|
| 2532 |
+
if ENABLE_PERSISTENCE:
|
| 2533 |
+
st.success("β
Persistence")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2534 |
else:
|
| 2535 |
+
st.info("πΎ Memory Only")
|
| 2536 |
+
|
| 2537 |
+
with info_col3:
|
| 2538 |
+
api_key = get_openai_api_key()
|
| 2539 |
+
if api_key:
|
| 2540 |
+
st.success("β
OpenAI API")
|
| 2541 |
else:
|
| 2542 |
+
st.error("β No API Key")
|
| 2543 |
+
|
| 2544 |
+
with info_col4:
|
| 2545 |
+
if SENTENCE_TRANSFORMERS_AVAILABLE:
|
| 2546 |
+
st.success("β
Embeddings")
|
| 2547 |
else:
|
| 2548 |
+
st.error("β Embeddings")
|
| 2549 |
+
|
| 2550 |
+
def initialize_session_state():
|
| 2551 |
+
"""Initialize session state variables for multi-view application"""
|
| 2552 |
+
if 'current_view' not in st.session_state:
|
| 2553 |
+
st.session_state.current_view = 'master' # master, admin, client
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2554 |
|
|
|
|
| 2555 |
if 'processor' not in st.session_state:
|
| 2556 |
st.session_state.processor = EnhancedCreativeJobProcessor()
|
| 2557 |
|
|
|
|
| 2564 |
if 'auto_detected_mode' not in st.session_state:
|
| 2565 |
st.session_state.auto_detected_mode = False
|
| 2566 |
|
| 2567 |
+
if 'query_history' not in st.session_state:
|
| 2568 |
+
st.session_state.query_history = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2569 |
|
| 2570 |
+
if 'last_query' not in st.session_state:
|
| 2571 |
+
st.session_state.last_query = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2572 |
|
| 2573 |
+
if 'last_response' not in st.session_state:
|
| 2574 |
+
st.session_state.last_response = ""
|
| 2575 |
+
|
| 2576 |
+
def show_master_view():
|
| 2577 |
+
"""Master view for selecting between Admin and Client modes"""
|
| 2578 |
+
st.title("π¨ Creative Job RAG Analyzer")
|
| 2579 |
+
st.markdown("### Choose Your Mode")
|
| 2580 |
|
| 2581 |
+
# Check for existing RAG index
|
| 2582 |
+
processor = st.session_state.processor
|
| 2583 |
stats = processor.vector_store.get_stats()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2584 |
|
| 2585 |
+
if stats.get('documents_count', 0) > 0:
|
| 2586 |
+
st.success(f"β
Existing RAG Index Found: {stats['documents_count']:,} documents")
|
| 2587 |
+
st.info(f"Backend: {stats['backend']} | Persistent: {'Yes' if stats.get('persistent') else 'No'}")
|
| 2588 |
+
else:
|
| 2589 |
+
st.warning("β οΈ No RAG index found. Please create one in Admin Mode first.")
|
| 2590 |
+
|
| 2591 |
+
st.markdown("---")
|
| 2592 |
|
| 2593 |
+
col1, col2 = st.columns(2)
|
| 2594 |
+
|
| 2595 |
+
with col1:
|
| 2596 |
+
st.markdown("### π§ Admin Mode")
|
| 2597 |
st.markdown("""
|
| 2598 |
+
**For Administrators:**
|
| 2599 |
+
- Upload creative job datasets (CSV)
|
| 2600 |
+
- Build and manage RAG indexes
|
| 2601 |
+
- View dataset statistics
|
| 2602 |
+
- Configure vector storage
|
| 2603 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2604 |
|
| 2605 |
+
if st.button("Enter Admin Mode", type="primary", use_container_width=True):
|
| 2606 |
+
st.session_state.current_view = 'admin'
|
| 2607 |
+
st.rerun()
|
| 2608 |
+
|
| 2609 |
+
with col2:
|
| 2610 |
+
st.markdown("### π¬ Client Mode")
|
| 2611 |
+
st.markdown("""
|
| 2612 |
+
**For Users:**
|
| 2613 |
+
- Query the creative jobs database
|
| 2614 |
+
- Get AI-powered insights
|
| 2615 |
+
- Analyze industry trends
|
| 2616 |
+
- Follow-up questions supported
|
| 2617 |
""")
|
| 2618 |
+
|
| 2619 |
+
if stats.get('documents_count', 0) > 0:
|
| 2620 |
+
if st.button("Enter Client Mode", type="primary", use_container_width=True):
|
| 2621 |
+
st.session_state.current_view = 'client'
|
| 2622 |
+
st.rerun()
|
| 2623 |
+
else:
|
| 2624 |
+
st.button("Enter Client Mode", type="primary", use_container_width=True, disabled=True)
|
| 2625 |
+
st.caption("β οΈ Create RAG index in Admin Mode first")
|
| 2626 |
|
| 2627 |
+
# System information
|
| 2628 |
+
st.markdown("---")
|
| 2629 |
+
st.markdown("### π System Information")
|
|
|
|
|
|
|
|
|
|
| 2630 |
|
| 2631 |
+
info_col1, info_col2, info_col3, info_col4 = st.columns(4)
|
|
|
|
| 2632 |
|
| 2633 |
+
with info_col1:
|
| 2634 |
+
if CHROMA_AVAILABLE:
|
| 2635 |
+
st.success("β
ChromaDB")
|
| 2636 |
+
else:
|
| 2637 |
+
st.error("β ChromaDB")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2638 |
|
| 2639 |
+
with info_col2:
|
| 2640 |
+
if ENABLE_PERSISTENCE:
|
| 2641 |
+
st.success("β
Persistence")
|
| 2642 |
+
else:
|
| 2643 |
+
st.info("πΎ Memory Only")
|
| 2644 |
|
| 2645 |
+
with info_col3:
|
| 2646 |
+
api_key = get_openai_api_key()
|
| 2647 |
+
if api_key:
|
| 2648 |
+
st.success("β
OpenAI API")
|
| 2649 |
+
else:
|
| 2650 |
+
st.error("β No API Key")
|
|
|
|
|
|
|
| 2651 |
|
| 2652 |
+
with info_col4:
|
| 2653 |
+
if SENTENCE_TRANSFORMERS_AVAILABLE:
|
| 2654 |
+
st.success("β
Embeddings")
|
| 2655 |
+
else:
|
| 2656 |
+
st.error("β Embeddings")
|
| 2657 |
+
|
| 2658 |
+
def show_admin_view():
|
| 2659 |
+
"""Admin view for RAG index creation and management"""
|
| 2660 |
|
| 2661 |
+
# Header with back button
|
| 2662 |
+
col1, col2 = st.columns([6, 1])
|
| 2663 |
+
with col1:
|
| 2664 |
+
st.title("π§ Admin Mode - RAG Index Management")
|
| 2665 |
+
with col2:
|
| 2666 |
+
if st.button("β Back", type="secondary"):
|
| 2667 |
+
st.session_state.current_view = 'master'
|
| 2668 |
+
st.rerun()
|
| 2669 |
|
| 2670 |
+
processor = st.session_state.processor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2671 |
|
| 2672 |
+
# Display RAG Index Status
|
| 2673 |
+
st.markdown("### π Current RAG Index Status")
|
| 2674 |
+
|
| 2675 |
+
stats = processor.vector_store.get_stats()
|
| 2676 |
+
|
| 2677 |
+
if stats.get('documents_count', 0) > 0:
|
| 2678 |
+
# Display existing index information
|
| 2679 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 2680 |
+
with col1:
|
| 2681 |
+
st.metric("Documents", f"{stats['documents_count']:,}")
|
| 2682 |
+
with col2:
|
| 2683 |
+
st.metric("Backend", stats['backend'].title())
|
| 2684 |
+
with col3:
|
| 2685 |
+
if stats.get('persistent'):
|
| 2686 |
+
st.metric("Storage", "Persistent", delta="β")
|
| 2687 |
+
else:
|
| 2688 |
+
st.metric("Storage", "Memory", delta="β ")
|
| 2689 |
+
with col4:
|
| 2690 |
+
st.metric("Status", "Active", delta="β")
|
| 2691 |
|
| 2692 |
+
# Auto-detected information
|
| 2693 |
+
if stats.get('auto_detected'):
|
| 2694 |
+
processor.vector_store.display_existing_index_info()
|
| 2695 |
+
|
| 2696 |
+
# Management options
|
| 2697 |
+
st.markdown("---")
|
| 2698 |
+
st.markdown("### βοΈ Index Management")
|
| 2699 |
+
|
| 2700 |
+
mgmt_col1, mgmt_col2, mgmt_col3 = st.columns(3)
|
| 2701 |
+
|
| 2702 |
+
with mgmt_col1:
|
| 2703 |
+
if st.button("π Rebuild Index", type="secondary", use_container_width=True):
|
| 2704 |
+
if processor.processed_df is not None and processor.dataset_analysis is not None:
|
| 2705 |
+
with st.spinner("Rebuilding RAG index..."):
|
| 2706 |
+
if processor.build_rag_index(force_rebuild=True):
|
| 2707 |
+
st.success("β
Index rebuilt successfully!")
|
| 2708 |
+
st.rerun()
|
| 2709 |
+
else:
|
| 2710 |
+
st.error("No processed dataset available. Please upload and process data first.")
|
| 2711 |
+
|
| 2712 |
+
with mgmt_col2:
|
| 2713 |
+
if st.button("ποΈ Clear Index", type="secondary", use_container_width=True):
|
| 2714 |
+
if st.button("β οΈ Confirm Clear", type="secondary"):
|
| 2715 |
if processor.vector_store.use_chroma and processor.vector_store.chroma_store:
|
| 2716 |
if processor.vector_store.chroma_store.clear_collection():
|
| 2717 |
+
st.success("β
Index cleared")
|
| 2718 |
st.session_state.rag_ready = False
|
| 2719 |
+
st.rerun()
|
| 2720 |
+
|
| 2721 |
+
with mgmt_col3:
|
| 2722 |
+
if st.button("π View Details", type="secondary", use_container_width=True):
|
| 2723 |
+
with st.expander("Detailed Index Information", expanded=True):
|
| 2724 |
+
st.json(stats)
|
| 2725 |
+
|
| 2726 |
+
else:
|
| 2727 |
+
st.error("β No RAG index found")
|
| 2728 |
+
st.info("π Upload a CSV file below to create a new RAG index")
|
| 2729 |
+
|
| 2730 |
+
st.markdown("---")
|
| 2731 |
+
|
| 2732 |
+
# Sidebar for configuration
|
| 2733 |
+
with st.sidebar:
|
| 2734 |
+
st.header("π§ Configuration")
|
| 2735 |
+
|
| 2736 |
+
# API Key status
|
| 2737 |
+
st.subheader("π API Configuration")
|
| 2738 |
+
if st.session_state.openai_processor and st.session_state.openai_processor.is_available():
|
| 2739 |
+
st.success("β
OpenAI API Ready")
|
| 2740 |
+
else:
|
| 2741 |
+
st.error("β OpenAI API Not Available")
|
| 2742 |
+
st.caption("Check your API key configuration")
|
| 2743 |
+
|
| 2744 |
+
st.markdown("---")
|
| 2745 |
+
|
| 2746 |
+
# Persistence settings
|
| 2747 |
+
st.subheader("πΎ Storage Settings")
|
| 2748 |
+
if ENABLE_PERSISTENCE:
|
| 2749 |
+
st.success("β
Persistence Enabled")
|
| 2750 |
+
st.caption(f"Path: {CHROMA_PERSIST_PATH}")
|
| 2751 |
+
else:
|
| 2752 |
+
st.info("πΎ Memory Mode Active")
|
| 2753 |
+
|
| 2754 |
+
if CHROMA_AVAILABLE:
|
| 2755 |
+
st.success("β
ChromaDB Available")
|
| 2756 |
+
else:
|
| 2757 |
+
st.error("β ChromaDB Missing")
|
| 2758 |
+
|
| 2759 |
+
st.markdown("---")
|
| 2760 |
+
|
| 2761 |
+
# Processing options
|
| 2762 |
+
st.subheader("βοΈ Processing Options")
|
| 2763 |
+
force_rebuild = st.checkbox("Force Index Rebuild", help="Rebuild index even if data hasn't changed")
|
| 2764 |
+
|
| 2765 |
+
st.markdown("---")
|
| 2766 |
+
|
| 2767 |
+
# Dependency status
|
| 2768 |
+
st.subheader("π¦ Dependencies")
|
| 2769 |
+
dep_status = []
|
| 2770 |
+
if SENTENCE_TRANSFORMERS_AVAILABLE:
|
| 2771 |
+
dep_status.append("β
SentenceTransformers")
|
| 2772 |
+
else:
|
| 2773 |
+
dep_status.append("β SentenceTransformers")
|
| 2774 |
+
|
| 2775 |
+
if NLTK_AVAILABLE:
|
| 2776 |
+
dep_status.append("β
NLTK")
|
| 2777 |
+
else:
|
| 2778 |
+
dep_status.append("β οΈ NLTK Limited")
|
| 2779 |
+
|
| 2780 |
+
if TIKTOKEN_AVAILABLE:
|
| 2781 |
+
dep_status.append("β
Tiktoken")
|
| 2782 |
+
else:
|
| 2783 |
+
dep_status.append("β οΈ Tiktoken Fallback")
|
| 2784 |
+
|
| 2785 |
+
for status in dep_status:
|
| 2786 |
+
if "β
" in status:
|
| 2787 |
+
st.success(status)
|
| 2788 |
+
elif "β οΈ" in status:
|
| 2789 |
+
st.warning(status)
|
| 2790 |
+
else:
|
| 2791 |
+
st.error(status)
|
| 2792 |
|
| 2793 |
# File upload section
|
| 2794 |
+
st.markdown("### π Upload Creative Job Dataset")
|
| 2795 |
+
|
| 2796 |
uploaded_file = st.file_uploader(
|
| 2797 |
"Choose a CSV file containing creative job data",
|
| 2798 |
type="csv",
|
|
|
|
| 2800 |
)
|
| 2801 |
|
| 2802 |
if uploaded_file is not None:
|
| 2803 |
+
# Warning if overwriting
|
| 2804 |
if processor.vector_store.collection_exists():
|
| 2805 |
+
st.warning("β οΈ **Data Overwrite Warning**: Uploading a new CSV will replace the existing RAG index.")
|
| 2806 |
+
if not st.checkbox("I understand existing data will be overwritten", key="overwrite_confirm"):
|
|
|
|
| 2807 |
st.stop()
|
| 2808 |
|
| 2809 |
# Load data
|
|
|
|
| 2825 |
st.dataframe(processor.df.head(), use_container_width=True)
|
| 2826 |
|
| 2827 |
# Processing workflow
|
| 2828 |
+
st.markdown("---")
|
| 2829 |
+
st.markdown("### βοΈ Processing Workflow")
|
| 2830 |
|
| 2831 |
col1, col2, col3 = st.columns(3)
|
| 2832 |
|
|
|
|
| 2835 |
with st.spinner("Cleaning and processing data..."):
|
| 2836 |
if processor.clean_and_process():
|
| 2837 |
st.success("β
Data processed!")
|
| 2838 |
+
st.rerun()
|
| 2839 |
else:
|
| 2840 |
st.error("β Processing failed")
|
| 2841 |
|
|
|
|
| 2845 |
with st.spinner("Analyzing creative job patterns..."):
|
| 2846 |
if processor.analyze_dataset():
|
| 2847 |
st.success("β
Analysis complete!")
|
| 2848 |
+
st.rerun()
|
| 2849 |
else:
|
| 2850 |
st.error("β Analysis failed")
|
| 2851 |
else:
|
|
|
|
| 2860 |
if processor.build_rag_index(force_rebuild):
|
| 2861 |
st.session_state.rag_ready = True
|
| 2862 |
st.success("β
RAG system ready!")
|
| 2863 |
+
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2864 |
else:
|
| 2865 |
st.error("β RAG build failed")
|
| 2866 |
else:
|
|
|
|
| 2874 |
st.session_state.rag_ready = True
|
| 2875 |
st.balloons()
|
| 2876 |
st.success("π Complete workflow successful!")
|
| 2877 |
+
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2878 |
else:
|
| 2879 |
st.error("β Workflow failed")
|
| 2880 |
|
| 2881 |
+
# Processing status
|
| 2882 |
+
st.markdown("---")
|
| 2883 |
+
show_processing_status_admin(processor)
|
| 2884 |
|
| 2885 |
+
# Analysis results
|
| 2886 |
if processor.dataset_analysis is not None:
|
| 2887 |
+
st.markdown("---")
|
| 2888 |
+
show_analysis_results_admin(processor)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2889 |
|
| 2890 |
# Export functionality
|
| 2891 |
if processor.processed_df is not None:
|
| 2892 |
+
st.markdown("---")
|
| 2893 |
+
show_export_options_admin(processor)
|
| 2894 |
|
| 2895 |
+
def show_processing_status_admin(processor):
|
| 2896 |
+
"""Show processing status indicators in admin view"""
|
| 2897 |
+
st.markdown("### π Processing Status")
|
| 2898 |
+
|
| 2899 |
status_cols = st.columns(5)
|
| 2900 |
+
|
| 2901 |
with status_cols[0]:
|
| 2902 |
if processor.processed_df is not None:
|
| 2903 |
+
st.success("β
Data Processed")
|
| 2904 |
else:
|
| 2905 |
+
st.warning("β³ Not Processed")
|
| 2906 |
|
| 2907 |
with status_cols[1]:
|
| 2908 |
if processor.dataset_analysis is not None:
|
| 2909 |
+
st.success("β
Analysis Complete")
|
| 2910 |
else:
|
| 2911 |
+
st.warning("β³ Pending")
|
| 2912 |
|
| 2913 |
with status_cols[2]:
|
| 2914 |
if st.session_state.rag_ready:
|
| 2915 |
+
st.success("β
RAG Ready")
|
| 2916 |
else:
|
| 2917 |
+
st.warning("β³ Not Built")
|
| 2918 |
|
| 2919 |
with status_cols[3]:
|
| 2920 |
+
if st.session_state.openai_processor and st.session_state.openai_processor.is_available():
|
| 2921 |
+
st.success("β
OpenAI Ready")
|
| 2922 |
else:
|
| 2923 |
st.warning("β³ API Key Needed")
|
| 2924 |
|
| 2925 |
with status_cols[4]:
|
|
|
|
| 2926 |
stats = processor.vector_store.get_stats()
|
| 2927 |
if stats.get('persistent', False):
|
| 2928 |
+
st.success("β
Persistent")
|
| 2929 |
else:
|
| 2930 |
+
st.info("πΎ Memory")
|
| 2931 |
|
| 2932 |
+
def show_analysis_results_admin(processor):
|
| 2933 |
+
"""Show analysis results in admin view"""
|
| 2934 |
+
st.markdown("### π Creative Job Analysis Results")
|
| 2935 |
|
| 2936 |
analysis_summary = processor.get_analysis_summary()
|
| 2937 |
|
| 2938 |
if analysis_summary:
|
| 2939 |
# Key metrics
|
| 2940 |
+
st.subheader("Key Metrics")
|
| 2941 |
metric_cols = st.columns(4)
|
| 2942 |
|
| 2943 |
with metric_cols[0]:
|
|
|
|
| 2950 |
st.metric("Photo Professionals", f"{analysis_summary['role_counts']['photo_professionals']:,}")
|
| 2951 |
|
| 2952 |
# Software analysis
|
| 2953 |
+
st.subheader("Software Requirements Analysis")
|
| 2954 |
software_cols = st.columns(4)
|
| 2955 |
|
| 2956 |
with software_cols[0]:
|
|
|
|
| 2966 |
col1, col2 = st.columns(2)
|
| 2967 |
|
| 2968 |
with col1:
|
| 2969 |
+
st.subheader("Top Adobe Applications")
|
| 2970 |
if analysis_summary['top_adobe_apps']:
|
| 2971 |
adobe_df = pd.DataFrame(analysis_summary['top_adobe_apps'], columns=['Software', 'Mentions'])
|
| 2972 |
st.dataframe(adobe_df, use_container_width=True)
|
|
|
|
| 2974 |
st.info("No Adobe applications found in dataset")
|
| 2975 |
|
| 2976 |
with col2:
|
| 2977 |
+
st.subheader("Top Non-Adobe Applications")
|
| 2978 |
if analysis_summary['top_non_adobe_apps']:
|
| 2979 |
non_adobe_df = pd.DataFrame(analysis_summary['top_non_adobe_apps'], columns=['Software', 'Mentions'])
|
| 2980 |
st.dataframe(non_adobe_df, use_container_width=True)
|
|
|
|
| 2982 |
st.info("No non-Adobe applications found in dataset")
|
| 2983 |
|
| 2984 |
# Cross-disciplinary analysis
|
| 2985 |
+
st.subheader("Cross-Disciplinary Requirements")
|
| 2986 |
cross_cols = st.columns(3)
|
| 2987 |
|
| 2988 |
with cross_cols[0]:
|
|
|
|
| 2991 |
st.metric("Non-Photo Jobs with Photo Tools", analysis_summary['cross_disciplinary']['non_photo_with_photo_tools'])
|
| 2992 |
with cross_cols[2]:
|
| 2993 |
st.metric("Non-Design Jobs with Design Tools", analysis_summary['cross_disciplinary']['non_design_with_design_tools'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2994 |
|
| 2995 |
+
def show_export_options_admin(processor):
|
| 2996 |
+
"""Show export functionality in admin view"""
|
| 2997 |
+
st.markdown("### π€ Export Results")
|
| 2998 |
|
| 2999 |
export_col1, export_col2, export_col3, export_col4 = st.columns(4)
|
| 3000 |
|
| 3001 |
with export_col1:
|
| 3002 |
+
if st.button("π Download Processed Data", use_container_width=True):
|
| 3003 |
csv = processor.processed_df.to_csv(index=False)
|
| 3004 |
st.download_button(
|
| 3005 |
label="Download CSV",
|
|
|
|
| 3009 |
)
|
| 3010 |
|
| 3011 |
with export_col2:
|
| 3012 |
+
if st.button("π Download Analysis Results", use_container_width=True):
|
| 3013 |
if processor.dataset_analysis:
|
| 3014 |
json_data = json.dumps(processor.dataset_analysis, indent=2, default=str)
|
| 3015 |
st.download_button(
|
|
|
|
| 3020 |
)
|
| 3021 |
|
| 3022 |
with export_col3:
|
| 3023 |
+
if st.button("π€ Download Q&A Answers", use_container_width=True):
|
| 3024 |
if processor.qa_answers:
|
| 3025 |
json_data = json.dumps(processor.qa_answers, indent=2, default=str)
|
| 3026 |
st.download_button(
|
|
|
|
| 3031 |
)
|
| 3032 |
|
| 3033 |
with export_col4:
|
| 3034 |
+
if st.button("π§ Download System Info", use_container_width=True):
|
| 3035 |
system_info = {
|
| 3036 |
'persistence_status': processor.get_persistence_status(),
|
| 3037 |
'analysis_summary': processor.get_analysis_summary(),
|
|
|
|
| 3045 |
mime="application/json"
|
| 3046 |
)
|
| 3047 |
|
| 3048 |
+
def show_client_view():
|
| 3049 |
+
"""Client view for querying the RAG system"""
|
| 3050 |
+
|
| 3051 |
+
# Header with back button
|
| 3052 |
+
col1, col2 = st.columns([6, 1])
|
| 3053 |
+
with col1:
|
| 3054 |
+
st.title("π¬ Client Mode - Query Interface")
|
| 3055 |
+
with col2:
|
| 3056 |
+
if st.button("β Back", type="secondary"):
|
| 3057 |
+
st.session_state.current_view = 'master'
|
| 3058 |
+
st.rerun()
|
| 3059 |
|
| 3060 |
+
processor = st.session_state.processor
|
| 3061 |
+
openai_processor = st.session_state.openai_processor
|
| 3062 |
|
| 3063 |
+
# Check RAG Index Status
|
| 3064 |
+
st.markdown("### π RAG Index Status")
|
| 3065 |
+
|
| 3066 |
+
stats = processor.vector_store.get_stats()
|
| 3067 |
+
|
| 3068 |
+
if stats.get('documents_count', 0) > 0:
|
| 3069 |
+
# Display index information
|
| 3070 |
+
col1, col2, col3, col4 = st.columns(4)
|
| 3071 |
+
with col1:
|
| 3072 |
+
st.metric("Documents", f"{stats['documents_count']:,}")
|
| 3073 |
+
with col2:
|
| 3074 |
+
st.metric("Backend", stats['backend'].title())
|
| 3075 |
+
with col3:
|
| 3076 |
+
if stats.get('persistent'):
|
| 3077 |
+
st.success("πΎ Persistent Storage")
|
| 3078 |
+
else:
|
| 3079 |
+
st.info("πΎ Memory Storage")
|
| 3080 |
+
with col4:
|
| 3081 |
+
st.success("β
Index Active")
|
| 3082 |
+
|
| 3083 |
+
if stats.get('auto_detected'):
|
| 3084 |
+
st.info("π Using auto-detected existing index")
|
| 3085 |
+
else:
|
| 3086 |
+
st.error("β No RAG index found")
|
| 3087 |
+
st.warning("β οΈ Please create a RAG index in Admin Mode before querying")
|
| 3088 |
+
st.info("π Return to Master View and enter Admin Mode to create an index")
|
| 3089 |
+
st.stop()
|
| 3090 |
+
|
| 3091 |
+
st.markdown("---")
|
| 3092 |
+
|
| 3093 |
+
# Sidebar configuration
|
| 3094 |
+
with st.sidebar:
|
| 3095 |
+
st.header("βοΈ Query Settings")
|
| 3096 |
+
|
| 3097 |
+
k_results = st.slider("Retrieved Documents", 5, 15, 10, help="Number of relevant documents to retrieve")
|
| 3098 |
+
show_retrieved = st.checkbox("Show Retrieved Context", value=True, help="Display the documents used to answer your question")
|
| 3099 |
+
show_debug = st.checkbox("Show Debug Information", value=False, help="Display technical debugging information")
|
| 3100 |
+
|
| 3101 |
+
st.markdown("---")
|
| 3102 |
+
|
| 3103 |
+
st.header("π API Status")
|
| 3104 |
+
if openai_processor and openai_processor.is_available():
|
| 3105 |
+
st.success("β
OpenAI API Ready")
|
| 3106 |
else:
|
| 3107 |
+
st.error("β OpenAI API Not Available")
|
| 3108 |
+
st.caption("Check API key configuration")
|
| 3109 |
+
|
| 3110 |
+
st.markdown("---")
|
| 3111 |
+
|
| 3112 |
+
st.header("π Query History")
|
| 3113 |
+
if st.session_state.query_history:
|
| 3114 |
+
st.caption(f"Recent queries: {len(st.session_state.query_history)}")
|
| 3115 |
+
if st.button("Clear History", type="secondary", use_container_width=True):
|
| 3116 |
+
st.session_state.query_history = []
|
| 3117 |
+
st.session_state.last_query = ""
|
| 3118 |
+
st.session_state.last_response = ""
|
| 3119 |
+
st.rerun()
|
| 3120 |
+
|
| 3121 |
+
with st.expander("View History"):
|
| 3122 |
+
for i, query in enumerate(reversed(st.session_state.query_history[-5:]), 1):
|
| 3123 |
+
st.caption(f"{i}. {query[:50]}...")
|
| 3124 |
+
else:
|
| 3125 |
+
st.caption("No queries yet")
|
| 3126 |
|
| 3127 |
+
# Main query interface
|
| 3128 |
+
st.markdown("### π€ Intelligent Creative Job Analysis")
|
| 3129 |
+
st.markdown("Ask sophisticated questions about creative professionals, software requirements, and industry trends!")
|
| 3130 |
+
|
| 3131 |
+
# Example questions
|
| 3132 |
+
with st.expander("π‘ Example Questions from Your Analysis Requirements"):
|
| 3133 |
+
st.markdown("""
|
| 3134 |
+
**Adobe vs Non-Adobe Analysis:**
|
| 3135 |
+
- How many postings ask for non-Adobe apps but not Adobe apps? What are those apps?
|
| 3136 |
+
- How many postings ask for both Adobe and non-Adobe apps? What are those combinations?
|
| 3137 |
+
- How many job listings request experience with Photoshop? And how many request Photoshop's competitors?
|
| 3138 |
+
|
| 3139 |
+
**Creative Role Analysis:**
|
| 3140 |
+
- How many records describe a designer role?
|
| 3141 |
+
- Find all designer roles and summarize their creative job requirements
|
| 3142 |
+
- What are the top job titles among designer roles?
|
| 3143 |
+
|
| 3144 |
+
**Cross-Disciplinary Requirements:**
|
| 3145 |
+
- Which jobs are not video jobs but still require video editing tools? What video tools are they?
|
| 3146 |
+
- Which jobs are not photo jobs but still require photo editing tools? What photo tools are they?
|
| 3147 |
+
- Which jobs are not design jobs but still require design editing tools? What design tools are they?
|
| 3148 |
+
|
| 3149 |
+
**AI Tools and Modern Workflows:**
|
| 3150 |
+
- How many posts ask for AI skills? What are those AI tools? What are those occupations?
|
| 3151 |
+
- What industries are hiring more creative professionals? What kind of creative professionals?
|
| 3152 |
+
- What soft skills are mentioned in the postings for creative professionals?
|
| 3153 |
+
""")
|
| 3154 |
+
|
| 3155 |
+
# Query input with follow-up support
|
| 3156 |
+
if st.session_state.last_response:
|
| 3157 |
+
st.info("π¬ **Previous Response Available** - You can ask a follow-up question or start a new query")
|
| 3158 |
+
|
| 3159 |
+
col1, col2 = st.columns([1, 1])
|
| 3160 |
+
with col1:
|
| 3161 |
+
if st.button("π Ask Follow-up Question", type="secondary", use_container_width=True):
|
| 3162 |
+
st.session_state.query_mode = 'followup'
|
| 3163 |
+
with col2:
|
| 3164 |
+
if st.button("β¨ Start New Query", type="secondary", use_container_width=True):
|
| 3165 |
+
st.session_state.query_mode = 'new'
|
| 3166 |
+
st.session_state.last_query = ""
|
| 3167 |
+
st.session_state.last_response = ""
|
| 3168 |
+
|
| 3169 |
+
# Determine query mode
|
| 3170 |
+
query_mode = st.session_state.get('query_mode', 'new')
|
| 3171 |
+
|
| 3172 |
+
if query_mode == 'followup' and st.session_state.last_response:
|
| 3173 |
+
st.markdown("#### Follow-up Question")
|
| 3174 |
+
st.caption(f"Previous query: {st.session_state.last_query[:100]}...")
|
| 3175 |
+
question = st.text_area(
|
| 3176 |
+
"Ask a follow-up question about the previous response:",
|
| 3177 |
+
placeholder="e.g., Can you provide more details about those designer roles?",
|
| 3178 |
+
height=100,
|
| 3179 |
+
key="followup_query"
|
| 3180 |
+
)
|
| 3181 |
+
else:
|
| 3182 |
+
st.markdown("#### New Query")
|
| 3183 |
+
question = st.text_area(
|
| 3184 |
+
"Ask about creative jobs, software requirements, or industry trends:",
|
| 3185 |
+
placeholder="e.g., How many designer roles require Adobe software vs non-Adobe alternatives?",
|
| 3186 |
+
height=100,
|
| 3187 |
+
key="new_query"
|
| 3188 |
+
)
|
| 3189 |
+
|
| 3190 |
+
# Query button
|
| 3191 |
+
if st.button("π Analyze with Enhanced RAG", type="primary", use_container_width=True) and question:
|
| 3192 |
+
if not openai_processor or not openai_processor.is_available():
|
| 3193 |
+
st.error("OpenAI processor not available. Please check your API key configuration.")
|
| 3194 |
+
st.stop()
|
| 3195 |
+
|
| 3196 |
+
# Add to history
|
| 3197 |
+
st.session_state.query_history.append(question)
|
| 3198 |
+
|
| 3199 |
+
# Build context-aware query for follow-ups
|
| 3200 |
+
if query_mode == 'followup' and st.session_state.last_query:
|
| 3201 |
+
contextual_question = f"Previous question: {st.session_state.last_query}\n\nFollow-up: {question}"
|
| 3202 |
else:
|
| 3203 |
+
contextual_question = question
|
| 3204 |
+
|
| 3205 |
+
with st.spinner("Performing intelligent analysis..."):
|
| 3206 |
+
try:
|
| 3207 |
+
response = openai_processor.query_with_enhanced_rag(
|
| 3208 |
+
contextual_question,
|
| 3209 |
+
processor.vector_store,
|
| 3210 |
+
processor.dataset_analysis
|
| 3211 |
+
)
|
| 3212 |
+
|
| 3213 |
+
# Store for follow-up
|
| 3214 |
+
st.session_state.last_query = question
|
| 3215 |
+
st.session_state.last_response = response
|
| 3216 |
+
|
| 3217 |
+
st.markdown("---")
|
| 3218 |
+
st.subheader("π Analysis Results")
|
| 3219 |
+
st.write(response)
|
| 3220 |
+
|
| 3221 |
+
# Show retrieved context if requested
|
| 3222 |
+
if show_retrieved:
|
| 3223 |
+
with st.expander("π Retrieved Context"):
|
| 3224 |
+
retrieved_docs = processor.vector_store.search(contextual_question, k=k_results)
|
| 3225 |
+
if retrieved_docs:
|
| 3226 |
+
for i, doc in enumerate(retrieved_docs, 1):
|
| 3227 |
+
st.write(f"**Document {i}** (Score: {doc['score']:.3f})")
|
| 3228 |
+
st.write(doc['text'])
|
| 3229 |
+
|
| 3230 |
+
metadata = doc.get('metadata', {})
|
| 3231 |
+
if metadata:
|
| 3232 |
+
info_parts = []
|
| 3233 |
+
if metadata.get('company'):
|
| 3234 |
+
info_parts.append(f"Company: {metadata['company']}")
|
| 3235 |
+
|
| 3236 |
+
# Handle both string and list formats for software
|
| 3237 |
+
adobe_apps = metadata.get('adobe_apps', [])
|
| 3238 |
+
if isinstance(adobe_apps, str):
|
| 3239 |
+
adobe_apps = [app.strip() for app in adobe_apps.split(',') if app.strip()]
|
| 3240 |
+
if adobe_apps:
|
| 3241 |
+
info_parts.append(f"Adobe: {', '.join(adobe_apps)}")
|
| 3242 |
+
|
| 3243 |
+
non_adobe_apps = metadata.get('non_adobe_apps', [])
|
| 3244 |
+
if isinstance(non_adobe_apps, str):
|
| 3245 |
+
non_adobe_apps = [app.strip() for app in non_adobe_apps.split(',') if app.strip()]
|
| 3246 |
+
if non_adobe_apps:
|
| 3247 |
+
info_parts.append(f"Non-Adobe: {', '.join(non_adobe_apps)}")
|
| 3248 |
+
|
| 3249 |
+
if info_parts:
|
| 3250 |
+
st.caption(" | ".join(info_parts))
|
| 3251 |
+
st.markdown("---")
|
| 3252 |
+
else:
|
| 3253 |
+
st.write("No relevant documents retrieved")
|
| 3254 |
+
|
| 3255 |
+
# Debug information
|
| 3256 |
+
if show_debug:
|
| 3257 |
+
with st.expander("π Debug Information"):
|
| 3258 |
+
st.write("**Query Mode:**", query_mode)
|
| 3259 |
+
st.write("**Original Question:**", question)
|
| 3260 |
+
st.write("**Contextual Question:**", contextual_question)
|
| 3261 |
+
st.write("**Retrieved Documents:**", len(retrieved_docs) if retrieved_docs else 0)
|
| 3262 |
+
st.write("**Vector Store Stats:**", stats)
|
| 3263 |
+
|
| 3264 |
+
# Reset query mode
|
| 3265 |
+
st.session_state.query_mode = 'new'
|
| 3266 |
+
|
| 3267 |
+
except Exception as e:
|
| 3268 |
+
st.error(f"Query processing error: {str(e)}")
|
| 3269 |
+
if show_debug:
|
| 3270 |
+
st.exception(e)
|
| 3271 |
+
|
| 3272 |
+
# Quick analysis buttons
|
| 3273 |
+
st.markdown("---")
|
| 3274 |
+
st.subheader("β‘ Quick Analysis")
|
| 3275 |
+
|
| 3276 |
+
quick_col1, quick_col2, quick_col3 = st.columns(3)
|
| 3277 |
+
|
| 3278 |
+
with quick_col1:
|
| 3279 |
+
if st.button("Count Designer Roles", use_container_width=True):
|
| 3280 |
+
if processor.dataset_analysis:
|
| 3281 |
+
count = processor.dataset_analysis['role_analysis'].get('designer_count', 0)
|
| 3282 |
+
st.success(f"**{count}** designer roles found")
|
| 3283 |
+
else:
|
| 3284 |
+
st.info("Analyzing designer roles from index...")
|
| 3285 |
|
| 3286 |
+
with quick_col2:
|
| 3287 |
+
if st.button("Adobe vs Non-Adobe", use_container_width=True):
|
| 3288 |
+
if processor.dataset_analysis:
|
| 3289 |
+
adobe_analysis = processor.dataset_analysis['adobe_analysis']
|
| 3290 |
+
st.success(f"Adobe only: **{adobe_analysis.get('adobe_only_count', 0)}** | Non-Adobe only: **{adobe_analysis.get('non_adobe_only_count', 0)}** | Both: **{adobe_analysis.get('both_apps_count', 0)}**")
|
| 3291 |
+
else:
|
| 3292 |
+
st.info("Analyzing software requirements from index...")
|
| 3293 |
+
|
| 3294 |
+
with quick_col3:
|
| 3295 |
+
if st.button("AI Tools Count", use_container_width=True):
|
| 3296 |
+
if processor.dataset_analysis:
|
| 3297 |
+
count = processor.dataset_analysis['ai_tools_analysis'].get('ai_tools_count', 0)
|
| 3298 |
+
st.success(f"**{count}** jobs mention AI tools")
|
| 3299 |
+
else:
|
| 3300 |
+
st.info("Analyzing AI tool mentions from index...")
|
| 3301 |
+
|
| 3302 |
+
# Display last response if available
|
| 3303 |
+
if st.session_state.last_response and not question:
|
| 3304 |
+
st.markdown("---")
|
| 3305 |
+
st.markdown("### π Last Response")
|
| 3306 |
+
with st.expander("View Last Response", expanded=False):
|
| 3307 |
+
st.write(f"**Query:** {st.session_state.last_query}")
|
| 3308 |
+
st.markdown("---")
|
| 3309 |
+
st.write(st.session_state.last_response)
|
| 3310 |
+
|
| 3311 |
+
def main():
|
| 3312 |
+
"""Main Streamlit application with multi-view support"""
|
| 3313 |
+
|
| 3314 |
+
# Initialize NLTK data
|
| 3315 |
+
setup_nltk_data()
|
| 3316 |
+
|
| 3317 |
+
# Initialize session state
|
| 3318 |
+
initialize_session_state()
|
| 3319 |
+
|
| 3320 |
+
# Get API key from secrets
|
| 3321 |
+
api_key = get_openai_api_key()
|
| 3322 |
+
|
| 3323 |
+
# Configure OpenAI processor if key is available
|
| 3324 |
+
if api_key and st.session_state.openai_processor is None:
|
| 3325 |
+
try:
|
| 3326 |
+
st.session_state.openai_processor = EnhancedOpenAIProcessor(api_key)
|
| 3327 |
+
except Exception as e:
|
| 3328 |
+
logger.error(f"Failed to initialize OpenAI processor: {e}")
|
| 3329 |
+
|
| 3330 |
+
# Route to appropriate view
|
| 3331 |
+
if st.session_state.current_view == 'master':
|
| 3332 |
+
show_master_view()
|
| 3333 |
+
elif st.session_state.current_view == 'admin':
|
| 3334 |
+
show_admin_view()
|
| 3335 |
+
elif st.session_state.current_view == 'client':
|
| 3336 |
+
show_client_view()
|
| 3337 |
|
| 3338 |
if __name__ == "__main__":
|
| 3339 |
main()
|