sriramsrivatsan commited on
Commit
d96653d
Β·
verified Β·
1 Parent(s): d2aa4ee

Upload streamlit_app.py

Browse files
Files changed (1) hide show
  1. 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=sanitized_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, min(1.0, 1.0 - float(distance)))
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 main():
2472
- """Main Streamlit application with enhanced Chroma persistence support and auto-detection"""
 
 
2473
 
2474
- st.title("🎨 Creative Professionals Job Analysis System")
2475
- st.markdown("**Enhanced RAG-powered analysis** with persistent vector storage using ChromaDB")
2476
 
2477
- # Initialize NLTK data
2478
- setup_nltk_data()
2479
 
2480
- # NEW: Get API key from secrets
2481
- api_key = get_openai_api_key()
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
- # Display persistence status at top
2488
- persistence_status = {
2489
- 'chroma_available': CHROMA_AVAILABLE,
2490
- 'persistence_enabled': ENABLE_PERSISTENCE,
2491
- 'persist_path': CHROMA_PERSIST_PATH if ENABLE_PERSISTENCE else None
2492
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2493
 
2494
- # Persistence status indicator
2495
- if ENABLE_PERSISTENCE and CHROMA_AVAILABLE:
2496
- st.success(f"πŸ’Ύ Persistent mode enabled - Data stored at: {CHROMA_PERSIST_PATH}")
2497
- elif CHROMA_AVAILABLE:
2498
- st.info("πŸ’Ύ In-memory mode - ChromaDB available but persistence disabled")
2499
  else:
2500
- st.warning("⚠️ ChromaDB not available - Using fallback memory storage")
2501
 
2502
- # Sidebar configuration
2503
- with st.sidebar:
2504
- st.header("πŸ”§ System Configuration")
2505
-
2506
- # API Key status
2507
- st.subheader("πŸ”‘ API Configuration")
2508
- st.success("βœ… OpenAI API Key Loaded")
2509
- st.caption("Configured via secrets/environment")
 
 
 
 
 
2510
 
2511
- st.markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
2512
 
2513
- # Persistence controls
2514
- st.subheader("πŸ’Ύ Persistence Settings")
2515
- if ENABLE_PERSISTENCE:
2516
- st.success("βœ… Persistence Enabled")
2517
- st.caption(f"Path: {CHROMA_PERSIST_PATH}")
2518
  else:
2519
- st.info("πŸ’Ύ Memory Mode")
2520
-
 
 
 
 
 
 
 
 
2521
  if CHROMA_AVAILABLE:
2522
- st.success("βœ… ChromaDB Available")
2523
  else:
2524
- st.error("❌ ChromaDB Missing")
2525
- st.caption("Install chromadb for persistence")
2526
-
2527
- st.markdown("---")
2528
-
2529
- # Dependency status
2530
- dep_status = []
2531
- if USEARCH_AVAILABLE:
2532
- dep_status.append("βœ… USearch (Fallback)")
2533
  else:
2534
- dep_status.append("❌ USearch - Install recommended")
2535
-
2536
- if SENTENCE_TRANSFORMERS_AVAILABLE:
2537
- dep_status.append("βœ… SentenceTransformers")
 
 
2538
  else:
2539
- dep_status.append("❌ SentenceTransformers - Install required")
2540
-
2541
- if NLTK_AVAILABLE:
2542
- dep_status.append("βœ… NLTK")
 
2543
  else:
2544
- dep_status.append("⚠️ NLTK - Limited functionality")
2545
-
2546
- for status in dep_status:
2547
- if "βœ…" in status:
2548
- st.success(status)
2549
- elif "⚠️" in status:
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
- # Configure OpenAI with secret key
2583
- if api_key:
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
- # NEW: Auto-detection check on startup
2594
- if CHROMA_AVAILABLE and not st.session_state.auto_detected_mode:
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
- st.header("πŸ€– Intelligent Creative Job Analysis")
2652
- st.markdown("Ask sophisticated questions about creative professionals, software requirements, and industry trends!")
 
 
 
 
 
2653
 
2654
- # Quick stats with persistence info
 
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
- # Show auto-detection info if applicable
2672
- if stats.get('auto_detected', False):
2673
- st.success("πŸ” **Using Auto-Detected Index** - No data processing required!")
 
 
 
 
2674
 
2675
- # Example questions based on q.txt
2676
- with st.expander("πŸ’‘ Example Questions from Your Analysis Requirements"):
 
 
2677
  st.markdown("""
2678
- **Adobe vs Non-Adobe Analysis:**
2679
- - How many postings ask for non-Adobe apps but not Adobe apps? What are those apps?
2680
- - How many postings ask for both Adobe and non-Adobe apps? What are those combinations?
2681
- - How many job listings request experience with Photoshop? And how many request Photoshop's competitors?
2682
-
2683
- **Creative Role Analysis:**
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
- **AI Tools and Modern Workflows:**
2694
- - How many posts ask for AI skills? What are those AI tools? What are those occupations?
2695
- - What industries are hiring more creative professionals? What kind of creative professionals?
2696
- - What soft skills are mentioned in the postings for creative professionals?
 
 
 
 
 
 
 
 
2697
  """)
 
 
 
 
 
 
 
 
2698
 
2699
- # Query input
2700
- question = st.text_area(
2701
- "Ask about creative jobs, software requirements, or industry trends:",
2702
- placeholder="e.g., How many designer roles require Adobe software vs non-Adobe alternatives?",
2703
- height=100
2704
- )
2705
 
2706
- # Query options
2707
- show_retrieved = st.checkbox("Show Retrieved Context", value=True)
2708
 
2709
- if st.button("πŸš€ Analyze with Enhanced RAG", type="primary", use_container_width=True) and question:
2710
- if not openai_processor or not openai_processor.is_available():
2711
- st.error("OpenAI processor not available. Please check your API key configuration.")
2712
- return
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
- # Quick analysis buttons
2765
- st.subheader("⚑ Quick Analysis")
2766
- quick_col1, quick_col2, quick_col3 = st.columns(3)
 
 
2767
 
2768
- with quick_col1:
2769
- if st.button("Count Designer Roles", use_container_width=True):
2770
- if processor.dataset_analysis:
2771
- count = processor.dataset_analysis['role_analysis'].get('designer_count', 0)
2772
- st.success(f"**{count}** designer roles found")
2773
- else:
2774
- # Try to get quick stats from vector store
2775
- st.info("Analyzing designer roles from index...")
2776
 
2777
- with quick_col2:
2778
- if st.button("Adobe vs Non-Adobe", use_container_width=True):
2779
- if processor.dataset_analysis:
2780
- adobe_analysis = processor.dataset_analysis['adobe_analysis']
2781
- 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)}**")
2782
- else:
2783
- st.info("Analyzing software requirements from index...")
 
2784
 
2785
- with quick_col3:
2786
- if st.button("AI Tools Count", use_container_width=True):
2787
- if processor.dataset_analysis:
2788
- count = processor.dataset_analysis['ai_tools_analysis'].get('ai_tools_count', 0)
2789
- st.success(f"**{count}** jobs mention AI tools")
2790
- else:
2791
- st.info("Analyzing AI tool mentions from index...")
 
2792
 
2793
- # Option to switch back to normal mode
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 existing index status if found
2826
- if CHROMA_AVAILABLE:
2827
- persistence_status = processor.get_persistence_status()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2828
 
2829
- if persistence_status['collection_exists']:
2830
- st.info(f"πŸ“š Found existing persistent index with {persistence_status['documents_count']} documents using {persistence_status['backend']} backend")
2831
-
2832
- # Option to use existing index
2833
- col1, col2 = st.columns(2)
2834
- with col1:
2835
- if st.button("πŸ’Ύ Use Existing Index", help="Skip to query interface using existing data"):
2836
- st.session_state.rag_ready = True
2837
- st.success("βœ… Ready to query existing index!")
2838
- st.experimental_rerun()
2839
-
2840
- with col2:
2841
- if st.button("πŸ—‘οΈ Clear Existing Index", help="Remove existing index data"):
 
 
 
 
 
 
 
 
 
 
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("βœ… Existing index cleared")
2845
  st.session_state.rag_ready = False
2846
- st.experimental_rerun()
2847
- else:
2848
- st.error("❌ Failed to clear index")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2849
 
2850
  # File upload section
2851
- st.header("πŸ“„ Upload Creative Job Dataset")
 
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
- # Check if new file will overwrite existing data
2860
  if processor.vector_store.collection_exists():
2861
- st.warning("⚠️ **Data Overwrite Warning**: Uploading a new CSV will replace the existing RAG index with new data. The current persistent index will be overwritten.")
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.header("βš™οΈ Processing Workflow")
 
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 indicators
2945
- show_processing_status(processor, openai_processor)
 
2946
 
2947
- # Show analysis results if available
2948
  if processor.dataset_analysis is not None:
2949
- show_analysis_results(processor)
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
- show_export_options(processor)
 
2963
 
2964
- def show_processing_status(processor, openai_processor):
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.info("βœ… Data Processed")
2970
  else:
2971
- st.warning("⏳ Data Not Processed")
2972
 
2973
  with status_cols[1]:
2974
  if processor.dataset_analysis is not None:
2975
- st.info("βœ… Analysis Complete")
2976
  else:
2977
- st.warning("⏳ Analysis Pending")
2978
 
2979
  with status_cols[2]:
2980
  if st.session_state.rag_ready:
2981
- st.info("βœ… RAG Ready")
2982
  else:
2983
- st.warning("⏳ RAG Not Built")
2984
 
2985
  with status_cols[3]:
2986
- if openai_processor and openai_processor.is_available():
2987
- st.info("βœ… OpenAI Ready")
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.info("βœ… Persistent")
2996
  else:
2997
- st.warning("πŸ’Ύ Memory Only")
2998
 
2999
- def show_analysis_results(processor):
3000
- """Show comprehensive analysis results"""
3001
- st.header("πŸ“ˆ Creative Job Analysis Results")
3002
 
3003
  analysis_summary = processor.get_analysis_summary()
3004
 
3005
  if analysis_summary:
3006
  # Key metrics
3007
- st.subheader("πŸ“Š Key Metrics")
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("πŸ’» Software Requirements Analysis")
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("🎨 Top Adobe Applications")
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("πŸ› οΈ Top Non-Adobe Applications")
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("πŸ”„ Cross-Disciplinary Requirements")
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 show_export_options(processor):
3085
- """Show export functionality"""
3086
- st.header("πŸ“€ Export Results")
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
- # Footer with technical information
3138
- st.markdown("---")
3139
- st.markdown("### πŸ”§ Technical Details")
 
 
 
 
 
 
 
 
3140
 
3141
- tech_col1, tech_col2, tech_col3 = st.columns(3)
 
3142
 
3143
- with tech_col1:
3144
- st.caption("**Vector Storage**")
3145
- if CHROMA_AVAILABLE:
3146
- st.caption("βœ… ChromaDB Available")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3147
  else:
3148
- st.caption("❌ ChromaDB Missing")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3149
 
3150
- with tech_col2:
3151
- st.caption("**Persistence**")
3152
- if ENABLE_PERSISTENCE:
3153
- st.caption(f"βœ… Enabled: {CHROMA_PERSIST_PATH}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3154
  else:
3155
- st.caption("πŸ’Ύ Memory Mode")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3156
 
3157
- with tech_col3:
3158
- st.caption("**API Configuration**")
3159
- st.caption("βœ… Secrets-based Config")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()