diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..bce75ea383bbb4a0997b59836b2832d62c88270f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,16 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +assets/all_resto.png filter=lfs diff=lfs merge=lfs -text +assets/general_conv_info_through_chat.png filter=lfs diff=lfs merge=lfs -text +assets/some_results.PNG filter=lfs diff=lfs merge=lfs -text +assets/greet_general_convo.png filter=lfs diff=lfs merge=lfs -text +assets/name_entering.png filter=lfs diff=lfs merge=lfs -text +assets/rubbish.PNG filter=lfs diff=lfs merge=lfs -text +assets/landing.png filter=lfs diff=lfs merge=lfs -text +assets/mermaid.png filter=lfs diff=lfs merge=lfs -text +assets/ready_to_book.png filter=lfs diff=lfs merge=lfs -text +assets/mermaid-1.png filter=lfs diff=lfs merge=lfs -text +assets/booking_successful.png filter=lfs diff=lfs merge=lfs -text +db/restaurant_reservation.db filter=lfs diff=lfs merge=lfs -text +db/chroma/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text diff --git a/Business Strategy Presentation/app.js b/Business Strategy Presentation/app.js new file mode 100644 index 0000000000000000000000000000000000000000..3f5cb43c19d10ca9cbeb45d7480e60f5aeb54144 --- /dev/null +++ b/Business Strategy Presentation/app.js @@ -0,0 +1,534 @@ +// Presentation JavaScript functionality + +class Presentation { + constructor() { + this.currentSlide = 0; + this.totalSlides = 15; + this.slides = []; + this.isFullscreen = false; + + this.init(); + } + + init() { + // Wait for DOM to be fully loaded + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.setupPresentation()); + } else { + this.setupPresentation(); + } + } + + setupPresentation() { + this.slides = document.querySelectorAll('.slide'); + console.log('Found slides:', this.slides.length); + + if (this.slides.length === 0) { + console.error('No slides found!'); + return; + } + + this.totalSlides = this.slides.length; + this.bindEvents(); + this.showSlide(0); + this.updateProgress(); + this.updateCounter(); + this.updateNavigationButtons(); + } + + bindEvents() { + // Button controls + const prevBtn = document.getElementById('prev-slide'); + const nextBtn = document.getElementById('next-slide'); + const fullscreenBtn = document.getElementById('fullscreen-toggle'); + + if (prevBtn) prevBtn.addEventListener('click', () => this.previousSlide()); + if (nextBtn) nextBtn.addEventListener('click', () => this.nextSlide()); + if (fullscreenBtn) fullscreenBtn.addEventListener('click', () => this.toggleFullscreen()); + + // Keyboard controls + document.addEventListener('keydown', (e) => this.handleKeydown(e)); + + // Fullscreen change events + document.addEventListener('fullscreenchange', () => this.handleFullscreenChange()); + document.addEventListener('webkitfullscreenchange', () => this.handleFullscreenChange()); + document.addEventListener('mozfullscreenchange', () => this.handleFullscreenChange()); + document.addEventListener('msfullscreenchange', () => this.handleFullscreenChange()); + + // Touch/swipe events for mobile + this.addTouchEvents(); + + console.log('Event listeners bound successfully'); + } + + handleKeydown(e) { + switch(e.key) { + case 'ArrowRight': + case ' ': + case 'Enter': + e.preventDefault(); + this.nextSlide(); + break; + case 'ArrowLeft': + e.preventDefault(); + this.previousSlide(); + break; + case 'Home': + e.preventDefault(); + this.goToSlide(0); + break; + case 'End': + e.preventDefault(); + this.goToSlide(this.totalSlides - 1); + break; + case 'f': + case 'F': + if (!e.ctrlKey && !e.metaKey) { + e.preventDefault(); + this.toggleFullscreen(); + } + break; + case 'Escape': + if (this.isFullscreen) { + this.exitFullscreen(); + } + break; + } + } + + addTouchEvents() { + let startX = 0; + let startY = 0; + const container = document.querySelector('.presentation-container'); + + if (!container) return; + + container.addEventListener('touchstart', (e) => { + startX = e.touches[0].clientX; + startY = e.touches[0].clientY; + }, { passive: true }); + + container.addEventListener('touchend', (e) => { + if (!startX || !startY) return; + + const endX = e.changedTouches[0].clientX; + const endY = e.changedTouches[0].clientY; + + const deltaX = startX - endX; + const deltaY = startY - endY; + + // Only trigger if horizontal swipe is dominant and significant + if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) { + if (deltaX > 0) { + this.nextSlide(); + } else { + this.previousSlide(); + } + } + + startX = 0; + startY = 0; + }, { passive: true }); + } + + nextSlide() { + if (this.currentSlide < this.totalSlides - 1) { + this.goToSlide(this.currentSlide + 1); + } + } + + previousSlide() { + if (this.currentSlide > 0) { + this.goToSlide(this.currentSlide - 1); + } + } + + goToSlide(slideIndex) { + if (slideIndex >= 0 && slideIndex < this.totalSlides) { + console.log(`Going to slide ${slideIndex + 1}`); + + // Hide current slide + this.hideAllSlides(); + + // Update current slide index + this.currentSlide = slideIndex; + + // Show new slide + this.showSlide(slideIndex); + + // Update UI elements + this.updateProgress(); + this.updateCounter(); + this.updateNavigationButtons(); + + // Handle slide-specific logic + this.handleSlideSpecifics(slideIndex); + } + } + + hideAllSlides() { + this.slides.forEach(slide => { + slide.classList.remove('active', 'animate-in'); + }); + } + + showSlide(index) { + const slide = this.slides[index]; + if (slide) { + // Remove active class from all slides first + this.hideAllSlides(); + + // Add active class to current slide + slide.classList.add('active'); + + // Add animation after a brief delay + setTimeout(() => { + slide.classList.add('animate-in'); + this.animateSlideContent(slide); + }, 50); + } + } + + animateSlideContent(slide) { + const elements = slide.querySelectorAll('.bullet-points li, .value-prop, .flow-step, .metric-category, .tech-component, .framework-item, .phase-card, .roi-metric, .challenge-category, .advantage, .timeline-item'); + + elements.forEach((element, index) => { + element.style.opacity = '0'; + element.style.transform = 'translateY(20px)'; + element.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; + + setTimeout(() => { + element.style.opacity = '1'; + element.style.transform = 'translateY(0)'; + }, 100 + (index * 100)); + }); + } + + updateProgress() { + const progressBar = document.querySelector('.progress-indicator'); + if (progressBar) { + const progress = ((this.currentSlide + 1) / this.totalSlides) * 100; + progressBar.style.width = `${progress}%`; + } + } + + updateCounter() { + const currentSlideEl = document.getElementById('current-slide'); + const totalSlidesEl = document.getElementById('total-slides'); + + if (currentSlideEl) currentSlideEl.textContent = this.currentSlide + 1; + if (totalSlidesEl) totalSlidesEl.textContent = this.totalSlides; + } + + updateNavigationButtons() { + const prevBtn = document.getElementById('prev-slide'); + const nextBtn = document.getElementById('next-slide'); + + if (prevBtn) { + prevBtn.disabled = this.currentSlide === 0; + } + + if (nextBtn) { + nextBtn.disabled = this.currentSlide === this.totalSlides - 1; + + // Update button text for last slide + if (this.currentSlide === this.totalSlides - 1) { + nextBtn.innerHTML = ` + + + + `; + nextBtn.setAttribute('aria-label', 'Finish presentation'); + } else { + nextBtn.innerHTML = ` + + + + `; + nextBtn.setAttribute('aria-label', 'Next slide'); + } + } + } + + toggleFullscreen() { + if (!this.isFullscreen) { + this.enterFullscreen(); + } else { + this.exitFullscreen(); + } + } + + enterFullscreen() { + const container = document.querySelector('.presentation-container'); + + if (container.requestFullscreen) { + container.requestFullscreen(); + } else if (container.webkitRequestFullscreen) { + container.webkitRequestFullscreen(); + } else if (container.mozRequestFullScreen) { + container.mozRequestFullScreen(); + } else if (container.msRequestFullscreen) { + container.msRequestFullscreen(); + } + } + + exitFullscreen() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } + } + + handleFullscreenChange() { + const isFullscreen = !!(document.fullscreenElement || + document.webkitFullscreenElement || + document.mozFullScreenElement || + document.msFullscreenElement); + + this.isFullscreen = isFullscreen; + this.updateFullscreenIcon(); + + const container = document.querySelector('.presentation-container'); + if (container) { + if (isFullscreen) { + container.classList.add('fullscreen'); + } else { + container.classList.remove('fullscreen'); + } + } + } + + updateFullscreenIcon() { + const icon = document.getElementById('fullscreen-icon'); + + if (icon) { + if (this.isFullscreen) { + icon.innerHTML = ` + + `; + } else { + icon.innerHTML = ` + + `; + } + } + } + + // Method to handle special slide interactions + initSlideInteractions() { + // Add click handlers for call-to-action buttons + const ctaButtons = document.querySelectorAll('.call-to-action .btn'); + ctaButtons.forEach(button => { + button.addEventListener('click', (e) => { + e.preventDefault(); + this.handleCTAClick(button.textContent.trim()); + }); + }); + + // Add hover effects for interactive elements + this.addHoverEffects(); + } + + handleCTAClick(buttonText) { + if (buttonText.includes('Schedule')) { + this.showNotification('Meeting request sent! Check your calendar for confirmation.'); + } else if (buttonText.includes('Download')) { + this.showNotification('Strategy document download initiated.'); + } + } + + showNotification(message) { + // Create notification element + const notification = document.createElement('div'); + notification.className = 'notification'; + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background-color: var(--color-success); + color: var(--color-btn-primary-text); + padding: 16px 24px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + box-shadow: 0 4px 12px rgba(0,0,0,0.1); + z-index: 1001; + opacity: 0; + transform: translateX(100%); + transition: all 0.3s ease; + `; + notification.textContent = message; + + document.body.appendChild(notification); + + // Animate in + setTimeout(() => { + notification.style.opacity = '1'; + notification.style.transform = 'translateX(0)'; + }, 100); + + // Remove after 3 seconds + setTimeout(() => { + notification.style.opacity = '0'; + notification.style.transform = 'translateX(100%)'; + setTimeout(() => { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + }, 300); + }, 3000); + } + + addHoverEffects() { + // Add hover effects to interactive elements + const interactiveElements = document.querySelectorAll('.value-prop, .tech-component, .framework-item, .advantage, .phase-card'); + + interactiveElements.forEach(element => { + element.addEventListener('mouseenter', () => { + element.style.transform = 'translateY(-5px)'; + element.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease'; + element.style.boxShadow = 'var(--shadow-lg)'; + }); + + element.addEventListener('mouseleave', () => { + element.style.transform = 'translateY(0)'; + element.style.boxShadow = ''; + }); + }); + } + + // Method to handle slide-specific logic + handleSlideSpecifics(slideIndex) { + switch(slideIndex) { + case 0: // Title slide + setTimeout(() => this.animateTitleSlide(), 500); + break; + case 5: // Customer journey flow + setTimeout(() => this.animateFlowDiagram(), 500); + break; + case 11: // Business impact + setTimeout(() => this.animateROIMetrics(), 500); + break; + case 14: // Conclusion + setTimeout(() => this.initSlideInteractions(), 500); + break; + } + } + + animateTitleSlide() { + const highlights = document.querySelectorAll('.highlight'); + highlights.forEach((highlight, index) => { + highlight.style.opacity = '0'; + highlight.style.transform = 'scale(0.8)'; + highlight.style.transition = 'all 0.5s ease'; + + setTimeout(() => { + highlight.style.opacity = '1'; + highlight.style.transform = 'scale(1)'; + }, index * 200); + }); + } + + animateFlowDiagram() { + const steps = document.querySelectorAll('.flow-step'); + steps.forEach((step, index) => { + step.style.opacity = '0'; + step.style.transform = 'translateX(-50px)'; + step.style.transition = 'all 0.5s ease'; + + setTimeout(() => { + step.style.opacity = '1'; + step.style.transform = 'translateX(0)'; + }, index * 300); + }); + } + + animateROIMetrics() { + const metrics = document.querySelectorAll('.roi-metric h3'); + metrics.forEach((metric, index) => { + const finalValue = metric.textContent; + const isPercentage = finalValue.includes('%'); + const numericValue = parseInt(finalValue.replace(/[^\d]/g, '')); + + if (numericValue > 0) { + metric.textContent = isPercentage ? '0%' : '0'; + + setTimeout(() => { + this.animateNumber(metric, finalValue, numericValue, isPercentage); + }, index * 200); + } + }); + } + + animateNumber(element, finalValue, numericValue, isPercentage) { + let currentValue = 0; + const increment = Math.ceil(numericValue / 30); + + const timer = setInterval(() => { + currentValue += increment; + if (currentValue >= numericValue) { + element.textContent = finalValue; + clearInterval(timer); + } else { + element.textContent = isPercentage ? `${currentValue}%` : `${currentValue}`; + } + }, 50); + } +} + +// Initialize presentation when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + console.log('DOM loaded, initializing presentation...'); + window.presentation = new Presentation(); +}); + +// Fallback initialization for cases where DOMContentLoaded already fired +if (document.readyState !== 'loading') { + console.log('DOM already loaded, initializing presentation...'); + window.presentation = new Presentation(); +} + +// Add global error handling +window.addEventListener('error', (e) => { + console.error('Presentation error:', e.error); +}); + +// Add performance optimization +window.addEventListener('load', () => { + console.log('Window loaded, optimizing...'); + // Preload images + const images = document.querySelectorAll('img[src]'); + images.forEach(img => { + const link = document.createElement('link'); + link.rel = 'prefetch'; + link.href = img.src; + document.head.appendChild(link); + }); +}); + +// Add visibility change handling for presentations +document.addEventListener('visibilitychange', () => { + if (document.hidden) { + // Pause any ongoing animations when tab is not visible + document.querySelectorAll('.animate-in').forEach(el => { + if (el.style.animationPlayState !== undefined) { + el.style.animationPlayState = 'paused'; + } + }); + } else { + // Resume animations when tab becomes visible + document.querySelectorAll('.animate-in').forEach(el => { + if (el.style.animationPlayState !== undefined) { + el.style.animationPlayState = 'running'; + } + }); + } +}); + +// Export for potential external use +window.PresentationApp = Presentation; \ No newline at end of file diff --git a/Business Strategy Presentation/index.html b/Business Strategy Presentation/index.html new file mode 100644 index 0000000000000000000000000000000000000000..caf1e5b12571a843383ad73312a34e0507d2b604 --- /dev/null +++ b/Business Strategy Presentation/index.html @@ -0,0 +1,638 @@ + + + + + + FoodieSpot AI Strategy 2025 + + + +
+ +
+ +
+ 1 / 15 +
+ + +
+ + +
+
+
+ + +
+ +
+
+

FoodieSpot AI Strategy 2025

+

LLM-Based Conversational Agent Business Strategy

+
+
+
Transform Customer Experience
+
30% Cost Reduction
+
90% Automation
+
+
+
+

FoodieSpot Restaurant Chain

+

Presented by: Sri Vallabh Tammireddy

+
+ Restaurant integrating AI for customer service and order management +
+
+ + +
+
+

Executive Summary

+
    +
  • Industry-leading AI-powered restaurant customer experience by 2027
  • +
  • 90% automation of queries, 30% cost reduction, 25% satisfaction boost
  • +
  • 3-phase rollout across 20+ restaurant locations
  • +
  • ROI: 12% table turnover increase, 20% customer retention
  • +
+
+ Chatbots can assist restaurants in multiple ways +
+
+
+ + +
+
+

Strategic Vision & Goals

+
+
+

Long Term Goals

+
    +
  • Become restaurant industry leader in AI-powered customer experience by 2027
  • +
  • Establish new standard for restaurant automation and efficiency
  • +
  • Scale solution across 100+ locations nationwide
  • +
  • Develop proprietary conversation flow technology
  • +
+
+
+

Key Metrics & Targets

+
    +
  • 90% query automation within 12 months
  • +
  • 25% improvement in customer satisfaction scores
  • +
  • 30% reduction in operational costs
  • +
  • 20% boost in return customer frequency
  • +
+
+
+
+
+ + +
+
+

Success Metrics

+
+
+

Operational

+
    +
  • Handle 80%+ of customer queries without human intervention
  • +
  • Achieve <1 minute average response time for complex queries
  • +
  • Process 95%+ reservation requests successfully
  • +
  • Reduce no-show rates by 15%
  • +
+
+
+

Business Impact

+
    +
  • Increase table turnover rate by 12%
  • +
  • Boost customer retention by 20%
  • +
  • Generate 15% cost savings within 12 months
  • +
  • Achieve 4.5+ star customer satisfaction rating
  • +
+
+
+

Technical Performance

+
    +
  • Maintain 99.5% uptime
  • +
  • Support 500+ concurrent users during peak
  • +
  • Seamless POS integration
  • +
  • Multi-language natural language processing
  • +
+
+
+
+
+ + +
+
+

The FoodieSpot Solution

+
+

FoodieSpot's AI-powered conversational agent revolutionizes the dining experience by providing customers with instant, intelligent assistance for restaurant discovery, menu inquiries, and seamless reservation booking. The system combines advanced natural language processing with real-time database integration to handle everything from simple questions about restaurant hours to complex multi-party reservations across 20+ restaurant locations.

+
+
+

Key Value Propositions

+
+
+
๐Ÿ’ฌ
+

Intelligent Conversations

+

Natural language understanding with contextual awareness

+
+
+
โšก
+

Instant Response

+

24/7 availability with sub-second response times

+
+
+
๐Ÿ“Š
+

Data-Driven Insights

+

Continuous learning from customer interactions

+
+
+
+
+
+ + +
+
+

End-User Experience Flow

+
+
+
1
+
+

Discovery & Engagement

+

Customer initiates conversation and system identifies dining intent

+
+
+
+
2
+
+

Intelligent Recommendation

+

AI analyzes preferences and provides personalized restaurant suggestions

+
+
+
+
3
+
+

Seamless Reservation

+

Collects booking information and confirms real-time availability

+
+
+
+
4
+
+

Pre-Arrival Enhancement

+

Automated reminders and menu pre-ordering options

+
+
+
+
5
+
+

Post-Experience Follow-up

+

Feedback collection and loyalty program enrollment

+
+
+
+
+
+ + +
+
+

Conversation Flow Architecture

+
+ Diagram illustrating how chatbots function, including NLP, knowledge base, and data storage +
+
+

Key Conversation States

+
+
Initial Greeting
+
โ†’
+
Intent Recognition
+
โ†’
+
Answer user queries
+
โ†’
+
Information Collection
+
โ†’
+
Confirmation
+
โ†’
+
Completion
+
+
+
+
+ + +
+
+

Technical Capabilities

+
+
+

Knowledge Bases

+
    +
  • Restaurant locations, hours & policies
  • +
  • Complete menu database with allergen info
  • +
  • Historical customer preferences
  • +
+
+
+

Integration Requirements

+
    +
  • Reservation management system
  • +
  • Customer relationship management (CRM)
  • +
  • Point of sale (POS) system
  • +
+
+
+
+

Feature Implementation Complexity

+
+
+

Green (Easy)

+
    +
  • SMS/WhatsApp notification integration
  • +
  • Basic customer feedback collection
  • +
  • Email reminder system
  • +
  • Simple loyalty point tracking
  • +
+
+
+

Yellow (Medium)

+
    +
  • Voice interface integration
  • +
  • Advanced dietary preference matching
  • +
  • Dynamic pricing integration
  • +
  • Multi-language support expansion
  • +
+
+
+

Red (Complex)

+
    +
  • Predictive table optimization ML
  • +
  • External calendar integration
  • +
  • Advanced sentiment analysis
  • +
  • IoT integration for smart management
  • +
+
+
+
+
+
+ + +
+
+

Technology Architecture

+
+
+
๐Ÿง 
+

Language Model

+

LLaMA 3 8B via Groq API

+
+
+
๐Ÿ”
+

Vector Database

+

ChromaDB for semantic search

+
+
+
๐Ÿ’พ
+

SQL Database

+

SQLite for structured data

+
+
+
๐Ÿ”„
+

Embedding Model

+

Sentence Transformers

+
+
+
๐Ÿ–ฅ๏ธ
+

UI Framework

+

Streamlit for web interface

+
+
+
+
+ + +
+
+

Phase 1: Pilot Program (Months 1-3)

+
+
+
+

Scope

+

3 flagship locations

+
+
+

Capacity

+

100 daily interactions per location

+
+
+

Focus

+

Testing and validation

+
+
+
+

Testing Framework

+
+
+

A/B Testing

+

Comparing AI vs traditional customer service

+
+
+

User Satisfaction

+

Post-interaction surveys and feedback collection

+
+
+

Performance Metrics

+

Response time, accuracy, completion rate

+
+
+

Staff Feedback

+

Regular listening sessions with restaurant teams

+
+
+
+
+
+
+ + +
+
+

Scale-Up Plan (Months 4-12)

+
+
+

Phase 2: Regional Expansion

+
Months 4-8
+
    +
  • Scope: 10 additional locations across 3-4 regions
  • +
  • Capacity: 5,000+ daily interactions
  • +
  • Focus: Multi-language support and advanced personalization
  • +
+
+
+

Phase 3: National Deployment

+
Months 9-12
+
    +
  • Scope: All 20+ FoodieSpot locations nationwide
  • +
  • Capacity: 50,000+ daily interactions
  • +
  • Focus: Predictive analytics and cross-location recommendations
  • +
+
+
+
+

Feature Progression

+
+
+
+

Basic reservation

+
+
+
+

Menu recommendations

+
+
+
+

Multi-language

+
+
+
+

Personalization

+
+
+
+

Predictive analytics

+
+
+
+
+
+ + +
+
+

Business Impact & ROI

+
+
+

30%

+

Reduction in customer service costs

+
+
+

12%

+

Table turnover improvement

+
+
+

20%

+

Customer retention increase

+
+
+
+

Market Opportunity

+
+
+

$20.81B

+

Chatbot market by 2029

+
+
+

24.53%

+

Annual growth rate

+
+
+

$6.2B

+

Reservation system market by 2033

+
+
+
+
+

Sources

+ +

Indian Conversion Note (at 1 USD = โ‚น85.48):
+ $20.81B โ‰ˆ โ‚น1,78,000 crores
+ $6.2B โ‰ˆ โ‚น53,000 crores

+
+
+
+ + + +
+
+

Implementation Challenges & Mitigation

+
+
+

Technical Challenges

+
+
+

LLM reliability

+

Prompt engineering & fallback mechanisms

+
+
+

Real-time data sync

+

API integration protocols

+
+
+

Peak load performance

+

Auto-scaling infrastructure

+
+
+
+
+

Business Challenges

+
+
+

Customer adoption

+

Gradual introduction with clear benefits

+
+
+

Integration complexity

+

Phased integration approach

+
+
+

Data privacy

+

Enterprise security protocols

+
+
+
+
+

Operational Challenges

+
+
+

Staff training

+

Comprehensive training programs

+
+
+

Quality assurance

+

Continuous monitoring systems

+
+
+

Competitive response

+

Rapid innovation cycles

+
+
+
+
+
+
+ + +
+
+

Market Position & Differentiation

+
+
+

Market Trends

+
    +
  • Rapid growth in restaurant automation solutions
  • +
  • Rising customer expectations for personalization
  • +
  • Increasing adoption of voice and chat interfaces
  • +
  • Growing focus on data-driven customer experiences
  • +
+
+
+

Competitive Landscape

+
    +
  • Most competitors offer single-channel solutions
  • +
  • Limited personalization capabilities
  • +
  • Few solutions integrate with existing POS systems
  • +
  • Minimal predictive analytics capabilities
  • +
+
+
+

Our Sustainable Advantages

+
+
+

Omni-channel Integration

+

Seamless experience across all touchpoints

+
+
+

Proprietary ML Models

+

Custom-trained on restaurant-specific data

+
+
+

First-mover Advantage

+

Rapid deployment ahead of competitors

+
+
+
+
+
+
+ +
+
+

Key Elements of Our Brand

+
+ Infographic detailing key elements of a company brand-building brainstorm +
+
+
+ + + +
+
+

Conclusion & Recommended Actions

+
+
+

Strategic Recommendations

+
    +
  • Immediately approve Phase 1 pilot for Q3 2025 launch
  • +
  • Allocate $1.2M initial budget for technology development
  • +
  • Form cross-functional team from IT, Operations and Marketing
  • +
  • Begin customer research at 3 pilot locations next month
  • +
  • Approve vendor selection process for technology partners
  • +
+
+
+

Implementation Timeline

+
+
+ July 2025 +

Project kickoff & vendor selection

+
+
+ August 2025 +

Pilot development begins

+
+
+ October 2025 +

Phase 1 launch at pilot locations

+
+
+ January 2026 +

Phase 2 regional expansion

+
+
+ June 2026 +

Phase 3 national deployment

+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/Business Strategy Presentation/readme.txt.txt b/Business Strategy Presentation/readme.txt.txt new file mode 100644 index 0000000000000000000000000000000000000000..03f718eed0f64def83f327a76d9bc040ef88a1fe --- /dev/null +++ b/Business Strategy Presentation/readme.txt.txt @@ -0,0 +1 @@ +Open index.html for presentation \ No newline at end of file diff --git a/Business Strategy Presentation/style.css b/Business Strategy Presentation/style.css new file mode 100644 index 0000000000000000000000000000000000000000..80e2f5da9eea69bf3a1cae3aade6e70c747bc38c --- /dev/null +++ b/Business Strategy Presentation/style.css @@ -0,0 +1,1575 @@ + +:root { + /* Colors */ + --color-background: rgba(252, 252, 249, 1); + --color-surface: rgba(255, 255, 253, 1); + --color-text: rgba(19, 52, 59, 1); + --color-text-secondary: rgba(98, 108, 113, 1); + --color-primary: rgba(33, 128, 141, 1); + --color-primary-hover: rgba(29, 116, 128, 1); + --color-primary-active: rgba(26, 104, 115, 1); + --color-secondary: rgba(94, 82, 64, 0.12); + --color-secondary-hover: rgba(94, 82, 64, 0.2); + --color-secondary-active: rgba(94, 82, 64, 0.25); + --color-border: rgba(94, 82, 64, 0.2); + --color-btn-primary-text: rgba(252, 252, 249, 1); + --color-card-border: rgba(94, 82, 64, 0.12); + --color-card-border-inner: rgba(94, 82, 64, 0.12); + --color-error: rgba(192, 21, 47, 1); + --color-success: rgba(33, 128, 141, 1); + --color-warning: rgba(168, 75, 47, 1); + --color-info: rgba(98, 108, 113, 1); + --color-focus-ring: rgba(33, 128, 141, 0.4); + --color-select-caret: rgba(19, 52, 59, 0.8); + + /* Common style patterns */ + --focus-ring: 0 0 0 3px var(--color-focus-ring); + --focus-outline: 2px solid var(--color-primary); + --status-bg-opacity: 0.15; + --status-border-opacity: 0.25; + --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + + /* RGB versions for opacity control */ + --color-success-rgb: 33, 128, 141; + --color-error-rgb: 192, 21, 47; + --color-warning-rgb: 168, 75, 47; + --color-info-rgb: 98, 108, 113; + + /* Typography */ + --font-family-base: "FKGroteskNeue", "Geist", "Inter", -apple-system, + BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + --font-family-mono: "Berkeley Mono", ui-monospace, SFMono-Regular, Menlo, + Monaco, Consolas, monospace; + --font-size-xs: 11px; + --font-size-sm: 12px; + --font-size-base: 14px; + --font-size-md: 14px; + --font-size-lg: 16px; + --font-size-xl: 18px; + --font-size-2xl: 20px; + --font-size-3xl: 24px; + --font-size-4xl: 30px; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 550; + --font-weight-bold: 600; + --line-height-tight: 1.2; + --line-height-normal: 1.5; + --letter-spacing-tight: -0.01em; + + /* Spacing */ + --space-0: 0; + --space-1: 1px; + --space-2: 2px; + --space-4: 4px; + --space-6: 6px; + --space-8: 8px; + --space-10: 10px; + --space-12: 12px; + --space-16: 16px; + --space-20: 20px; + --space-24: 24px; + --space-32: 32px; + + /* Border Radius */ + --radius-sm: 6px; + --radius-base: 8px; + --radius-md: 10px; + --radius-lg: 12px; + --radius-full: 9999px; + + /* Shadows */ + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.02); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.04), + 0 2px 4px -1px rgba(0, 0, 0, 0.02); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.04), + 0 4px 6px -2px rgba(0, 0, 0, 0.02); + --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.15), + inset 0 -1px 0 rgba(0, 0, 0, 0.03); + + /* Animation */ + --duration-fast: 150ms; + --duration-normal: 250ms; + --ease-standard: cubic-bezier(0.16, 1, 0.3, 1); + + /* Layout */ + --container-sm: 640px; + --container-md: 768px; + --container-lg: 1024px; + --container-xl: 1280px; +} + +/* Dark mode colors */ +@media (prefers-color-scheme: dark) { + :root { + --color-background: rgba(31, 33, 33, 1); + --color-surface: rgba(38, 40, 40, 1); + --color-text: rgba(245, 245, 245, 1); + --color-text-secondary: rgba(167, 169, 169, 0.7); + --color-primary: rgba(50, 184, 198, 1); + --color-primary-hover: rgba(45, 166, 178, 1); + --color-primary-active: rgba(41, 150, 161, 1); + --color-secondary: rgba(119, 124, 124, 0.15); + --color-secondary-hover: rgba(119, 124, 124, 0.25); + --color-secondary-active: rgba(119, 124, 124, 0.3); + --color-border: rgba(119, 124, 124, 0.3); + --color-error: rgba(255, 84, 89, 1); + --color-success: rgba(50, 184, 198, 1); + --color-warning: rgba(230, 129, 97, 1); + --color-info: rgba(167, 169, 169, 1); + --color-focus-ring: rgba(50, 184, 198, 0.4); + --color-btn-primary-text: rgba(19, 52, 59, 1); + --color-card-border: rgba(119, 124, 124, 0.2); + --color-card-border-inner: rgba(119, 124, 124, 0.15); + --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1), + inset 0 -1px 0 rgba(0, 0, 0, 0.15); + --button-border-secondary: rgba(119, 124, 124, 0.2); + --color-border-secondary: rgba(119, 124, 124, 0.2); + --color-select-caret: rgba(245, 245, 245, 0.8); + + /* Common style patterns - updated for dark mode */ + --focus-ring: 0 0 0 3px var(--color-focus-ring); + --focus-outline: 2px solid var(--color-primary); + --status-bg-opacity: 0.15; + --status-border-opacity: 0.25; + --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + + /* RGB versions for dark mode */ + --color-success-rgb: 50, 184, 198; + --color-error-rgb: 255, 84, 89; + --color-warning-rgb: 230, 129, 97; + --color-info-rgb: 167, 169, 169; + } +} + +/* Data attribute for manual theme switching */ +[data-color-scheme="dark"] { + --color-background: rgba(31, 33, 33, 1); + --color-surface: rgba(38, 40, 40, 1); + --color-text: rgba(245, 245, 245, 1); + --color-text-secondary: rgba(167, 169, 169, 0.7); + --color-primary: rgba(50, 184, 198, 1); + --color-primary-hover: rgba(45, 166, 178, 1); + --color-primary-active: rgba(41, 150, 161, 1); + --color-secondary: rgba(119, 124, 124, 0.15); + --color-secondary-hover: rgba(119, 124, 124, 0.25); + --color-secondary-active: rgba(119, 124, 124, 0.3); + --color-border: rgba(119, 124, 124, 0.3); + --color-error: rgba(255, 84, 89, 1); + --color-success: rgba(50, 184, 198, 1); + --color-warning: rgba(230, 129, 97, 1); + --color-info: rgba(167, 169, 169, 1); + --color-focus-ring: rgba(50, 184, 198, 0.4); + --color-btn-primary-text: rgba(19, 52, 59, 1); + --color-card-border: rgba(119, 124, 124, 0.15); + --color-card-border-inner: rgba(119, 124, 124, 0.15); + --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1), + inset 0 -1px 0 rgba(0, 0, 0, 0.15); + --color-border-secondary: rgba(119, 124, 124, 0.2); + --color-select-caret: rgba(245, 245, 245, 0.8); + + /* Common style patterns - updated for dark mode */ + --focus-ring: 0 0 0 3px var(--color-focus-ring); + --focus-outline: 2px solid var(--color-primary); + --status-bg-opacity: 0.15; + --status-border-opacity: 0.25; + --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); + + /* RGB versions for dark mode */ + --color-success-rgb: 50, 184, 198; + --color-error-rgb: 255, 84, 89; + --color-warning-rgb: 230, 129, 97; + --color-info-rgb: 167, 169, 169; +} + +[data-color-scheme="light"] { + --color-background: rgba(252, 252, 249, 1); + --color-surface: rgba(255, 255, 253, 1); + --color-text: rgba(19, 52, 59, 1); + --color-text-secondary: rgba(98, 108, 113, 1); + --color-primary: rgba(33, 128, 141, 1); + --color-primary-hover: rgba(29, 116, 128, 1); + --color-primary-active: rgba(26, 104, 115, 1); + --color-secondary: rgba(94, 82, 64, 0.12); + --color-secondary-hover: rgba(94, 82, 64, 0.2); + --color-secondary-active: rgba(94, 82, 64, 0.25); + --color-border: rgba(94, 82, 64, 0.2); + --color-btn-primary-text: rgba(252, 252, 249, 1); + --color-card-border: rgba(94, 82, 64, 0.12); + --color-card-border-inner: rgba(94, 82, 64, 0.12); + --color-error: rgba(192, 21, 47, 1); + --color-success: rgba(33, 128, 141, 1); + --color-warning: rgba(168, 75, 47, 1); + --color-info: rgba(98, 108, 113, 1); + --color-focus-ring: rgba(33, 128, 141, 0.4); + + /* RGB versions for light mode */ + --color-success-rgb: 33, 128, 141; + --color-error-rgb: 192, 21, 47; + --color-warning-rgb: 168, 75, 47; + --color-info-rgb: 98, 108, 113; +} + +/* Base styles */ +html { + font-size: var(--font-size-base); + font-family: var(--font-family-base); + line-height: var(--line-height-normal); + color: var(--color-text); + background-color: var(--color-background); + -webkit-font-smoothing: antialiased; + box-sizing: border-box; +} + +body { + margin: 0; + padding: 0; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +/* Typography */ +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0; + font-weight: var(--font-weight-semibold); + line-height: var(--line-height-tight); + color: var(--color-text); + letter-spacing: var(--letter-spacing-tight); +} + +h1 { + font-size: var(--font-size-4xl); +} +h2 { + font-size: var(--font-size-3xl); +} +h3 { + font-size: var(--font-size-2xl); +} +h4 { + font-size: var(--font-size-xl); +} +h5 { + font-size: var(--font-size-lg); +} +h6 { + font-size: var(--font-size-md); +} + +p { + margin: 0 0 var(--space-16) 0; +} + +a { + color: var(--color-primary); + text-decoration: none; + transition: color var(--duration-fast) var(--ease-standard); +} + +a:hover { + color: var(--color-primary-hover); +} + +code, +pre { + font-family: var(--font-family-mono); + font-size: calc(var(--font-size-base) * 0.95); + background-color: var(--color-secondary); + border-radius: var(--radius-sm); +} + +code { + padding: var(--space-1) var(--space-4); +} + +pre { + padding: var(--space-16); + margin: var(--space-16) 0; + overflow: auto; + border: 1px solid var(--color-border); +} + +pre code { + background: none; + padding: 0; +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: var(--space-8) var(--space-16); + border-radius: var(--radius-base); + font-size: var(--font-size-base); + font-weight: 500; + line-height: 1.5; + cursor: pointer; + transition: all var(--duration-normal) var(--ease-standard); + border: none; + text-decoration: none; + position: relative; +} + +.btn:focus-visible { + outline: none; + box-shadow: var(--focus-ring); +} + +.btn--primary { + background: var(--color-primary); + color: var(--color-btn-primary-text); +} + +.btn--primary:hover { + background: var(--color-primary-hover); +} + +.btn--primary:active { + background: var(--color-primary-active); +} + +.btn--secondary { + background: var(--color-secondary); + color: var(--color-text); +} + +.btn--secondary:hover { + background: var(--color-secondary-hover); +} + +.btn--secondary:active { + background: var(--color-secondary-active); +} + +.btn--outline { + background: transparent; + border: 1px solid var(--color-border); + color: var(--color-text); +} + +.btn--outline:hover { + background: var(--color-secondary); +} + +.btn--sm { + padding: var(--space-4) var(--space-12); + font-size: var(--font-size-sm); + border-radius: var(--radius-sm); +} + +.btn--lg { + padding: var(--space-10) var(--space-20); + font-size: var(--font-size-lg); + border-radius: var(--radius-md); +} + +.btn--full-width { + width: 100%; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Form elements */ +.form-control { + display: block; + width: 100%; + padding: var(--space-8) var(--space-12); + font-size: var(--font-size-md); + line-height: 1.5; + color: var(--color-text); + background-color: var(--color-surface); + border: 1px solid var(--color-border); + border-radius: var(--radius-base); + transition: border-color var(--duration-fast) var(--ease-standard), + box-shadow var(--duration-fast) var(--ease-standard); +} + +textarea.form-control { + font-family: var(--font-family-base); + font-size: var(--font-size-base); +} + +select.form-control { + padding: var(--space-8) var(--space-12); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: var(--select-caret-light); + background-repeat: no-repeat; + background-position: right var(--space-12) center; + background-size: 16px; + padding-right: var(--space-32); +} + +/* Add a dark mode specific caret */ +@media (prefers-color-scheme: dark) { + select.form-control { + background-image: var(--select-caret-dark); + } +} + +/* Also handle data-color-scheme */ +[data-color-scheme="dark"] select.form-control { + background-image: var(--select-caret-dark); +} + +[data-color-scheme="light"] select.form-control { + background-image: var(--select-caret-light); +} + +.form-control:focus { + border-color: var(--color-primary); + outline: var(--focus-outline); +} + +.form-label { + display: block; + margin-bottom: var(--space-8); + font-weight: var(--font-weight-medium); + font-size: var(--font-size-sm); +} + +.form-group { + margin-bottom: var(--space-16); +} + +/* Card component */ +.card { + background-color: var(--color-surface); + border-radius: var(--radius-lg); + border: 1px solid var(--color-card-border); + box-shadow: var(--shadow-sm); + overflow: hidden; + transition: box-shadow var(--duration-normal) var(--ease-standard); +} + +.card:hover { + box-shadow: var(--shadow-md); +} + +.card__body { + padding: var(--space-16); +} + +.card__header, +.card__footer { + padding: var(--space-16); + border-bottom: 1px solid var(--color-card-border-inner); +} + +/* Status indicators - simplified with CSS variables */ +.status { + display: inline-flex; + align-items: center; + padding: var(--space-6) var(--space-12); + border-radius: var(--radius-full); + font-weight: var(--font-weight-medium); + font-size: var(--font-size-sm); +} + +.status--success { + background-color: rgba( + var(--color-success-rgb, 33, 128, 141), + var(--status-bg-opacity) + ); + color: var(--color-success); + border: 1px solid + rgba(var(--color-success-rgb, 33, 128, 141), var(--status-border-opacity)); +} + +.status--error { + background-color: rgba( + var(--color-error-rgb, 192, 21, 47), + var(--status-bg-opacity) + ); + color: var(--color-error); + border: 1px solid + rgba(var(--color-error-rgb, 192, 21, 47), var(--status-border-opacity)); +} + +.status--warning { + background-color: rgba( + var(--color-warning-rgb, 168, 75, 47), + var(--status-bg-opacity) + ); + color: var(--color-warning); + border: 1px solid + rgba(var(--color-warning-rgb, 168, 75, 47), var(--status-border-opacity)); +} + +.status--info { + background-color: rgba( + var(--color-info-rgb, 98, 108, 113), + var(--status-bg-opacity) + ); + color: var(--color-info); + border: 1px solid + rgba(var(--color-info-rgb, 98, 108, 113), var(--status-border-opacity)); +} + +/* Container layout */ +.container { + width: 100%; + margin-right: auto; + margin-left: auto; + padding-right: var(--space-16); + padding-left: var(--space-16); +} + +@media (min-width: 640px) { + .container { + max-width: var(--container-sm); + } +} +@media (min-width: 768px) { + .container { + max-width: var(--container-md); + } +} +@media (min-width: 1024px) { + .container { + max-width: var(--container-lg); + } +} +@media (min-width: 1280px) { + .container { + max-width: var(--container-xl); + } +} + +/* Utility classes */ +.flex { + display: flex; +} +.flex-col { + flex-direction: column; +} +.items-center { + align-items: center; +} +.justify-center { + justify-content: center; +} +.justify-between { + justify-content: space-between; +} +.gap-4 { + gap: var(--space-4); +} +.gap-8 { + gap: var(--space-8); +} +.gap-16 { + gap: var(--space-16); +} + +.m-0 { + margin: 0; +} +.mt-8 { + margin-top: var(--space-8); +} +.mb-8 { + margin-bottom: var(--space-8); +} +.mx-8 { + margin-left: var(--space-8); + margin-right: var(--space-8); +} +.my-8 { + margin-top: var(--space-8); + margin-bottom: var(--space-8); +} + +.p-0 { + padding: 0; +} +.py-8 { + padding-top: var(--space-8); + padding-bottom: var(--space-8); +} +.px-8 { + padding-left: var(--space-8); + padding-right: var(--space-8); +} +.py-16 { + padding-top: var(--space-16); + padding-bottom: var(--space-16); +} +.px-16 { + padding-left: var(--space-16); + padding-right: var(--space-16); +} + +.block { + display: block; +} +.hidden { + display: none; +} + +/* Accessibility */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +:focus-visible { + outline: var(--focus-outline); + outline-offset: 2px; +} + +/* Dark mode specifics */ +[data-color-scheme="dark"] .btn--outline { + border: 1px solid var(--color-border-secondary); +} + +@font-face { + font-family: 'FKGroteskNeue'; + src: url('https://www.perplexity.ai/fonts/FKGroteskNeue.woff2') + format('woff2'); +} + +/* Custom styles for the presentation */ + +/* Presentation container */ +.presentation-container { + position: relative; + width: 100vw; + height: 100vh; + overflow: hidden; + background-color: var(--color-background); + font-family: var(--font-family-base); +} + +/* Controls */ +.presentation-controls { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + gap: var(--space-16); + background-color: var(--color-surface); + padding: var(--space-8) var(--space-16); + border-radius: var(--radius-full); + box-shadow: var(--shadow-md); + z-index: 100; +} + +.slide-counter { + font-size: var(--font-size-md); + color: var(--color-text); + font-weight: var(--font-weight-medium); +} + +/* Progress bar */ +.progress-bar { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 4px; + background-color: var(--color-border); + z-index: 100; +} + +.progress-indicator { + height: 100%; + background-color: var(--color-primary); + width: 6.67%; /* Start at slide 1 of 15 */ + transition: width 0.3s ease; +} + +/* Slides container */ +.slides-container { + width: 100%; + height: 100%; + position: relative; +} + +/* Individual slides */ +.slide { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: none; + flex-direction: column; + justify-content: center; + align-items: center; + padding: var(--space-32); + opacity: 0; + transition: opacity 0.5s ease; + overflow-y: auto; + box-sizing: border-box; +} + +.slide.active { + display: flex; + opacity: 1; +} + +.slide-content { + max-width: 1200px; + width: 100%; + margin: 0 auto; + padding: var(--space-32); + background-color: var(--color-surface); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + position: relative; + min-height: 60vh; + box-sizing: border-box; +} + +/* Title slide specific styles */ +.slide-title { + background: linear-gradient(135deg, var(--color-primary), var(--color-success)); + color: var(--color-btn-primary-text); + position: relative; + overflow: hidden; +} + +.slide-title .slide-content { + background-color: transparent; + box-shadow: none; + color: var(--color-btn-primary-text); + text-align: center; +} + +.slide h2.slide-title { + font-size: var(--font-size-3xl); + color: var(--color-text); + margin-bottom: var(--space-24); + text-align: center; + font-weight: var(--font-weight-semibold); + line-height: 1.2; +} + +/* Main title slide */ +.main-title { + font-size: 48px; + margin-bottom: var(--space-16); + color: var(--color-btn-primary-text); + text-align: center; + font-weight: var(--font-weight-bold); +} + +.subtitle { + font-size: var(--font-size-xl); + color: rgba(255, 255, 255, 0.9); + margin-bottom: var(--space-24); + text-align: center; + font-weight: var(--font-weight-medium); +} + +.key-highlights { + display: flex; + justify-content: center; + margin-bottom: var(--space-32); + width: 100%; +} + +.highlight-container { + display: flex; + gap: var(--space-16); + justify-content: center; + width: 100%; + flex-wrap: wrap; +} + +.highlight { + background-color: rgba(255, 255, 255, 0.2); + color: var(--color-btn-primary-text); + padding: var(--space-12) var(--space-20); + border-radius: var(--radius-base); + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-lg); + text-align: center; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.company-info { + margin-top: var(--space-32); + text-align: center; + font-size: var(--font-size-lg); + color: rgba(255, 255, 255, 0.8); +} + +.background-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0.15; + z-index: -1; + border-radius: var(--radius-lg); +} + +/* Bullet points */ +.bullet-points { + font-size: var(--font-size-lg); + line-height: 1.8; + padding-left: var(--space-24); + margin-bottom: var(--space-16); + list-style-type: disc; +} + +.bullet-points li { + margin-bottom: var(--space-12); + color: var(--color-text); +} + +.bullet-points.highlighted li { + background-color: rgba(var(--color-success-rgb), 0.1); + padding: var(--space-8) var(--space-16); + border-left: 4px solid var(--color-success); + list-style-type: none; + margin-left: calc(-1 * var(--space-24)); + border-radius: var(--radius-sm); +} + +/* Two-column layout */ +.two-column-layout { + display: flex; + gap: var(--space-32); + margin-top: var(--space-24); +} + +.column { + flex: 1; +} + +.column-title { + font-size: var(--font-size-xl); + margin-bottom: var(--space-16); + color: var(--color-text); + font-weight: var(--font-weight-semibold); +} + +/* Metrics container */ +.metrics-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-24); + margin-top: var(--space-24); +} + +.metric-category { + background-color: var(--color-background); + padding: var(--space-20); + border-radius: var(--radius-base); + border: 1px solid var(--color-card-border); + box-shadow: var(--shadow-sm); +} + +.metric-title { + font-size: var(--font-size-xl); + margin-bottom: var(--space-16); + padding-bottom: var(--space-8); + border-bottom: 3px solid var(--color-primary); + color: var(--color-text); + font-weight: var(--font-weight-semibold); +} + +.metric-title.operational { + border-color: #1FB8CD; +} + +.metric-title.business { + border-color: #FFC185; +} + +.metric-title.technical { + border-color: #B4413C; +} + +/* Solution description */ +.solution-description p { + font-size: var(--font-size-lg); + line-height: 1.8; + margin-bottom: var(--space-24); + text-align: justify; + color: var(--color-text); +} + +/* Value propositions */ +.value-props h3 { + text-align: center; + margin-bottom: var(--space-20); + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); +} + +.value-prop-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-20); +} + +.value-prop { + text-align: center; + padding: var(--space-20); + background-color: rgba(var(--color-success-rgb), 0.05); + border-radius: var(--radius-base); + border: 1px solid rgba(var(--color-success-rgb), 0.2); +} + +.value-icon { + font-size: 36px; + margin-bottom: var(--space-12); +} + +.value-prop h4 { + font-size: var(--font-size-lg); + margin-bottom: var(--space-8); + font-weight: var(--font-weight-semibold); +} + +.value-prop p { + font-size: var(--font-size-md); + color: var(--color-text-secondary); + line-height: 1.5; +} + +/* Flow diagram */ +.flow-diagram { + display: flex; + flex-direction: column; + gap: var(--space-16); + margin-bottom: var(--space-24); +} + +.flow-step { + display: flex; + align-items: center; + gap: var(--space-20); + padding: var(--space-16); + background-color: var(--color-background); + border-radius: var(--radius-base); + border-left: 4px solid var(--color-primary); + box-shadow: var(--shadow-sm); +} + +.step-number { + width: 50px; + height: 50px; + border-radius: 50%; + background-color: var(--color-primary); + color: var(--color-btn-primary-text); + display: flex; + justify-content: center; + align-items: center; + font-weight: var(--font-weight-bold); + font-size: var(--font-size-xl); + flex-shrink: 0; +} + +.step-content h4 { + margin-bottom: var(--space-8); + font-size: var(--font-size-lg); + font-weight: var(--font-weight-semibold); +} + +.step-content p { + color: var(--color-text-secondary); + font-size: var(--font-size-md); + line-height: 1.5; +} + +/* Images */ +.slide-image-container { + margin-top: var(--space-24); + text-align: center; +} + +.slide-image, .flow-image, .architecture-image, .tech-diagram, .market-image { + max-width: 100%; + height: auto; + border-radius: var(--radius-base); + max-height: 300px; + object-fit: cover; + box-shadow: var(--shadow-sm); +} + +/* Architecture diagram */ +.architecture-diagram { + text-align: center; + margin-bottom: var(--space-24); +} + +.architecture-details h3 { + text-align: center; + margin-bottom: var(--space-16); + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); +} + +.states-container { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + gap: var(--space-8); + margin-top: var(--space-16); +} + +.state { + padding: var(--space-8) var(--space-16); + background-color: var(--color-primary); + color: var(--color-btn-primary-text); + border-radius: var(--radius-base); + font-size: var(--font-size-md); + font-weight: var(--font-weight-medium); +} + +.state-arrow { + font-size: var(--font-size-xl); + color: var(--color-text-secondary); + margin: 0 var(--space-4); +} + +/* Feature matrix */ +.capability-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: var(--space-24); + margin-bottom: var(--space-24); +} + +.capability-section h3 { + font-size: var(--font-size-xl); + margin-bottom: var(--space-16); + color: var(--color-text); + border-bottom: 2px solid var(--color-border); + padding-bottom: var(--space-8); + font-weight: var(--font-weight-semibold); +} + +.feature-matrix h3 { + font-size: var(--font-size-xl); + margin-bottom: var(--space-16); + text-align: center; + font-weight: var(--font-weight-semibold); +} + +.feature-difficulty { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-16); +} + +.difficulty-container { + padding: var(--space-16); + border-radius: var(--radius-base); + border: 1px solid var(--color-border); + background-color: var(--color-surface); +} + +.difficulty-level { + margin-bottom: var(--space-16); + font-size: var(--font-size-lg); + text-align: center; + padding: var(--space-8) var(--space-12); + border-radius: var(--radius-base); + font-weight: var(--font-weight-semibold); +} + +.difficulty-level.green { + background-color: rgba(33, 128, 141, 0.1); + color: #1FB8CD; + border: 1px solid rgba(33, 128, 141, 0.3); +} + +.difficulty-level.yellow { + background-color: rgba(210, 186, 76, 0.1); + color: #D2BA4C; + border: 1px solid rgba(210, 186, 76, 0.3); +} + +.difficulty-level.red { + background-color: rgba(180, 65, 60, 0.1); + color: #B4413C; + border: 1px solid rgba(180, 65, 60, 0.3); +} + +/* Tech stack */ +.tech-stack-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-20); + margin-bottom: var(--space-24); +} + +.tech-component { + text-align: center; + padding: var(--space-20); + border: 1px solid var(--color-border); + border-radius: var(--radius-base); + background-color: var(--color-background); + box-shadow: var(--shadow-sm); +} + +.tech-icon { + font-size: 36px; + margin-bottom: var(--space-12); +} + +.tech-component h3 { + font-size: var(--font-size-lg); + margin-bottom: var(--space-8); + font-weight: var(--font-weight-semibold); +} + +.tech-component p { + font-size: var(--font-size-md); + color: var(--color-text-secondary); + line-height: 1.4; +} + +/* Phase details */ +.phase-details { + display: flex; + flex-direction: column; + gap: var(--space-24); +} + +.phase-content { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-16); + margin-bottom: var(--space-16); +} + +.phase-stat { + text-align: center; + padding: var(--space-16); + background-color: var(--color-background); + border-radius: var(--radius-base); + border: 1px solid var(--color-border); +} + +.phase-stat h3 { + font-size: var(--font-size-xl); + margin-bottom: var(--space-8); + color: var(--color-primary); + font-weight: var(--font-weight-semibold); +} + +.phase-stat p { + font-size: var(--font-size-lg); + color: var(--color-text); +} + +/* Continue with remaining styles... */ +.phase-framework h3 { + text-align: center; + margin-bottom: var(--space-16); + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); +} + +.framework-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-16); +} + +.framework-item { + padding: var(--space-16); + border-radius: var(--radius-base); + background-color: var(--color-secondary); + border-left: 4px solid var(--color-primary); +} + +.framework-item h4 { + margin-bottom: var(--space-8); + font-size: var(--font-size-lg); + font-weight: var(--font-weight-semibold); +} + +.framework-item p { + font-size: var(--font-size-md); + color: var(--color-text-secondary); + line-height: 1.5; +} + +/* Phases container */ +.phases-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: var(--space-24); + margin-bottom: var(--space-24); +} + +.phase-card { + padding: var(--space-20); + border-radius: var(--radius-base); + background-color: var(--color-background); + border: 1px solid var(--color-card-border); + box-shadow: var(--shadow-sm); +} + +.phase-title { + font-size: var(--font-size-xl); + margin-bottom: var(--space-8); + color: var(--color-text); + font-weight: var(--font-weight-semibold); +} + +.phase-title.phase-2 { + color: #1FB8CD; +} + +.phase-title.phase-3 { + color: #FFC185; +} + +.phase-timeline { + font-size: var(--font-size-md); + color: var(--color-text-secondary); + margin-bottom: var(--space-16); + font-weight: var(--font-weight-medium); +} + +/* ROI metrics */ +.roi-metrics { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-16); + margin-bottom: var(--space-32); +} + +.roi-metric { + text-align: center; + padding: var(--space-20); + background-color: var(--color-background); + border-radius: var(--radius-base); + border: 1px solid var(--color-border); +} + +.roi-metric h3 { + font-size: 48px; + color: var(--color-primary); + margin-bottom: var(--space-8); + font-weight: var(--font-weight-bold); +} + +.roi-metric p { + font-size: var(--font-size-lg); + color: var(--color-text); + line-height: 1.4; +} + +.market-metrics h3 { + text-align: center; + margin-bottom: var(--space-16); + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); +} + +.market-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: var(--space-16); +} + +.market-stat { + text-align: center; + padding: var(--space-16); + background-color: var(--color-background); + border-radius: var(--radius-base); + border: 1px solid var(--color-border); +} + +.stat-value { + font-size: var(--font-size-2xl); + font-weight: var(--font-weight-bold); + color: var(--color-text); + margin-bottom: var(--space-4); +} + +.stat-label { + font-size: var(--font-size-md); + color: var(--color-text-secondary); + line-height: 1.4; +} + +/* Challenges */ +.challenges-container { + display: flex; + flex-direction: column; + gap: var(--space-24); +} + +.challenge-category { + padding: var(--space-20); + border-radius: var(--radius-base); + background-color: var(--color-background); + border: 1px solid var(--color-card-border); +} + +.category-title { + font-size: var(--font-size-xl); + margin-bottom: var(--space-16); + padding-bottom: var(--space-8); + border-bottom: 3px solid; + font-weight: var(--font-weight-semibold); +} + +.category-title.technical { + border-color: #1FB8CD; + color: #1FB8CD; +} + +.category-title.business { + border-color: #FFC185; + color: #FFC185; +} + +.category-title.operational { + border-color: #B4413C; + color: #B4413C; +} + +.challenge-items { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: var(--space-16); +} + +.challenge-item { + padding: var(--space-16); + background-color: var(--color-secondary); + border-radius: var(--radius-base); + border: 1px solid var(--color-border); +} + +.challenge-item h4 { + margin-bottom: var(--space-8); + font-size: var(--font-size-md); + font-weight: var(--font-weight-semibold); +} + +.challenge-item p { + font-size: var(--font-size-sm); + color: var(--color-text-secondary); + line-height: 1.4; +} + +/* Market position */ +.market-content { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: var(--space-24); + margin-bottom: var(--space-24); +} + +.advantages-section { + grid-column: span 2; +} + +.market-section h3, .advantages-section h3 { + font-size: var(--font-size-xl); + margin-bottom: var(--space-16); + padding-bottom: var(--space-8); + border-bottom: 2px solid var(--color-border); + font-weight: var(--font-weight-semibold); +} + +.advantages-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--space-16); +} + +.advantage { + padding: var(--space-16); + background-color: rgba(31, 184, 205, 0.05); + border-radius: var(--radius-base); + border-left: 4px solid #1FB8CD; + border: 1px solid rgba(31, 184, 205, 0.2); +} + +.advantage h4 { + margin-bottom: var(--space-8); + font-size: var(--font-size-lg); + font-weight: var(--font-weight-semibold); +} + +.advantage p { + font-size: var(--font-size-md); + color: var(--color-text-secondary); + line-height: 1.4; +} + +/* Conclusion */ +.conclusion-content { + display: flex; + flex-direction: column; + gap: var(--space-24); +} + +.recommendations h3, .timeline-section h3 { + margin-bottom: var(--space-16); + font-size: var(--font-size-xl); + font-weight: var(--font-weight-semibold); +} + +.timeline { + display: flex; + flex-direction: column; + gap: var(--space-12); +} + +.timeline-item { + display: flex; + gap: var(--space-16); + padding: var(--space-12) 0; + border-left: 3px solid var(--color-primary); + padding-left: var(--space-16); +} + +.timeline-date { + font-weight: var(--font-weight-bold); + min-width: 120px; + color: var(--color-primary); +} + +.call-to-action { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-16); + margin-top: var(--space-24); + padding: var(--space-24); + background-color: var(--color-background); + border-radius: var(--radius-base); + border: 1px solid var(--color-border); +} + +.call-to-action h3 { + margin-bottom: var(--space-8); + font-size: var(--font-size-xl); + text-align: center; + font-weight: var(--font-weight-semibold); +} + +.call-to-action .btn { + min-width: 300px; + margin-bottom: var(--space-8); +} + +/* Media queries for responsiveness */ +@media (max-width: 768px) { + .slide { + padding: var(--space-16); + } + + .slide-content { + padding: var(--space-20); + min-height: auto; + } + + .main-title { + font-size: 36px; + } + + .bullet-points { + font-size: var(--font-size-md); + padding-left: var(--space-16); + } + + .two-column-layout, .market-content { + grid-template-columns: 1fr; + } + + .advantages-section { + grid-column: span 1; + } + + .highlight-container { + flex-direction: column; + align-items: center; + } + + .presentation-controls { + bottom: 10px; + gap: var(--space-8); + padding: var(--space-6) var(--space-12); + } + + .states-container { + flex-direction: column; + align-items: center; + } + + .state-arrow { + transform: rotate(90deg); + } +} + +/* Fullscreen mode */ +.fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: var(--color-background); + z-index: 1000; + overflow: hidden; +} + +/* Animation classes */ +.animate-in { + animation: slideInFromRight 0.5s ease forwards; +} + +@keyframes slideInFromRight { + from { + opacity: 0; + transform: translateX(30px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Button disabled state */ +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; +} \ No newline at end of file diff --git a/__pycache__/agent.cpython-312.pyc b/__pycache__/agent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..128af6c2d21057d59949ed7d57e88e349142f1c0 Binary files /dev/null and b/__pycache__/agent.cpython-312.pyc differ diff --git a/__pycache__/sticky.cpython-312.pyc b/__pycache__/sticky.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..801afc19bc8b0f5d165a5f58727a684dcc3938d9 Binary files /dev/null and b/__pycache__/sticky.cpython-312.pyc differ diff --git a/__pycache__/sticky.cpython-313.pyc b/__pycache__/sticky.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4a8fc1b735d8efaeb1edc415677d50571fab7aa8 Binary files /dev/null and b/__pycache__/sticky.cpython-313.pyc differ diff --git a/__pycache__/tools.cpython-312.pyc b/__pycache__/tools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5090f34554ed8743f84e9d744b8dbd9e5b3b5a81 Binary files /dev/null and b/__pycache__/tools.cpython-312.pyc differ diff --git a/__pycache__/tools.cpython-313.pyc b/__pycache__/tools.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e9a4a84dcca6291a38661def909807031b9d2ff Binary files /dev/null and b/__pycache__/tools.cpython-313.pyc differ diff --git a/__pycache__/ui_utils.cpython-312.pyc b/__pycache__/ui_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ac61103ec841fe87259cdd32de1b77d2bbc50ae Binary files /dev/null and b/__pycache__/ui_utils.cpython-312.pyc differ diff --git a/__pycache__/var.cpython-313.pyc b/__pycache__/var.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e33e15731d5209b70244098fd31dcefc4667d07 Binary files /dev/null and b/__pycache__/var.cpython-313.pyc differ diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..ba1df21d703fbe30c2ce9e3cc0ff7acdc673c852 --- /dev/null +++ b/app.py @@ -0,0 +1,500 @@ +import streamlit as st +from openai import OpenAI +import sqlite3 +import pandas as pd +import re +import json +from sticky import sticky_container +import chromadb +from sentence_transformers import SentenceTransformer +from transformers import pipeline +import hashlib +import inspect +from tools import * +from var import SCHEMA_DESCRIPTIONS, SchemaVectorDB, FullVectorDB +import os +from dotenv import load_dotenv +load_dotenv() + +# Set your Groq API key +GROQ_API_KEY = os.environ.get("GROQ_API_KEY") + +# Initialize Groq's OpenAI-compatible client +client = OpenAI( + api_key=GROQ_API_KEY, + base_url="https://api.groq.com/openai/v1" +) + +# --- Load prompt templates from prompts folder --- +with open("prompts/determine_intent.txt", "r", encoding="utf-8") as f: + determine_intent_prompt = f.read() + +with open("prompts/generate_reservation_conversation.txt", "r", encoding="utf-8") as f: + generate_reservation_conversation_prompt = f.read() + +with open("prompts/interpret_sql_result.txt", "r", encoding="utf-8") as f: + interpret_sql_result_prompt = f.read() + +with open("prompts/schema_prompt.txt", "r", encoding="utf-8") as f: + schema_prompt = f.read() + +with open("prompts/store_user_info.txt", "r", encoding="utf-8") as f: + store_user_info_prompt = f.read() + + + +st.set_page_config(page_title="FoodieSpot Assistant", layout="wide") + + +# --- Initialize State --- +if 'chat_history' not in st.session_state: + st.session_state.chat_history = [] + +if 'user_data' not in st.session_state: + st.session_state.user_data = { + "restaurant_name": None, + "user_name": None, + "contact": None, + "party_size": None, + "time": None + } +if 'vector_db' not in st.session_state: + st.session_state.vector_db = SchemaVectorDB() +vector_db = st.session_state.vector_db +if 'full_vector_db' not in st.session_state: + st.session_state.full_vector_db = FullVectorDB() +# Track last assistant reply for context +if 'last_assistant_reply' not in st.session_state: + st.session_state.last_assistant_reply = "" +# Fixed container at top for title + reservation +reservation_box = sticky_container(mode="top", border=False,z=999) + +with reservation_box: + st.text("") + st.text("") + st.title("๐Ÿฝ๏ธ FoodieSpot Assistant") + cols = st.columns([3, 3, 3, 2, 2, 1]) + + with cols[0]: + restaurant_name = st.text_input( + "Restaurant Name", + value=st.session_state.user_data.get("restaurant_name") or "", + key="restaurant_name_input" + ) + if restaurant_name!="": + st.session_state.user_data["restaurant_name"] = restaurant_name + + with cols[1]: + user_name = st.text_input( + "Your Name", + value=st.session_state.user_data.get("user_name") or "", + key="user_name_input" + ) + if user_name!="": + st.session_state.user_data["user_name"] = user_name + + with cols[2]: + contact = st.text_input( + "Contact", + value=st.session_state.user_data.get("contact") or "", + key="contact_input" + ) + if contact!="": + st.session_state.user_data["contact"] = contact + + with cols[3]: + party_size = st.number_input( + "Party Size", + value=st.session_state.user_data.get("party_size") or 0, + key="party_size_input" + ) + if party_size!=0: + st.session_state.user_data["party_size"] = party_size + + with cols[4]: + time = st.number_input( + "Time(24hr form, 9-20, 8 ~ null)", + min_value=8, + max_value=20, + value=st.session_state.user_data.get("time") or 8, + key="time_input" + ) + if time!=8: + st.session_state.user_data["time"] = time + # Place the BOOK button in the last column + with cols[5]: + st.text("") + st.text("") + book_clicked = st.button("BOOK", type="primary") + # Add a green BOOK button (primary style) + # book_clicked = st.button("BOOK", type="primary") + + if book_clicked: + # Check if all required fields are filled + required_keys = ["restaurant_name", "user_name", "contact", "party_size", "time"] + if all(st.session_state.user_data.get(k) not in [None, "", 0, 8] for k in required_keys): + booking_conn = None + try: + user_data = st.session_state.user_data + party_size = int(user_data["party_size"]) + tables_needed = -(-party_size // 4) + + booking_conn = sqlite3.connect("db/restaurant_reservation.db") + booking_cursor = booking_conn.cursor() + + booking_cursor.execute("SELECT id FROM restaurants WHERE LOWER(name) = LOWER(?)", (user_data["restaurant_name"],)) + restaurant_row = booking_cursor.fetchone() + if not restaurant_row: + raise Exception("Restaurant not found.") + restaurant_id = restaurant_row[0] + + booking_cursor.execute(""" + SELECT t.id AS table_id, s.id AS slot_id + FROM tables t + JOIN slots s ON t.id = s.table_id + WHERE t.restaurant_id = ? + AND s.hour = ? + AND s.date = '2025-05-12' + AND s.is_reserved = 0 + LIMIT ? + """, (restaurant_id, user_data["time"], tables_needed)) + available = booking_cursor.fetchall() + + if len(available) < tables_needed: + raise Exception("Not enough available tables.") + + booking_cursor.execute(""" + INSERT INTO reservations (restaurant_id, user_name, contact, date, time, party_size) + VALUES (?, ?, ?, '2025-05-12', ?, ?) + """, (restaurant_id, user_data["user_name"], user_data["contact"], user_data["time"], party_size)) + reservation_id = booking_cursor.lastrowid + + for table_id, _ in available: + booking_cursor.execute("INSERT INTO reservation_tables (reservation_id, table_id) VALUES (?, ?)", (reservation_id, table_id)) + + slot_ids = [slot_id for _, slot_id in available] + booking_cursor.executemany("UPDATE slots SET is_reserved = 1 WHERE id = ?", [(sid,) for sid in slot_ids]) + + booking_conn.commit() + + booking_cursor.execute("SELECT name FROM restaurants WHERE id = ?", (restaurant_id,)) + restaurant_name = booking_cursor.fetchone()[0] + + confirmation_msg = ( + f"โœ… Booking processed successfully!\n\n" + f"๐Ÿ“ Restaurant: **{restaurant_name}**\n" + f"โฐ Time: **{user_data['time']} on 2025-05-12**\n" + f"๐Ÿฝ๏ธ Tables Booked: **{tables_needed}**\n" + f"๐Ÿ†” Reservation ID: **{reservation_id}**\n\n" + f"๐Ÿ‘‰ Please mention this Reservation ID at the restaurant reception when you arrive." + ) + + st.success(confirmation_msg) + st.session_state.chat_history.append({'role': 'assistant', 'message': confirmation_msg}) + st.session_state.user_data["restaurant_name"] = None + st.session_state.user_data["party_size"] = None + st.session_state.user_data["time"] = None + st.session_state.last_assistant_reply = "" + except Exception as e: + if booking_conn: + booking_conn.rollback() + st.error(f"โŒ Booking failed: {e}") + finally: + if booking_conn: + booking_cursor = None + booking_conn.close() + else: + st.warning("โš ๏ธ Missing user information. Please provide all booking details first.") + st.text("") + # Inject custom CSS for smaller font and tighter layout + st.markdown(""" + + """, unsafe_allow_html=True) + + with st.container(): + col1, col2, col3 = st.columns(3) + + with col1: + with st.expander("๐Ÿฝ๏ธ Restaurants"): + st.markdown(""" + - Bella Italia + - Spice Symphony + - Tokyo Ramen House + - Saffron Grill + - El Toro Loco + - Noodle Bar + - Le Petit Bistro + - Tandoori Nights + - Green Leaf Cafe + - Ocean Pearl + - Mama Mia Pizza + - The Dumpling Den + - Bangkok Express + - Curry Kingdom + - The Garden Table + - Skyline Dine + - Pasta Republic + - Street Tacos Co + - Miso Hungry + - Chez Marie + """) + + with col2: + with st.expander("๐ŸŒŽ Cuisines"): + st.markdown(""" + - Italian + - French + - Chinese + - Japanese + - Indian + - Mexican + - Thai + - Healthy + - Fusion + """) + + with col3: + with st.expander("โœจ Special Features"): + st.markdown(""" + - Pet-Friendly + - Live Music + - Rooftop View + - Outdoor Seating + - Private Dining + """) + + + + +# --- Display previous chat history (before new input) --- + +for msg in st.session_state.chat_history: + # Check if both 'role' and 'message' are not None + if msg['role'] is not None and msg['message'] is not None: + with st.chat_message(msg['role']): + st.markdown(msg['message']) + +user_input = st.chat_input("Ask something about restaurants or reservations(eg. Tell me some best rated Italian cuisine restaurants)...") +if user_input: + # Show user message instantly + with st.chat_message("user"): + st.markdown(user_input) + st.session_state.chat_history.append({'role': 'user', 'message': user_input}) + + # Prepare conversation context + history_prompt = st.session_state.last_assistant_reply + + # Store possible user info + user_info = store_user_info(user_input,history_prompt,store_user_info_prompt,client) + if user_info: + st.session_state.user_data.update(user_info) + # st.rerun() + + # Detect intent + intent = determine_intent(user_input,determine_intent_prompt,client) + # st.write(intent) + if intent == "RUBBISH": + # Display user data for confirmation instead of invoking LLM + with st.chat_message("assistant"): + st.markdown("โŒ Sorry, I didn't understand that. Could you rephrase your request?") + st.session_state.chat_history.append({ + 'role': 'assistant', + 'message': "โŒ Sorry, I didn't understand that. Could you rephrase your request?" + }) + + st.stop() + + # Generate assistant reply + required_keys = ["restaurant_name", "user_name", "contact", "party_size", "time"] + user_data_complete = all( + k in st.session_state.user_data and st.session_state.user_data[k] not in [None, "", "NULL"] + for k in required_keys +) + + + if user_data_complete and intent != "BOOK": + + # Format user data as a Markdown bullet list + user_details = "\n".join([f"- **{key.capitalize()}**: {value}" for key, value in st.session_state.user_data.items()]) + + with st.chat_message("assistant"): + st.markdown("โœ… I have all the details needed for your reservation:") + st.markdown(user_details) + st.markdown("If everything looks good, please type **`book`** to confirm the reservation.") + + st.session_state.chat_history.append({ + 'role': 'assistant', + 'message': f"โœ… I have all the details needed for your reservation:\n{user_details}\nPlease type **`book`** to confirm." + }) + st.session_state.last_assistant_reply = "I have all the reservation details. Waiting for confirmation..." + st.rerun() + st.stop() + + + + + response_summary = None + + if intent == "SELECT": + response_summary=handle_query(user_input, st.session_state.full_vector_db, client) + + # First try semantic search + semantic_results = {} + + # Search across all collections + restaurant_results = st.session_state.full_vector_db.semantic_search(user_input, "restaurants") + table_results = st.session_state.full_vector_db.semantic_search(user_input, "tables") + slot_results = st.session_state.full_vector_db.semantic_search(user_input, "slots") + + if not is_large_output_request(user_input) and any([restaurant_results, table_results, slot_results]): + semantic_results = { + "restaurants": restaurant_results, + "tables": table_results, + "slots": slot_results + } + # Format semantic results + summary = [] + for category, items in semantic_results.items(): + if items: + summary.append(f"Found {len(items)} relevant {category}:") + summary.extend([f"- {item['name']}" if 'name' in item else f"- {item}" + for item in items[:3]]) + st.write("### Semantic Search used") + response_summary = "\n".join(summary) + else: + # Fall back to SQL generation for large or exact output requests + sql = generate_sql_query_v2(user_input,SCHEMA_DESCRIPTIONS, history_prompt, vector_db, client) + result = execute_query(sql) + response_summary = interpret_result_v2(result, user_input, sql) + + + + # sql = generate_sql_query_v2(user_input,history_prompt, vector_db, client) + # result = execute_query(sql) + # response_summary=interpret_result_v2(result, user_input, sql) + # if isinstance(result, pd.DataFrame): + # response_summary = interpret_sql_result(user_input, sql_query, result) + + + elif intent == "BOOK": + required_keys = ["restaurant_name", "user_name", "contact", "party_size", "time"] + if all(st.session_state.user_data.get(k) is not None for k in required_keys): + booking_conn = None + try: + user_data = st.session_state.user_data + party_size = int(user_data["party_size"]) + tables_needed = -(-party_size // 4) + + booking_conn = sqlite3.connect("db/restaurant_reservation.db") + booking_cursor = booking_conn.cursor() + + booking_cursor.execute("SELECT id FROM restaurants WHERE LOWER(name) = LOWER(?)", (user_data["restaurant_name"],)) + restaurant_row = booking_cursor.fetchone() + if not restaurant_row: + raise Exception("Restaurant not found.") + restaurant_id = restaurant_row[0] + + booking_cursor.execute(""" + SELECT t.id AS table_id, s.id AS slot_id + FROM tables t + JOIN slots s ON t.id = s.table_id + WHERE t.restaurant_id = ? + AND s.hour = ? + AND s.date = '2025-05-12' + AND s.is_reserved = 0 + LIMIT ? + """, (restaurant_id, user_data["time"], tables_needed)) + available = booking_cursor.fetchall() + # Debugging output + + if len(available) < tables_needed: + raise Exception("Not enough available tables.") + + booking_cursor.execute(""" + INSERT INTO reservations (restaurant_id, user_name, contact, date, time, party_size) + VALUES (?, ?, ?, '2025-05-12', ?, ?) + """, (restaurant_id, user_data["user_name"], user_data["contact"], user_data["time"], party_size)) + reservation_id = booking_cursor.lastrowid + + for table_id, _ in available: + booking_cursor.execute("INSERT INTO reservation_tables (reservation_id, table_id) VALUES (?, ?)", (reservation_id, table_id)) + + slot_ids = [slot_id for _, slot_id in available] + booking_cursor.executemany("UPDATE slots SET is_reserved = 1 WHERE id = ?", [(sid,) for sid in slot_ids]) + + booking_conn.commit() + # Fetch the restaurant name to confirm + booking_cursor.execute("SELECT name FROM restaurants WHERE id = ?", (restaurant_id,)) + restaurant_name = booking_cursor.fetchone()[0] + + # Prepare confirmation details + confirmation_msg = ( + f"โœ… Booking processed successfully!\n\n" + f"๐Ÿ“ Restaurant: **{restaurant_name}**\n" + f"โฐ Time: **{user_data['time']} on 2025-05-12**\n" + f"๐Ÿฝ๏ธ Tables Booked: **{tables_needed}**\n" + f"๐Ÿ†” Reservation ID: **{reservation_id}**\n\n" + f"๐Ÿ‘‰ Please mention this Reservation ID at the restaurant reception when you arrive." + ) + + response_summary = confirmation_msg + st.success(response_summary) + st.session_state.chat_history.append({'role': 'assistant', 'message': response_summary}) + response_summary="โœ… Booking processed successfully." + st.session_state.user_data["restaurant_name"]=None + st.session_state.user_data["party_size"]=None + st.session_state.user_data["time"]=None + st.session_state.last_assistant_reply="" + except Exception as e: + if booking_conn: + booking_conn.rollback() + response_summary = f"โŒ Booking failed: {e}" + st.error(response_summary) + finally: + if booking_conn: + booking_cursor=None + booking_conn.close() + else: + st.markdown("โš ๏ธ Missing user information. Please provide all booking details first.") + response_summary = "โš ๏ธ Missing user information. Please provide all booking details first." + + + elif intent == "GREET": + response_summary = "๐Ÿ‘‹ Hello! How can I help you with your restaurant reservation today?" + + elif intent == "RUBBISH": + response_summary = "โŒ Sorry, I didn't understand that. Could you rephrase your request?" + + # Generate assistant reply + if response_summary!="โœ… Booking processed successfully.": + follow_up = generate_reservation_conversation( + user_input, + history_prompt, + response_summary or "Info stored.", + json.dumps(st.session_state.user_data),generate_reservation_conversation_prompt,client + ) + else: + follow_up="Thanks for booking with FoodieSpot restaurant chain, I could assist you in new booking, also I could tell about restaurant features, pricing, etc... " + + # Show assistant reply instantly + with st.chat_message("assistant"): + st.markdown(follow_up) + + st.session_state.chat_history.append({'role': 'assistant', 'message': follow_up}) + # Update it after assistant speaks + st.session_state.last_assistant_reply = follow_up + st.rerun() + # Reset if booking done + + diff --git a/assets/all_resto.png b/assets/all_resto.png new file mode 100644 index 0000000000000000000000000000000000000000..bfd196a90e9986d02c6737fad3c19c4ee26a0982 --- /dev/null +++ b/assets/all_resto.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75439b4abe2272f6022a16cc3705287f423d5907409dca18c4bb329edd9fcf68 +size 176777 diff --git a/assets/booking_successful.png b/assets/booking_successful.png new file mode 100644 index 0000000000000000000000000000000000000000..46cea0c9b6615aad96a77c9285a9e750d72d8fb6 --- /dev/null +++ b/assets/booking_successful.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:405e43a9a4a6bdb24e0ad26bcd56a26e39ec022669bc5e87f0147d6dc6cbe12a +size 134921 diff --git a/assets/general_conv_info_through_chat.png b/assets/general_conv_info_through_chat.png new file mode 100644 index 0000000000000000000000000000000000000000..ba4e6668c0a1b55ba853a70fd659ce1218eefed7 --- /dev/null +++ b/assets/general_conv_info_through_chat.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:937cf76cd735fe0f39ab08b8e66cbb91ab522367e33520c4cae911516916da16 +size 152067 diff --git a/assets/greet_general_convo.png b/assets/greet_general_convo.png new file mode 100644 index 0000000000000000000000000000000000000000..c73d138f0cd28bc776e67445d868ea3d4e661f02 --- /dev/null +++ b/assets/greet_general_convo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e16d00f649f0d8e4ff797e50cb6422dba3802e3eb63d9c3a5144edaf185a0da3 +size 122190 diff --git a/assets/landing.png b/assets/landing.png new file mode 100644 index 0000000000000000000000000000000000000000..8b300664ca6655a68646bfa360e5de429612e405 --- /dev/null +++ b/assets/landing.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd03aaefe954c0527859cab2c8a74d2d5ea1c9dc18d4a502028807b08989bdf7 +size 103070 diff --git a/assets/mermaid-1.png b/assets/mermaid-1.png new file mode 100644 index 0000000000000000000000000000000000000000..1431cbdb1e2be48576eb1180b37f473872328740 --- /dev/null +++ b/assets/mermaid-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:372b67dd2b410f2537ab071bbe05b82c77ddb830ccffe54762276e358735a51a +size 599843 diff --git a/assets/mermaid.png b/assets/mermaid.png new file mode 100644 index 0000000000000000000000000000000000000000..81a72c66f293e1eac3678e1fb98cecb8e472ce19 --- /dev/null +++ b/assets/mermaid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d000eb1764401330a7cf99cfde781faf06f19cbab424bd39e83984576fee466e +size 562335 diff --git a/assets/name_entering.png b/assets/name_entering.png new file mode 100644 index 0000000000000000000000000000000000000000..64a326f91f32ec7e3b021c0f887252020c6f34e0 --- /dev/null +++ b/assets/name_entering.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78a06bd163dd9269f0ad572ced2ed6fb31a354b9e3d4920e08885fbb0ec65553 +size 123519 diff --git a/assets/ready_to_book.png b/assets/ready_to_book.png new file mode 100644 index 0000000000000000000000000000000000000000..6f76a2efc882150e1ddeace88b6bc67cccdc0d5e --- /dev/null +++ b/assets/ready_to_book.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c805bdcebf89e178565f037a24b97367743985bce7bb93e86e33dd0e99e215b +size 136732 diff --git a/assets/rubbish.PNG b/assets/rubbish.PNG new file mode 100644 index 0000000000000000000000000000000000000000..183b1870fd090dc7154fbb18702ba5cee20e3888 --- /dev/null +++ b/assets/rubbish.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:846c39c6265b506e8e20788333cd8bf0b7e73d52b86139cce92a58588cc31c75 +size 101894 diff --git a/assets/some_results.PNG b/assets/some_results.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9d6104bf8b589220402616bdf71b279523d24862 --- /dev/null +++ b/assets/some_results.PNG @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bfb3f7631528ff646eaeeba9cce0b56b612930575b145c3c34167a979a3f4f6 +size 146439 diff --git a/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/data_level0.bin b/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/data_level0.bin new file mode 100644 index 0000000000000000000000000000000000000000..0fee824df425bbd6a2ddf45894dfa0ad3633141e --- /dev/null +++ b/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/data_level0.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8146ecc3e4c3a36ea9b3edc3778630c452f483990ec942d38e8006f4661e430 +size 16760000 diff --git a/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/header.bin b/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/header.bin new file mode 100644 index 0000000000000000000000000000000000000000..51ca55818d180b505757e2a099351f24a94d6fbe --- /dev/null +++ b/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/header.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18f1e924efbb5e1af5201e3fbab86a97f5c195c311abe651eeec525884e5e449 +size 100 diff --git a/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/length.bin b/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/length.bin new file mode 100644 index 0000000000000000000000000000000000000000..9ef0bae91d943a32c7cee5cbb3fe254453890d3f --- /dev/null +++ b/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/length.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:861b50c40305a93f26d251878405410518604fff022602201f32d4a4f5139253 +size 40000 diff --git a/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/link_lists.bin b/db/chroma/0f1c557e-a6e2-45cb-8079-0720b4f2093f/link_lists.bin new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/data_level0.bin b/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/data_level0.bin new file mode 100644 index 0000000000000000000000000000000000000000..0fee824df425bbd6a2ddf45894dfa0ad3633141e --- /dev/null +++ b/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/data_level0.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8146ecc3e4c3a36ea9b3edc3778630c452f483990ec942d38e8006f4661e430 +size 16760000 diff --git a/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/header.bin b/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/header.bin new file mode 100644 index 0000000000000000000000000000000000000000..51ca55818d180b505757e2a099351f24a94d6fbe --- /dev/null +++ b/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/header.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18f1e924efbb5e1af5201e3fbab86a97f5c195c311abe651eeec525884e5e449 +size 100 diff --git a/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/length.bin b/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/length.bin new file mode 100644 index 0000000000000000000000000000000000000000..355e4ac4593a3ea3071df921bdc35261a26c38c9 --- /dev/null +++ b/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/length.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5b4759a12671b6d014df975fda1dd7ac0f7c71b4919c679a36bc9f76bc8aaf3 +size 40000 diff --git a/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/link_lists.bin b/db/chroma/45948c9b-58d5-4762-9fd1-cf9b5925ba84/link_lists.bin new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/data_level0.bin b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/data_level0.bin new file mode 100644 index 0000000000000000000000000000000000000000..0818f645c38e6153027d435500f17ba56236cf2f --- /dev/null +++ b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/data_level0.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cdd813c7b938a646786a77ee5e41520302ea0d68cda1b40a41e3e633c7165a8 +size 16760000 diff --git a/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/header.bin b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/header.bin new file mode 100644 index 0000000000000000000000000000000000000000..6133f1bcd2b8170ac01e26030ea16c562eef20b8 --- /dev/null +++ b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/header.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:834eaecd974231ba7c9bae46579a2e84cae7acd944d00827cddb0c3cbd1915e4 +size 100 diff --git a/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/index_metadata.pickle b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/index_metadata.pickle new file mode 100644 index 0000000000000000000000000000000000000000..758b8abc1696a7c10d4a362af9b3673fcf464bcc --- /dev/null +++ b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/index_metadata.pickle @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18ec9cba57199ceca85f8017e5eec2047d0538fbeb5024abc3c260ba5ecbedb8 +size 312140 diff --git a/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/length.bin b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/length.bin new file mode 100644 index 0000000000000000000000000000000000000000..e8b13b6c922123893ff6eefdebdea1aa730022b1 --- /dev/null +++ b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/length.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:231f5aecbcb787adc68fa2e2f8f9876e22cdca514de5ffc2a9091a4ce5d072ae +size 40000 diff --git a/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/link_lists.bin b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/link_lists.bin new file mode 100644 index 0000000000000000000000000000000000000000..a09e0d30ad5148517cd2617cbaad31c0badeb09d --- /dev/null +++ b/db/chroma/b97e1f95-8691-4375-b2f8-2f2e96d82a8c/link_lists.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34aa97ec388caf3881bf351c822de9a429f610fb3528d766660d616c1e14e88a +size 25396 diff --git a/db/chroma/chroma.sqlite3 b/db/chroma/chroma.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..eaa96ea8e25c02d1a084ab74852f70d2dae7ed4e --- /dev/null +++ b/db/chroma/chroma.sqlite3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94eef2082e840a627f17ecdbc0477cc7d9754ebf2663a2f1a039d09b777b8382 +size 5046272 diff --git a/db/create_base.py b/db/create_base.py new file mode 100644 index 0000000000000000000000000000000000000000..7535e3a9ff7df7de1d5d677e7d8592964e6bdfde --- /dev/null +++ b/db/create_base.py @@ -0,0 +1,64 @@ +import sqlite3 + +# Connect to the SQLite database +conn = sqlite3.connect("restaurant_reservation.db") +cursor = conn.cursor() + +# Create tables if they do not exist +cursor.executescript(""" +CREATE TABLE IF NOT EXISTS restaurants ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + cuisine TEXT, + location TEXT, + seating_capacity INTEGER, + rating REAL, + address TEXT, + contact TEXT, + price_range TEXT, + special_features TEXT +); + +CREATE TABLE IF NOT EXISTS tables ( + id TEXT PRIMARY KEY, + restaurant_id TEXT, + capacity INTEGER DEFAULT 4, + FOREIGN KEY (restaurant_id) REFERENCES restaurants(id) +); + +CREATE TABLE IF NOT EXISTS slots ( + id TEXT PRIMARY KEY, + table_id TEXT, + date TEXT, + hour INTEGER, + is_reserved INTEGER DEFAULT 0, + FOREIGN KEY (table_id) REFERENCES tables(id) +); + +CREATE TABLE IF NOT EXISTS reservations ( + id TEXT PRIMARY KEY, + restaurant_id TEXT, + user_name TEXT, + contact TEXT, + date TEXT, + time INTEGER, + party_size INTEGER, + FOREIGN KEY (restaurant_id) REFERENCES restaurants(id) +); + +CREATE TABLE IF NOT EXISTS reservation_tables ( + id TEXT PRIMARY KEY, + reservation_id TEXT, + table_id TEXT, + FOREIGN KEY (reservation_id) REFERENCES reservations(id), + FOREIGN KEY (table_id) REFERENCES tables(id) +); +""") + +# Commit the changes +conn.commit() + +# Close the connection +conn.close() + +print("Tables have been created successfully!") diff --git a/db/create_slots.py b/db/create_slots.py new file mode 100644 index 0000000000000000000000000000000000000000..0650bd46c406349223be33bca191773625d7df0c --- /dev/null +++ b/db/create_slots.py @@ -0,0 +1,33 @@ +import sqlite3 +import uuid +from datetime import datetime + +# Connect to your SQLite DB +conn = sqlite3.connect("restaurant_reservation.db") +cursor = conn.cursor() + +# Get all table IDs +cursor.execute("SELECT id FROM tables") +table_ids = [row[0] for row in cursor.fetchall()] + +# Define the time range and current date +start_hour = 9 # 9AM +end_hour = 21 # 9PM + +# Prepare slot entries +slot_entries = [] +for table_id in table_ids: + for hour in range(start_hour, end_hour): + slot_id = str(uuid.uuid4()) + slot_entries.append((slot_id, table_id, "2025-05-12", hour, 0)) # is_reserved = 0 + +# Insert into slots table +cursor.executemany(""" + INSERT INTO slots (id, table_id, date, hour, is_reserved) + VALUES (?, ?, ?, ?, ?) +""", slot_entries) + +conn.commit() +conn.close() + +print("โœ… Slots successfully added for all tables for today.") diff --git a/db/dbmodify.py b/db/dbmodify.py new file mode 100644 index 0000000000000000000000000000000000000000..f5bea3933b9fab072d52e90704c19aff5bf2f337 --- /dev/null +++ b/db/dbmodify.py @@ -0,0 +1,54 @@ +# import sqlite3 + +# conn = sqlite3.connect("restaurant_reservation.db") +# cursor = conn.cursor() + +# # Drop the existing empty tables +# cursor.execute("DROP TABLE IF EXISTS reservations;") +# cursor.execute("DROP TABLE IF EXISTS reservation_tables;") + +# # Recreate the tables with AUTOINCREMENT for `id` +# cursor.execute(""" +# CREATE TABLE reservations ( +# id INTEGER PRIMARY KEY AUTOINCREMENT, +# restaurant_id TEXT, +# user_name TEXT, +# contact TEXT, +# date TEXT, -- Hard coded to 2025-05-12 +# time TEXT, +# party_size INTEGER +# ); +# """) + +# cursor.execute(""" +# CREATE TABLE reservation_tables ( +# id INTEGER PRIMARY KEY AUTOINCREMENT, +# reservation_id TEXT, +# table_id TEXT +# ); +# """) + +# conn.commit() +# conn.close() + +# print("Tables recreated successfully with AUTOINCREMENT ids.") + +import sqlite3 + +conn = sqlite3.connect("restaurant_reservation.db") +cursor = conn.cursor() + +try: + cursor.execute(""" + UPDATE restaurants + SET name = 'Street Tacos Co' + WHERE name = 'Street Tacos Co.'; + """) + conn.commit() + print("โœ… Restaurant name updated successfully.") +except Exception as e: + conn.rollback() + print(f"โŒ Update failed: {e}") +finally: + conn.close() + diff --git a/db/fill_details.py b/db/fill_details.py new file mode 100644 index 0000000000000000000000000000000000000000..9e496e45b5274e823cbfc06bd4cdaa2f11752de6 --- /dev/null +++ b/db/fill_details.py @@ -0,0 +1,141 @@ +import uuid +import random +import sqlite3 + +# --------------------------- +# Data Classes +# --------------------------- +class Restaurant: + def __init__(self, restaurant_id, name, cuisine, location, seating_capacity, rating, address, contact, price_range, special_features): + self.restaurant_id = restaurant_id + self.name = name + self.cuisine = cuisine + self.location = location + self.seating_capacity = seating_capacity + self.rating = rating + self.address = address + self.contact = contact + self.price_range = price_range + self.special_features = special_features + self.tables = [] + +class Table: + def __init__(self, table_id, restaurant_id, capacity=4): + self.table_id = table_id + self.restaurant_id = restaurant_id + self.capacity = capacity + +# --------------------------- +# Sample Data +# --------------------------- +restaurant_names = [ + "Bella Italia", "Spice Symphony", "Tokyo Ramen House", "Saffron Grill", "El Toro Loco", + "Noodle Bar", "Le Petit Bistro", "Tandoori Nights", "Green Leaf Cafe", "Ocean Pearl", + "Mama Mia Pizza", "The Dumpling Den", "Bangkok Express", "Curry Kingdom", "The Garden Table", + "Skyline Dine", "Pasta Republic", "Street Tacos Co", "Miso Hungry", "Chez Marie" +] + +locations = ['Downtown', 'Uptown', 'Midtown', 'Suburbs'] +special_features_list = ['Outdoor Seating', 'Pet-Friendly', 'Live Music', 'Rooftop View', 'Private Dining'] + +def infer_cuisine(name): + name = name.lower() + if "italia" in name or "pasta" in name or "mama mia" in name: + return "Italian" + elif "tokyo" in name or "ramen" in name or "miso" in name: + return "Japanese" + elif "saffron" in name or "tandoori" in name or "curry" in name: + return "Indian" + elif "dumpling" in name or "noodle" in name: + return "Chinese" + elif "bistro" in name or "chez" in name or "marie" in name: + return "French" + elif "bangkok" in name: + return "Thai" + elif "el toro" in name or "tacos" in name: + return "Mexican" + elif "green" in name or "garden" in name: + return random.choice(["Multi-Cuisine", "Healthy", "Fusion"]) + elif "skyline" in name or "ocean" in name: + return random.choice(["Multi-Cuisine", "Seafood", "Fusion"]) + else: + return random.choice(["Italian", "Mexican", "Indian", "Japanese", "Chinese", "Thai", "French", "Multi-Cuisine"]) + +# Create restaurant objects +restaurants = [] + +for i in range(20): + rest_id = str(uuid.uuid4()) + name = restaurant_names[i] + cuisine = infer_cuisine(name) + if cuisine == "Multi-Cuisine": + cuisine = random.sample(["Italian", "Chinese", "Indian", "Mexican", "French"], k=2) + + location = random.choice(locations) + num_tables = random.randint(10, 20) + seating_capacity = num_tables * 4 + rating = round(random.uniform(3.5, 5.0), 1) + address = f"{100 + i} Main Street, {location}" + contact = f"555-{1000 + i}" + price_range = random.choice(['$', '$$', '$$$']) + features = random.sample(special_features_list, k=2) + + restaurant = Restaurant( + restaurant_id=rest_id, + name=name, + cuisine=cuisine, + location=location, + seating_capacity=seating_capacity, + rating=rating, + address=address, + contact=contact, + price_range=price_range, + special_features=features + ) + + for _ in range(num_tables): + table_id = str(uuid.uuid4()) + table = Table(table_id=table_id, restaurant_id=rest_id) + restaurant.tables.append(table) + + restaurants.append(restaurant) + +# --------------------------- +# Insert into SQLite Database +# --------------------------- +conn = sqlite3.connect("restaurant_reservation.db") +cursor = conn.cursor() + +for r in restaurants: + cuisine_str = ", ".join(r.cuisine) if isinstance(r.cuisine, list) else r.cuisine + features_str = ", ".join(r.special_features) + + cursor.execute(""" + INSERT INTO restaurants (id, name, cuisine, location, seating_capacity, rating, address, contact, price_range, special_features) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + r.restaurant_id, + r.name, + cuisine_str, + r.location, + r.seating_capacity, + r.rating, + r.address, + r.contact, + r.price_range, + features_str + )) + + for t in r.tables: + cursor.execute(""" + INSERT INTO tables (id, restaurant_id, capacity) + VALUES (?, ?, ?) + """, ( + t.table_id, + t.restaurant_id, + t.capacity + )) + +conn.commit() +conn.close() +print("โœ… Restaurants and tables successfully added to the database.") diff --git a/db/print_db.py b/db/print_db.py new file mode 100644 index 0000000000000000000000000000000000000000..89112dee8ab52cdeae3cf2d0112d4e41fa239492 --- /dev/null +++ b/db/print_db.py @@ -0,0 +1,24 @@ +import sqlite3 + +# Connect to the SQLite database +conn = sqlite3.connect("restaurant_reservation.db") +cursor = conn.cursor() + +# Function to print table contents +def print_table_contents(table_name): + print(f"Contents of the {table_name} table:") + cursor.execute(f"SELECT * FROM {table_name}") + rows = cursor.fetchall() + for row in rows: + print(row) + print("\n") + +# Print contents of all the tables +print_table_contents("restaurants") +print_table_contents("tables") +print_table_contents("slots") +print_table_contents("reservations") +print_table_contents("reservation_tables") + +# Close the database connection +conn.close() diff --git a/db/resetdb.py b/db/resetdb.py new file mode 100644 index 0000000000000000000000000000000000000000..a8120ea95214c7283cbd47d184d303992b8081f4 --- /dev/null +++ b/db/resetdb.py @@ -0,0 +1,27 @@ +import sqlite3 + +def reset_reservations(): + sql_statements = [ + "UPDATE slots SET is_reserved = 0;", + "DELETE FROM reservation_tables;", + "DELETE FROM reservations;" + ] + + try: + conn = sqlite3.connect("restaurant_reservation.db") + cursor = conn.cursor() + + cursor.execute("BEGIN TRANSACTION;") + for stmt in sql_statements: + cursor.execute(stmt) + conn.commit() + conn.close() + return "โœ… All slots marked as reserved and reservations cleared." + except Exception as e: + conn.rollback() + conn.close() + return f"โŒ Error during reset: {e}" + +# Call this function +result = reset_reservations() +print(result) diff --git a/db/restaurant_reservation.db b/db/restaurant_reservation.db new file mode 100644 index 0000000000000000000000000000000000000000..9907aacf95c830d8a924d1ef7bb5630c21c3d77a --- /dev/null +++ b/db/restaurant_reservation.db @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7663a4d218aae1e7d53ef8594b6d366506fe95a96f00f7a10e9e95df0416d883 +size 1273856 diff --git a/efficiency_log.txt b/efficiency_log.txt new file mode 100644 index 0000000000000000000000000000000000000000..d775764182af1bd9d3498311781d584ea4f5fe80 --- /dev/null +++ b/efficiency_log.txt @@ -0,0 +1,141 @@ +Function: store_user_info +Prompt tokens: 521 +Completion tokens: 26 +Total tokens: 547 +Prompt: You are a helpful assistant. Extract relevant user information from this user statement: +"Hi" + +Previously collected data in json: {"restaurant_name": null, "user_name": null, "contact": null, "party_size": null, "time": null} +Always remember this json data, you need to update this based on user statement. +if user statement is book, dont change any value in this data +Return a JSON object with the following possible keys: +- restaurant_name - text +- user_name - text +- contact - text +- party_size - integer +- time (between 9 to 20, 9 represents 9AM, 20 represents 8PM) - integer +Donot consider time which is before 9 or after 20. +Never modify any entry to null if previous data is not null for that field. +Update the previous data with any new fields found. Do not make previously known fields unless you are sure the user wants to change them. +Respond ONLY with a single valid JSON object. +important rules: +- "restaurant_name": Must always match from this list: + Bella Italia, Spice Symphony, Tokyo Ramen House, Saffron Grill, El Toro Loco, Noodle Bar, Le Petit Bistro, Tandoori Nights, Green Leaf Cafe, Ocean Pearl, Mama Mia Pizza, The Dumpling Den, Bangkok Express, Curry Kingdom, The Garden Table, Skyline Dine, Pasta Republic, Street Tacos Co, Miso Hungry, Chez Marie + + +If in previously collected data, the restaurant_name is there but not in this list as the exact spelling or not with correct casing, replace it with the correct one. + +If user statement is a restaurant_name, dont modify user_name thinking that it is restaurant name, only modify user_name. +- "user_name": + - Only extract if the input clearly states a name like โ€œMy name is ...โ€ or โ€œThis is ...โ€ + - Do not extract from greetings like โ€œHiโ€, โ€œHelloโ€, โ€œHeyโ€, โ€œYoโ€, โ€œGood eveningโ€ + - Do not invent names based on formatting or assumptions + +Output format rules: +-Make sure restaurant_name matches from the list given +- Return only valid JSON โ€” starting with { and ending with } +- All keys and values must be in double quotes +- Include all 5 keys in the output +- No markdown, comments, or explanation in output, just give a json +--- +Function: store_user_info +Prompt tokens: 521 +Completion tokens: 63 +Total tokens: 584 +Prompt: You are a helpful assistant. Extract relevant user information from this user statement: +"Hi" + +Previously collected data in json: {"restaurant_name": null, "user_name": null, "contact": null, "party_size": null, "time": null} +Always remember this json data, you need to update this based on user statement. +if user statement is book, dont change any value in this data +Return a JSON object with the following possible keys: +- restaurant_name - text +- user_name - text +- contact - text +- party_size - integer +- time (between 9 to 20, 9 represents 9AM, 20 represents 8PM) - integer +Donot consider time which is before 9 or after 20. +Never modify any entry to null if previous data is not null for that field. +Update the previous data with any new fields found. Do not make previously known fields unless you are sure the user wants to change them. +Respond ONLY with a single valid JSON object. +important rules: +- "restaurant_name": Must always match from this list: + Bella Italia, Spice Symphony, Tokyo Ramen House, Saffron Grill, El Toro Loco, Noodle Bar, Le Petit Bistro, Tandoori Nights, Green Leaf Cafe, Ocean Pearl, Mama Mia Pizza, The Dumpling Den, Bangkok Express, Curry Kingdom, The Garden Table, Skyline Dine, Pasta Republic, Street Tacos Co, Miso Hungry, Chez Marie + + +If in previously collected data, the restaurant_name is there but not in this list as the exact spelling or not with correct casing, replace it with the correct one. + +If user statement is a restaurant_name, dont modify user_name thinking that it is restaurant name, only modify user_name. +- "user_name": + - Only extract if the input clearly states a name like โ€œMy name is ...โ€ or โ€œThis is ...โ€ + - Do not extract from greetings like โ€œHiโ€, โ€œHelloโ€, โ€œHeyโ€, โ€œYoโ€, โ€œGood eveningโ€ + - Do not invent names based on formatting or assumptions + +Output format rules: +-Make sure restaurant_name matches from the list given +- Return only valid JSON โ€” starting with { and ending with } +- All keys and values must be in double quotes +- Include all 5 keys in the output +- No markdown, comments, or explanation in output, just give a json +--- +Function: determine_intent +Prompt tokens: 257 +Completion tokens: 3 +Total tokens: 260 +Prompt: You are an intent classification assistant for a restaurant reservation system. + +User input: "Hi" + +Classify the intent as one of: +- STORE: User shares name, contact, or reservation details (like party size or time) without asking anything. +- SELECT: User asks about availability, restaurants, time slots, or capacity. +- BOOK: User says only "book" (case-insensitive). Even "I want to book..." is SELECT, not BOOK. +- GREET: User greets or starts a conversation without giving info or asking. +- RUBBISH: Input is gibberish, irrelevant, or unrecognizable. + +Examples: +- "My name is Raj" โ†’ STORE +- "book" โ†’ BOOK +- "15 people" โ†’ SELECT +- "Tell me best restaurants" โ†’ SELECT +- "7801061333" โ†’ STORE +- "asdfgh" โ†’ RUBBISH +- "Hi there" โ†’ GREET + +Respond with ONE word only: SELECT, STORE, BOOK, GREET, or RUBBISH. No explanation +--- +Function: generate_reservation_conversation +Prompt tokens: 427 +Completion tokens: 50 +Total tokens: 477 +Prompt: You are a professional restaurant reservation assistant helping a customer make a booking. Speak concisely and professionally. Unless the booking is complete, end with a helpful question. + +User said: "Hi" +Always try to answer this user query. +Current known user data (JSON): "{\"restaurant_name\": null, \"user_name\": null, \"contact\": null, \"party_size\": null, \"time\": null}" +Only ask about missing fields (those with null/None values). Do not repeat questions for data already present. +Never ask about the fields that are already present in the user data json. +- user_name: user's name +- contact: userโ€™s phone (not for queries) +- restaurant_name: name of restaurant +- party_size: number of people +- time: hour of reservation (9โ€“20) + +If restaurant_name is missing, offer to suggest restaurants or cuisines. Never mention "null"โ€”be conversational. Show known info naturally if helpful. + +Database info: +"๐Ÿ‘‹ Hello! How can I help you with your restaurant reservation today?" +Explain this clearly based on what user said. If it says: +- "Info Stored": thank the user and ask next missing info. +- "โœ… Booking processed successfully.": Tell thanks for booking, I could assist you in new booking, also I could tell about restaurant features, pricing, etc, dont ask anything else. +- "โŒ Booking failed: ...": explain the error simply and suggest trying again. +- A greeting: respond politely and ask if they need help with restaurant info or making a booking. + +Personalize your response using available user data. Each table seats 4 people; use ceil(party_size / 4) to estimate how many are needed. +Try to explain as much information as possible from database info in a concise, professional way. + +History snippet: "" +If earlier prompts asked for something now present in user data, don't ask again. + +Be helpful, efficient, and professional in tone. +--- diff --git a/prompts/determine_intent.txt b/prompts/determine_intent.txt new file mode 100644 index 0000000000000000000000000000000000000000..1a0454df55dff530b83e14ba79672754d923e0a9 --- /dev/null +++ b/prompts/determine_intent.txt @@ -0,0 +1,21 @@ +You are an intent classification assistant for a restaurant reservation system. + +User input: "{user_input}" + +Classify the intent as one of: +- STORE: User shares name, contact, or reservation details (like party size or time) without asking anything. +- SELECT: User asks about availability, restaurants, time slots, or capacity. +- BOOK: User says only "book" (case-insensitive). Even "I want to book..." is SELECT, not BOOK. +- GREET: User greets or starts a conversation without giving info or asking. +- RUBBISH: Input is gibberish, irrelevant, or unrecognizable. + +Examples: +- "My name is Raj" โ†’ STORE +- "book" โ†’ BOOK +- "15 people" โ†’ SELECT +- "Tell me best restaurants" โ†’ SELECT +- "7801061333" โ†’ STORE +- "asdfgh" โ†’ RUBBISH +- "Hi there" โ†’ GREET + +Respond with ONE word only: SELECT, STORE, BOOK, GREET, or RUBBISH. No explanation \ No newline at end of file diff --git a/prompts/generate_reservation_conversation.txt b/prompts/generate_reservation_conversation.txt new file mode 100644 index 0000000000000000000000000000000000000000..351730a8ffbc4dbaef7d8db2a0ad72469786f019 --- /dev/null +++ b/prompts/generate_reservation_conversation.txt @@ -0,0 +1,30 @@ +You are a professional restaurant reservation assistant helping a customer make a booking. Speak concisely and professionally. Unless the booking is complete, end with a helpful question. + +User said: "{user_query}" +Always try to answer this user query. +Current known user data (JSON): {user_data} +Only ask about missing fields (those with null/None values). Do not repeat questions for data already present. +Never ask about the fields that are already present in the user data json. +- user_name: user's name +- contact: userโ€™s phone (not for queries) +- restaurant_name: name of restaurant +- party_size: number of people +- time: hour of reservation (9โ€“20) + +If restaurant_name is missing, offer to suggest restaurants or cuisines. Never mention "null"โ€”be conversational. Show known info naturally if helpful. + +Database info: +"{sql_summary}" +Explain this clearly based on what user said. If it says: +- "Info Stored": thank the user and ask next missing info. +- "โœ… Booking processed successfully.": Tell thanks for booking, I could assist you in new booking, also I could tell about restaurant features, pricing, etc, dont ask anything else. +- "โŒ Booking failed: ...": explain the error simply and suggest trying again. +- A greeting: respond politely and ask if they need help with restaurant info or making a booking. + +Personalize your response using available user data. Each table seats 4 people; use ceil(party_size / 4) to estimate how many are needed. +Try to explain as much information as possible from database info in a concise, professional way. + +History snippet: "{history_prompt_snippet}" +If earlier prompts asked for something now present in user data, don't ask again. + +Be helpful, efficient, and professional in tone. \ No newline at end of file diff --git a/prompts/interpret_sql_result.txt b/prompts/interpret_sql_result.txt new file mode 100644 index 0000000000000000000000000000000000000000..5b8dea848926c18ea4900d760fd98026e1b9b357 --- /dev/null +++ b/prompts/interpret_sql_result.txt @@ -0,0 +1,28 @@ +You are an expert assistant interpreting SQL query results for a restaurant reservation system. +Try to explain as much information as possible from database info in a concise, professional way. +Database schema overview: +- restaurants(id, name, cuisine, location, seating_capacity, rating, address, contact, price_range [$, $$, $$$], special_features) +- tables(id, restaurant_id, capacity=4) +- slots(id, table_id, date, hour [9-21], is_reserved [0=free,1=reserved]) + +Notes: +- Each table seats 4 guests. +- To accommodate a party, number_of_tables_needed = ceil(party_size / 4). +- Slots represent table availability by hour. +- The queries return counts or details based on user questions. + +You will get: +- User question: {user_query} +- Executed SQL query: {sql_query} +Understand this sql clearly and properly. +- Query result as JSON: {result_str} + +Instructions: +- Provide a clear, professional summary of the query result in context of the user's question and the sql query. +- For availability queries, explain if enough tables are free for the requested party size and time. +- For list queries, list relevant restaurant details clearly. +- If no data is found, say so politely. +- Do not ask follow-up questions or add info not supported by the data. + + +Now summarize the result based on the user query and data. \ No newline at end of file diff --git a/prompts/schema_prompt.txt b/prompts/schema_prompt.txt new file mode 100644 index 0000000000000000000000000000000000000000..bed71fada9bf691e2a5bed2001a21dfacbac7a82 --- /dev/null +++ b/prompts/schema_prompt.txt @@ -0,0 +1,50 @@ +You are an expert AI assistant for a restaurant reservation system using SQLite. +Your goal is to generate a single SELECT SQL query only. + +Use COUNT for availability checks to reduce result size, but when the query asks for restaurant info (name, rating, pricing, features), use regular SELECT without COUNT. + +SCHEMA +- restaurants(id, name, cuisine, location, seating_capacity, rating, address, contact, price_range, special_features) +- tables(id, restaurant_id, capacity = 4) +- slots(id, table_id, date, hour, is_reserved = 0) +strictly follow this schema + +LOGIC +- Each table seats 4 โ†’ use CEIL(party_size / 4) to get number of tables needed. +- Only consider slots where is_reserved = 0 and the date = '2025-05-12'. +- JOIN order: slots โ†’ tables โ†’ restaurants +- Use explicit column aliases to avoid ambiguity (e.g., s.id AS slot_id). +- Never reference internal id fields in user-facing outputs. +- Avoid SELECT * in multi-table joins. + +EXAMPLES +1. Availability: +SELECT COUNT(*) AS availability FROM slots WHERE is_reserved = 0 AND table_id IN (SELECT id FROM tables WHERE restaurant_id = (SELECT id FROM restaurants WHERE LOWER(name) = 'bella italia')); + +2. Availability at time: +SELECT COUNT(*) AS available_tables FROM slots WHERE hour = 10 AND is_reserved = 0 AND table_id IN (SELECT id FROM tables WHERE restaurant_id = (SELECT id FROM restaurants WHERE LOWER(name) = 'bella italia')); + +3. Availability for party size: +SELECT COUNT(*) AS available_tables FROM slots WHERE hour = 12 AND is_reserved = 0 AND table_id IN (SELECT id FROM tables WHERE restaurant_id = (SELECT id FROM restaurants WHERE LOWER(name) = 'bella italia')); +โ†’ compare count to CEIL(6 / 4) externally. + +4. Restaurant info: +SELECT price_range FROM restaurants WHERE LOWER(name) = 'bella italia'; +5. Best restaurants: +SELECT * FROM restaurants ORDER BY rating DESC +5. Best restaurant with Mexican cuisine: +SELECT * FROM restaurants WHERE cuisine LIKE '%Mexican%' ORDER BY rating DESC LIMIT 1; +6. Which cuisine has the best rating? : +SELECT cuisine, name AS restaurant_name, rating +FROM restaurants +ORDER BY rating DESC +LIMIT 1; +7. Total list: +"Give me a list of restaurants" +SELECT FROM restaurants +PRICING TERMS +- "cheap" โ†’ $ +- "moderate" โ†’ $$ +- "expensive" โ†’ $$$ + +History: {history_prompt}\n\nUser: {user_input}\nGive only SQL query as answer, SQL: \ No newline at end of file diff --git a/prompts/store_user_info.txt b/prompts/store_user_info.txt new file mode 100644 index 0000000000000000000000000000000000000000..874516d77d64210edb7091daff8db98b09299c61 --- /dev/null +++ b/prompts/store_user_info.txt @@ -0,0 +1,35 @@ +You are a helpful assistant. Extract relevant user information from this user statement: +"{user_input}" + +Previously collected data in json: {previous_info} +Always remember this json data, you need to update this based on user statement. +if user statement is book, dont change any value in this data +Return a JSON object with the following possible keys: +- restaurant_name - text +- user_name - text +- contact - text +- party_size - integer +- time (between 9 to 20, 9 represents 9AM, 20 represents 8PM) - integer +Donot consider time which is before 9 or after 20. +Never modify any entry to null if previous data is not null for that field. +Update the previous data with any new fields found. Do not make previously known fields unless you are sure the user wants to change them. +Respond ONLY with a single valid JSON object. +important rules: +- "restaurant_name": Must always match from this list: + Bella Italia, Spice Symphony, Tokyo Ramen House, Saffron Grill, El Toro Loco, Noodle Bar, Le Petit Bistro, Tandoori Nights, Green Leaf Cafe, Ocean Pearl, Mama Mia Pizza, The Dumpling Den, Bangkok Express, Curry Kingdom, The Garden Table, Skyline Dine, Pasta Republic, Street Tacos Co, Miso Hungry, Chez Marie + + +If in previously collected data, the restaurant_name is there but not in this list as the exact spelling or not with correct casing, replace it with the correct one. + +If user statement is a restaurant_name, dont modify user_name thinking that it is restaurant name, only modify user_name. +- "user_name": + - Only extract if the input clearly states a name like โ€œMy name is ...โ€ or โ€œThis is ...โ€ + - Do not extract from greetings like โ€œHiโ€, โ€œHelloโ€, โ€œHeyโ€, โ€œYoโ€, โ€œGood eveningโ€ + - Do not invent names based on formatting or assumptions + +Output format rules: +-Make sure restaurant_name matches from the list given +- Return only valid JSON โ€” starting with {{ and ending with }} +- All keys and values must be in double quotes +- Include all 5 keys in the output +- No markdown, comments, or explanation in output, just give a json \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 28d994e22f8dd432b51df193562052e315ad95f7..11b8014b581f49b6552a0129c097b6277f6b4b9f 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/restaurants.json b/restaurants.json new file mode 100644 index 0000000000000000000000000000000000000000..45fb7e6b1bf4849f3dfe1a1e0d2f0954c752a757 --- /dev/null +++ b/restaurants.json @@ -0,0 +1,182 @@ +[ + { + "id": "r001", + "name": "Spice Garden", + "location": "Downtown", + "cuisine": ["Indian"], + "capacity": 40, + "ambiance": "casual", + "available_times": ["18:00", "19:30", "21:00"] + }, + { + "id": "r002", + "name": "Bella Italia", + "location": "Midtown", + "cuisine": ["Italian"], + "capacity": 35, + "ambiance": "romantic", + "available_times": ["17:30", "19:00", "20:30"] + }, + { + "id": "r003", + "name": "Tokyo Bites", + "location": "Uptown", + "cuisine": ["Japanese"], + "capacity": 50, + "ambiance": "family", + "available_times": ["18:00", "19:30"] + }, + { + "id": "r004", + "name": "The Global Spoon", + "location": "Central", + "cuisine": ["Indian", "Mexican", "Chinese"], + "capacity": 60, + "ambiance": "casual", + "available_times": ["17:00", "18:30", "20:00"] + }, + { + "id": "r005", + "name": "Saffron Lounge", + "location": "Downtown", + "cuisine": ["Indian", "Persian"], + "capacity": 45, + "ambiance": "fine-dining", + "available_times": ["18:00", "19:30", "21:00"] + }, + { + "id": "r006", + "name": "La Vida", + "location": "Uptown", + "cuisine": ["Spanish"], + "capacity": 30, + "ambiance": "romantic", + "available_times": ["17:30", "19:00", "20:30"] + }, + { + "id": "r007", + "name": "Burger Craze", + "location": "Midtown", + "cuisine": ["American"], + "capacity": 25, + "ambiance": "casual", + "available_times": ["16:00", "17:00", "18:30"] + }, + { + "id": "r008", + "name": "Wok Express", + "location": "Chinatown", + "cuisine": ["Chinese"], + "capacity": 55, + "ambiance": "family", + "available_times": ["18:00", "19:30"] + }, + { + "id": "r009", + "name": "Taco Fiesta", + "location": "Southside", + "cuisine": ["Mexican"], + "capacity": 40, + "ambiance": "casual", + "available_times": ["17:00", "18:30", "20:00"] + }, + { + "id": "r010", + "name": "Green Fork", + "location": "Downtown", + "cuisine": ["Vegan", "Organic"], + "capacity": 28, + "ambiance": "minimalist", + "available_times": ["17:00", "18:30", "20:00"] + }, + { + "id": "r011", + "name": "The Royal Tandoor", + "location": "Eastside", + "cuisine": ["Indian"], + "capacity": 50, + "ambiance": "fine-dining", + "available_times": ["18:00", "19:30", "21:00"] + }, + { + "id": "r012", + "name": "Ocean Grill", + "location": "Seaside", + "cuisine": ["Seafood"], + "capacity": 60, + "ambiance": "coastal", + "available_times": ["17:30", "19:00", "20:30"] + }, + { + "id": "r013", + "name": "Le Petit Bistro", + "location": "Central", + "cuisine": ["French"], + "capacity": 26, + "ambiance": "romantic", + "available_times": ["18:00", "19:30"] + }, + { + "id": "r014", + "name": "Fusion Point", + "location": "Uptown", + "cuisine": ["Asian", "Italian"], + "capacity": 48, + "ambiance": "modern", + "available_times": ["17:00", "18:30", "20:00"] + }, + { + "id": "r015", + "name": "Himalayan Hearth", + "location": "Downtown", + "cuisine": ["Nepalese", "Tibetan"], + "capacity": 30, + "ambiance": "cozy", + "available_times": ["17:30", "19:00"] + }, + { + "id": "r016", + "name": "BBQ Pitstop", + "location": "Westend", + "cuisine": ["American", "Barbecue"], + "capacity": 52, + "ambiance": "rustic", + "available_times": ["18:00", "19:30", "21:00"] + }, + { + "id": "r017", + "name": "Curry & More", + "location": "Southside", + "cuisine": ["Indian", "Thai"], + "capacity": 40, + "ambiance": "family", + "available_times": ["17:00", "18:30", "20:00"] + }, + { + "id": "r018", + "name": "Dolce Vita", + "location": "Midtown", + "cuisine": ["Italian", "French"], + "capacity": 36, + "ambiance": "elegant", + "available_times": ["18:00", "19:30", "21:00"] + }, + { + "id": "r019", + "name": "K-Pot", + "location": "Chinatown", + "cuisine": ["Korean", "Hot Pot"], + "capacity": 42, + "ambiance": "lively", + "available_times": ["17:30", "19:00", "20:30"] + }, + { + "id": "r020", + "name": "Garden Table", + "location": "Central", + "cuisine": ["Farm-to-Table", "Vegetarian"], + "capacity": 38, + "ambiance": "natural", + "available_times": ["17:00", "18:30", "20:00"] + } +] diff --git a/sticky.py b/sticky.py new file mode 100644 index 0000000000000000000000000000000000000000..5ea99e9be67d16a94f7c009ffd38cc5f9cf4afee --- /dev/null +++ b/sticky.py @@ -0,0 +1,44 @@ +from typing import Literal + +import streamlit as st + +MARGINS = { + "top": "0", + "bottom": "0", +} + +STICKY_CONTAINER_HTML = """ + +
+""".strip() + +# Not to apply the same style to multiple containers +count = 0 + + +def sticky_container( + *, + height: int | None = None, + border: bool | None = None, + mode: Literal["top", "bottom"] = "top", + margin: str | None = None, + z:int |None=None +): + if margin is None: + margin = MARGINS[mode] + + global count + html_code = STICKY_CONTAINER_HTML.format(position=mode, margin=margin, i=count,z=z) + count += 1 + + container = st.container(height=height, border=border) + container.markdown(html_code, unsafe_allow_html=True) + return container + diff --git a/tools.py b/tools.py new file mode 100644 index 0000000000000000000000000000000000000000..f821ef1ca5858e2c3db17e048fabbded601fe6a0 --- /dev/null +++ b/tools.py @@ -0,0 +1,318 @@ +import sqlite3 +import inspect +import pandas as pd +import json +import re +import streamlit as st +def log_groq_token_usage(response, prompt=None, function_name=None, filename="efficiency_log.txt"): + usage = response.usage + log_message = ( + f"Function: {function_name or 'unknown'}\n" + f"Prompt tokens: {usage.prompt_tokens}\n" + f"Completion tokens: {usage.completion_tokens}\n" + f"Total tokens: {usage.total_tokens}\n" + f"Prompt: {prompt}\n" + "---\n" + ) + with open(filename, "a", encoding="utf-8") as f: # โ† THIS LINE + f.write(log_message) + +import pandas as pd +# --- Database Execution --- +def execute_transaction(sql_statements): + txn_conn = None + try: + txn_conn = sqlite3.connect("db/restaurant_reservation.db") + cursor = txn_conn.cursor() + for stmt in sql_statements: + cursor.execute(stmt) + txn_conn.commit() + return "โœ… Booking Executed" + except Exception as e: + if txn_conn: + txn_conn.rollback() + return f"โŒ Booking failed: {e}" + finally: + if txn_conn: + txn_conn.close() + + +def execute_query(sql_query, db_path="db/restaurant_reservation.db"): + conn = None + try: + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + cursor.execute(sql_query) + rows = cursor.fetchall() + columns = [desc[0] for desc in cursor.description] if cursor.description else [] + return pd.DataFrame(rows, columns=columns) + except Exception as e: + return f"โŒ Error executing query: {e}" + finally: + if conn: + conn.close() +def generate_sql_query_v2(user_input,SCHEMA_DESCRIPTIONS,history_prompt, vector_db, client, use_cache=False): + # Get relevant schema elements + relevant_tables = vector_db.get_relevant_schema(user_input) + schema_prompt = "\n".join([f"Table {table}:\n{SCHEMA_DESCRIPTIONS[table]}" for table in relevant_tables]) + # Cache check + cache_key = f"query:{user_input[:50]}" + if use_cache and (cached := cache.get(cache_key)): + return cached.decode() + # Generate SQL with Groq + prompt = f"""Based on these tables: +{schema_prompt} +Previous assistant reply: +{history_prompt} +Convert this request to SQL: {user_input} + +Only return the SQL query, nothing else.""" + response = client.chat.completions.create( + model="llama3-8b-8192", + messages=[ + {"role": "system", "content": "You are a helpful assistant that only returns SQL queries."}, + {"role": "user", "content": prompt} + ], + temperature=0.3, + max_tokens=200 + ) + log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name) + sql = response.choices[0].message.content.strip() + if use_cache: + cache.setex(cache_key, 3600, sql) + return sql +def interpret_result_v2(result, user_query, sql_query,client): + if isinstance(result, str): + return result + try: + # Compress to essential columns if possible + cols = [c for c in result.columns if c in ['name', 'cuisine', 'location', 'seating_capacity', 'rating', 'address', 'contact', 'price_range', 'special_features', 'capacity', 'date', 'hour']] + if cols: + compressed = result[cols] + else: + compressed = result + json_data = compressed.to_json(orient='records', indent=2) + # Summarize with Groq + prompt = f"""User query: {user_query} +SQL query: {sql_query} +Result data (JSON): {json_data} + +Summarize the results for the user.""" + response = client.chat.completions.create( + model="llama3-8b-8192", + messages=[ + {"role": "system", "content": "Summarize database query results for a restaurant reservation assistant."}, + {"role": "user", "content": prompt} + ], + temperature=0.3, + max_tokens=300 + ) + log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name) + return response.choices[0].message.content.strip() + except Exception as e: + return f"Error interpreting results: {e}" + +def handle_query(user_input, vector_db, client): + try: + # First try semantic search + semantic_results = {} + + # Search across all collections + restaurant_results = vector_db.semantic_search(user_input, "restaurants") + table_results = vector_db.semantic_search(user_input, "tables") + slot_results = vector_db.semantic_search(user_input, "slots") + + if any([restaurant_results, table_results, slot_results]): + semantic_results = { + "restaurants": restaurant_results, + "tables": table_results, + "slots": slot_results + } + + # Format semantic results + summary = [] + for category, items in semantic_results.items(): + if items: + summary.append(f"Found {len(items)} relevant {category}:") + summary.extend([f"- {item['name']}" if 'name' in item else f"- {item}" + for item in items[:3]]) + + return "\n".join(summary) + else: + # Fall back to SQL generation + sql = generate_sql_query_v2(user_input, vector_db, client) + result = execute_query(sql) + return interpret_result_v2(result, user_input, sql,client) + + except Exception as e: + return f"Error: {e}" + + +def is_large_output_request(query): + query = query.lower() + # List of single words and multi-word phrases (as lists) + triggers = [ + ['all'], ['every'], ['entire'], ['complete'], ['full'], ['each'], + ['list'], ['show'], ['display'], ['give', 'me'], ['get'], + ['every', 'single'], ['each', 'and', 'every'], + ['whole'], ['total'], ['collection'], ['set'], + ['no', 'filters'], ['without', 'filters'], + ['everything'], ['entirety'], + ['comprehensive'], ['exhaustive'], ['record'], + ['don\'t', 'filter'], ['without', 'limitations'] + ] + query_words = query.split() + for trigger in triggers: + if all(word in query_words for word in trigger): + return True + return False + + +def generate_reservation_conversation(user_query, history_prompt, sql_summary, user_data,generate_reservation_conversation_prompt,client): + words = history_prompt.split() if history_prompt else [] + if len(words) > 25: + history_prompt_snippet = " ".join(words[:15]) + " ... " + " ".join(words[-10:]) + else: + history_prompt_snippet = " ".join(words) + + # Serialize user_data as pretty JSON for readability in prompt + user_data_json = json.dumps(user_data, indent=2) + + prompt = generate_reservation_conversation_prompt.format( + user_query=user_query, + user_data=user_data_json, + sql_summary=sql_summary, + history_prompt_snippet=history_prompt_snippet + ) + + response = client.chat.completions.create( + model="llama3-8b-8192", + messages=[ + {"role": "system", "content": "You are a helpful restaurant reservation assistant."}, + {"role": "user", "content": prompt} + ], + temperature=0.4 + ) + + if not response.choices: + return "Sorry, I couldn't generate a response right now." + log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name) + + return response.choices[0].message.content.strip() + + +# --- Helper Functions --- + +def determine_intent(user_input,determine_intent_prompt,client): + prompt = determine_intent_prompt.format(user_input=user_input) + response = client.chat.completions.create( + model="llama3-8b-8192", + messages=[ + {"role": "system", "content": "Classify user intent into SELECT, STORE, BOOK, GREET, or RUBBISH based on message content."}, + {"role": "user", "content": prompt} + ], + temperature=0 + ) + log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name) + return response.choices[0].message.content.strip().upper() + + + +def store_user_info(user_input,history_prompt,store_user_info_prompt, client): + # words = history_prompt.split() + # if len(words) > 25: + # history_prompt_snippet = " ".join(words[:15]) + " ... " + " ".join(words[-10:]) + # else: + # history_prompt_snippet = " ".join(words) + previous_info = json.dumps(st.session_state.user_data) + # st.json(previous_info) + prompt = store_user_info_prompt.format(previous_info=previous_info,user_input=user_input) + response = client.chat.completions.create( + model="llama3-8b-8192", + messages=[{"role": "system", "content": "Extract or update user booking info in JSON."}, + {"role": "user", "content": prompt}], + temperature=0.3 + ) + log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name) + + try: + # Print raw LLM output for inspection + raw_output = response.choices[0].message.content + # st.subheader("๐Ÿง  Raw LLM Response") + # st.write(raw_output) + + # Extract JSON substring from anywhere in the response + json_match = re.search(r'{[\s\S]*?}', raw_output) + if not json_match: + return None + # raise ValueError("No JSON object found in response.") + + json_str = json_match.group() + + # Show the extracted JSON string + # st.subheader("๐Ÿ“ฆ Extracted JSON String") + # st.code(json_str, language="json") + + # Safely parse using json.loads + parsed = json.loads(json_str) + + # Display the parsed result + # st.subheader("โœ… Parsed JSON Object") + # st.json(parsed) + + return parsed + + except Exception as e: + st.error(f"โš ๏ธ Failed to parse JSON: {e}") + return {} + +def generate_sql_query(user_input,restaurant_name,party_size,time, history_prompt, schema_prompt, client): + words = history_prompt.split() + if len(words) > 25: + history_prompt_snippet = " ".join(words[:15]) + " ... " + " ".join(words[-10:]) + else: + history_prompt_snippet = " ".join(words) + prompt = schema_prompt.format( + history_prompt=history_prompt, + user_input=user_input + ) + + response = client.chat.completions.create( + model="llama3-8b-8192", + messages=[ + {"role": "system", "content": "You are a helpful assistant that only returns SQL queries."}, + {"role": "user", "content": prompt} + ], + temperature=0.3 + ) + log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name) + raw_sql = response.choices[0].message.content.strip() + extracted_sql = re.findall(r"(SELECT[\s\S]+?)(?:;|$)", raw_sql, re.IGNORECASE) + sql_query = extracted_sql[0].strip() + ";" if extracted_sql else raw_sql + + return sql_query + +def interpret_sql_result(user_query, sql_query, result,interpret_sql_result_prompt, client): + if isinstance(result, pd.DataFrame): + # Convert DataFrame to list of dicts + result_dict = result.to_dict(orient="records") + else: + # Fall back to raw string if not a DataFrame + result_dict = result + + prompt = interpret_sql_result_prompt.format( + user_query=user_query, + sql_query=sql_query, + result_str=json.dumps(result_dict, indent=2) # Pass as formatted JSON string + ) + + response = client.chat.completions.create( + model="llama3-8b-8192", + messages=[ + {"role": "system", "content": "You summarize database query results for a restaurant reservation assistant."}, + {"role": "user", "content": prompt} + ], + temperature=0.3 + ) + log_groq_token_usage(response,prompt, function_name=inspect.currentframe().f_code.co_name) + return response.choices[0].message.content.strip() diff --git a/ui_utils.py b/ui_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..027f84740c5252e95690d3a8c90c0045605369f8 --- /dev/null +++ b/ui_utils.py @@ -0,0 +1,150 @@ +from typing import Literal + +import streamlit as st +from streamlit.components.v1 import html + +""" +st_fixed_container consist of two parts - fixed container and opaque container. +Fixed container is a container that is fixed to the top or bottom of the screen. +When transparent is set to True, the container is typical `st.container`, which is transparent by default. +When transparent is set to False, the container is custom opaque_container, that updates its background color to match the background color of the app. +Opaque container is a helper class, but can be used to create more custom views. See main for examples. +""" +OPAQUE_CONTAINER_CSS = """ +:root {{ + --background-color: #ffffff; /* Default background color */ +}} +div[data-testid="stVerticalBlockBorderWrapper"]:has(div.opaque-container-{id}):not(:has(div.not-opaque-container)) div[data-testid="stVerticalBlock"]:has(div.opaque-container-{id}):not(:has(div.not-opaque-container)) > div[data-testid="stVerticalBlockBorderWrapper"] {{ + background-color: var(--background-color); + width: 100%; +}} +div[data-testid="stVerticalBlockBorderWrapper"]:has(div.opaque-container-{id}):not(:has(div.not-opaque-container)) div[data-testid="stVerticalBlock"]:has(div.opaque-container-{id}):not(:has(div.not-opaque-container)) > div[data-testid="element-container"] {{ + display: none; +}} +div[data-testid="stVerticalBlockBorderWrapper"]:has(div.not-opaque-container):not(:has(div[class^='opaque-container-'])) {{ + display: none; +}} +""".strip() + +OPAQUE_CONTAINER_JS = """ +const root = parent.document.querySelector('.stApp'); +let lastBackgroundColor = null; +function updateContainerBackground(currentBackground) { + parent.document.documentElement.style.setProperty('--background-color', currentBackground); + ; +} +function checkForBackgroundColorChange() { + const style = window.getComputedStyle(root); + const currentBackgroundColor = style.backgroundColor; + if (currentBackgroundColor !== lastBackgroundColor) { + lastBackgroundColor = currentBackgroundColor; // Update the last known value + updateContainerBackground(lastBackgroundColor); + } +} +const observerCallback = (mutationsList, observer) => { + for(let mutation of mutationsList) { + if (mutation.type === 'attributes' && (mutation.attributeName === 'class' || mutation.attributeName === 'style')) { + checkForBackgroundColorChange(); + } + } +}; +const main = () => { + checkForBackgroundColorChange(); + const observer = new MutationObserver(observerCallback); + observer.observe(root, { attributes: true, childList: false, subtree: false }); +} +// main(); +document.addEventListener("DOMContentLoaded", main); +""".strip() + + +def st_opaque_container( + *, + height: int | None = None, + border: bool | None = None, + key: str | None = None, +): + global opaque_counter + + opaque_container = st.container() + non_opaque_container = st.container() + css = OPAQUE_CONTAINER_CSS.format(id=key) + with opaque_container: + html(f"", scrolling=False, height=0) + st.markdown(f"", unsafe_allow_html=True) + st.markdown( + f"
", + unsafe_allow_html=True, + ) + with non_opaque_container: + st.markdown( + f"
", + unsafe_allow_html=True, + ) + + return opaque_container.container(height=height, border=border) + + +FIXED_CONTAINER_CSS = """ +div[data-testid="stVerticalBlockBorderWrapper"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)){{ + background-color: transparent; + position: {mode}; + width: inherit; + background-color: inherit; + {position}: {margin}; + z-index: 999; +}} +div[data-testid="stVerticalBlockBorderWrapper"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) div[data-testid="stVerticalBlock"]:has(div.fixed-container-{id}):not(:has(div.not-fixed-container)) > div[data-testid="element-container"] {{ + display: none; +}} +div[data-testid="stVerticalBlockBorderWrapper"]:has(div.not-fixed-container):not(:has(div[class^='fixed-container-'])) {{ + display: none; +}} +""".strip() + +MARGINS = { + "top": "0", + "bottom": "0", +} + + +def st_fixed_container( + *, + height: int | None = None, + border: bool | None = None, + mode: Literal["fixed", "sticky"] = "fixed", + position: Literal["top", "bottom"] = "top", + margin: str | None = None, + transparent: bool = False, + key: str | None = None, +): + if margin is None: + margin = MARGINS[position] + global fixed_counter + fixed_container = st.container() + non_fixed_container = st.container() + css = FIXED_CONTAINER_CSS.format( + mode=mode, + position=position, + margin=margin, + id=key, + ) + with fixed_container: + st.markdown(f"", unsafe_allow_html=True) + st.markdown( + f"
", + unsafe_allow_html=True, + ) + with non_fixed_container: + st.markdown( + f"
", + unsafe_allow_html=True, + ) + + with fixed_container: + if transparent: + return st.container(height=height, border=border) + + return st_opaque_container(height=height, border=border, key=f"opaque_{key}") + + diff --git a/var.py b/var.py new file mode 100644 index 0000000000000000000000000000000000000000..f613c4eb53ee379e3827f33589e29c5a0853e931 --- /dev/null +++ b/var.py @@ -0,0 +1,142 @@ +import chromadb +import sqlite3 +import hashlib +import pandas as pd +from sentence_transformers import SentenceTransformer +#--- Initialize ChromaDB and SentenceTransformer --- +SCHEMA_DESCRIPTIONS = { + "restaurants": """Table restaurants contains restaurant details: + - id: unique identifier + - name: restaurant name + - cuisine: type of cuisine + - location: area or neighborhood + - seating_capacity: total seats + - rating: average rating + - address: full address + - contact: phone or email + - price_range: price category + - special_features: amenities or highlights""", + "tables": """Table tables contains table details: + - id: unique identifier + - restaurant_id: links to restaurants.id + - capacity: number of seats (default 4)""", + "slots": """Table slots contains reservation time slots: + - id: unique identifier + - table_id: links to tables.id + - date: reservation date + - hour: reservation hour + - is_reserved: 0=available, 1=booked""" +} +class SchemaVectorDB: + def __init__(self): + self.client = chromadb.Client() + self.collection = self.client.get_or_create_collection("schema") + self.model = SentenceTransformer('all-MiniLM-L6-v2') + for idx, (name, desc) in enumerate(SCHEMA_DESCRIPTIONS.items()): + self.collection.add(ids=str(idx), documents=desc, metadatas={"name": name}) + + def get_relevant_schema(self, query, k=2): + query_embedding = self.model.encode(query).tolist() + results = self.collection.query(query_embeddings=[query_embedding], n_results=k) + # results['metadatas'] is a list of lists: [[{...}, {...}], ...] + # We only have one query, so grab the first list + metadatas = results['metadatas'][0] if results['metadatas'] else [] + return [m['name'] for m in metadatas if m and 'name' in m] + + + + + + +class FullVectorDB: + def __init__(self): + self.client = chromadb.PersistentClient(path="db/chroma") + self.model = SentenceTransformer('all-MiniLM-L6-v2') + + # Get existing collections or create if not exist + self.restaurants_col = self.client.get_or_create_collection("restaurants") + self.tables_col = self.client.get_or_create_collection("tables") + self.slots_col = self.client.get_or_create_collection("slots") + + # Initialize only if collections are empty + if len(self.restaurants_col.get()['ids']) == 0: + self._initialize_collections() + + def _row_to_text(self, row): + return ' '.join(str(v) for v in row.values if pd.notnull(v)) + + def _row_hash(self, row): + return hashlib.sha256(str(row.values).encode()).hexdigest() + + def _initialize_collections(self): + conn = sqlite3.connect("db/restaurant_reservation.db") + + # Create external changelog table + conn.execute(""" + CREATE TABLE IF NOT EXISTS chroma_changelog ( + id INTEGER PRIMARY KEY, + table_name TEXT, + record_id INTEGER, + content_hash TEXT, + UNIQUE(table_name, record_id) + ) + """) + conn.commit() + + # Process tables + self._process_table(conn, "restaurants", self.restaurants_col) + self._process_table(conn, "tables", self.tables_col) + self._process_table(conn, "slots", self.slots_col) + + conn.close() + + def _process_table(self, conn, table_name, collection): + # Get existing records from Chroma + existing_ids = set(collection.get()['ids']) + + # Get all records from SQLite with hash + df = pd.read_sql(f"SELECT * FROM {table_name}", conn) + + # Process each row + for _, row in df.iterrows(): + chroma_id = f"{table_name}_{row['id']}" + current_hash = self._row_hash(row) + + # Check if exists in changelog + changelog = pd.read_sql(f""" + SELECT content_hash + FROM chroma_changelog + WHERE table_name = ? AND record_id = ? + """, conn, params=(table_name, row['id'])) + + # Skip if hash matches + if not changelog.empty and changelog.iloc[0]['content_hash'] == current_hash: + continue + + # Generate embedding + embedding = self.model.encode(self._row_to_text(row)) + + # Update Chroma + collection.upsert( + ids=[chroma_id], + embeddings=[embedding.tolist()], + metadatas=[row.to_dict()] + ) + + # Update changelog + conn.execute(""" + INSERT OR REPLACE INTO chroma_changelog + (table_name, record_id, content_hash) + VALUES (?, ?, ?) + """, (table_name, row['id'], current_hash)) + conn.commit() + + def semantic_search(self, query, collection_name, k=5): + query_embedding = self.model.encode(query).tolist() + collection = getattr(self, f"{collection_name}_col") + results = collection.query( + query_embeddings=[query_embedding], + n_results=k, + include=["metadatas"] + ) + return results['metadatas'][0]