forked from sagnik/Project_Velocity
I have attached the screenshots of the native SwiftUI app. <img width="1705" alt="image.png" src="attachments/59fec2f3-0ae2-4b58-9349-457618ea0678"> <img width="1699" alt="image.png" src="attachments/0bf7c4f9-c883-4929-be36-774685b82fc4"> <img width="1698" alt="image.png" src="attachments/e3407e84-aaf2-45c0-9325-247d4020bace"> <img width="1694" alt="image.png" src="attachments/ee2cd47d-800d-4a40-855c-d54856680e79"> <img width="1694" alt="image.png" src="attachments/a2c902f1-9bc9-4427-8cae-b5801527c1ff"> Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: sagnik/Project_Velocity#2 Co-authored-by: sayan <sayan@desineuron.in> Co-committed-by: sayan <sayan@desineuron.in>
257 lines
10 KiB
Swift
257 lines
10 KiB
Swift
import SwiftUI
|
||
import Combine
|
||
|
||
// MARK: – Data Models
|
||
|
||
enum SentimentType: String, CaseIterable {
|
||
case excited, interested, neutral, confused, disinterested
|
||
var score: Int {
|
||
switch self {
|
||
case .excited: return 100
|
||
case .interested: return 80
|
||
case .neutral: return 50
|
||
case .confused: return 30
|
||
case .disinterested: return 10
|
||
}
|
||
}
|
||
var emoji: String {
|
||
switch self {
|
||
case .excited: return "😃"
|
||
case .interested: return "🤔"
|
||
case .neutral: return "😐"
|
||
case .confused: return "😕"
|
||
case .disinterested: return "😴"
|
||
}
|
||
}
|
||
var color: Color {
|
||
switch self {
|
||
case .excited: return VelocityTheme.success
|
||
case .interested: return VelocityTheme.accent
|
||
case .neutral: return VelocityTheme.mutedFg
|
||
case .confused: return VelocityTheme.warning
|
||
case .disinterested: return VelocityTheme.danger
|
||
}
|
||
}
|
||
}
|
||
|
||
struct Visitor: Identifiable {
|
||
let id: String
|
||
let faceId: String
|
||
var sentiment: SentimentType
|
||
var confidence: Double
|
||
var dwellTime: Int // seconds
|
||
var zone: String
|
||
let timestamp: Date
|
||
}
|
||
|
||
enum LeadSource: String {
|
||
case whatsapp = "WhatsApp"
|
||
case walkin = "Walk-in"
|
||
case website = "Website"
|
||
}
|
||
|
||
enum LeadStatus: String {
|
||
case hot = "Hot"
|
||
case engaged = "Engaged"
|
||
case new = "New"
|
||
case qualified = "Qualified"
|
||
case closed = "Closed"
|
||
|
||
var color: Color {
|
||
switch self {
|
||
case .hot: return VelocityTheme.danger
|
||
case .engaged: return VelocityTheme.accent
|
||
case .new: return VelocityTheme.mutedFg
|
||
case .qualified: return VelocityTheme.success
|
||
case .closed: return Color(red: 0.60, green: 0.57, blue: 0.99)
|
||
}
|
||
}
|
||
}
|
||
|
||
struct Lead: Identifiable {
|
||
let id: String
|
||
let name: String
|
||
let phone: String
|
||
let source: LeadSource
|
||
var status: LeadStatus
|
||
var lastMessage: String
|
||
var lastActive: Date
|
||
var unreadCount: Int
|
||
let qualification: String
|
||
let budget: String
|
||
let interest: String
|
||
var initials: String { String(name.split(separator: " ").prefix(2).compactMap(\.first)) }
|
||
}
|
||
|
||
struct ChatMessage: Identifiable {
|
||
let id: String
|
||
let sender: String // "user" | "oracle" | "ai"
|
||
let content: String
|
||
let timestamp: Date
|
||
}
|
||
|
||
struct SystemHealth {
|
||
var cpu: Double // 0–1
|
||
var gpu: Double
|
||
var memory: Double
|
||
}
|
||
|
||
struct DashboardMetrics {
|
||
var activeVisitors: Int
|
||
var revenue: String
|
||
var aiJobs: Int
|
||
var dailyVisitors: Int
|
||
var sentimentScore: Double // 0–100
|
||
var systemHealth: SystemHealth
|
||
}
|
||
|
||
// MARK: – Shared Store
|
||
|
||
@Observable
|
||
final class AppStore {
|
||
|
||
static let shared = AppStore()
|
||
private init() { startTimer() }
|
||
|
||
// ── Dashboard ─────────────────────────────────────────────────
|
||
var metrics = DashboardMetrics(
|
||
activeVisitors: 17,
|
||
revenue: "$3.2M",
|
||
aiJobs: 24,
|
||
dailyVisitors: 128,
|
||
sentimentScore: 78,
|
||
systemHealth: SystemHealth(cpu: 0.42, gpu: 0.61, memory: 0.55)
|
||
)
|
||
|
||
var dashboardMessages: [ChatMessage] = [
|
||
ChatMessage(id: "d0", sender: "ai",
|
||
content: "Hello, Ahmed. I've analysed the Q3 pipeline. Would you like a refined strategy for the Apex Innovations deal?",
|
||
timestamp: Date().addingTimeInterval(-300))
|
||
]
|
||
var isDashboardThinking = false
|
||
|
||
// ── Visitors ──────────────────────────────────────────────────
|
||
var visitors: [Visitor] = [
|
||
Visitor(id: "v1", faceId: "face_001", sentiment: .excited, confidence: 0.92, dwellTime: 450, zone: "Penthouse Show", timestamp: Date()),
|
||
Visitor(id: "v2", faceId: "face_002", sentiment: .interested, confidence: 0.87, dwellTime: 320, zone: "Amenity Deck VR", timestamp: Date()),
|
||
Visitor(id: "v3", faceId: "face_003", sentiment: .neutral, confidence: 0.78, dwellTime: 180, zone: "Reception", timestamp: Date()),
|
||
Visitor(id: "v4", faceId: "face_004", sentiment: .confused, confidence: 0.74, dwellTime: 95, zone: "Penthouse Show", timestamp: Date()),
|
||
Visitor(id: "v5", faceId: "face_005", sentiment: .disinterested, confidence: 0.65, dwellTime: 60, zone: "Gallery", timestamp: Date()),
|
||
]
|
||
|
||
// ── Alerts ────────────────────────────────────────────────────
|
||
var isAlertActive = false
|
||
var alertMessage = ""
|
||
|
||
func triggerAlert(_ msg: String) {
|
||
isAlertActive = true
|
||
alertMessage = msg
|
||
}
|
||
func clearAlert() {
|
||
isAlertActive = false
|
||
alertMessage = ""
|
||
}
|
||
|
||
// ── Leads (Oracle) ────────────────────────────────────────────
|
||
var leads: [Lead] = [
|
||
Lead(id: "1", name: "Mohammed Al-Rashid", phone: "+971 55 123 4567", source: .whatsapp,
|
||
status: .hot, lastMessage: "Can we schedule a viewing for the penthouse tomorrow?",
|
||
lastActive: Date().addingTimeInterval(-300), unreadCount: 2,
|
||
qualification: "whale", budget: "AED 15M+", interest: "Penthouse Suite"),
|
||
Lead(id: "2", name: "Sarah Chen", phone: "+971 50 987 6543", source: .walkin,
|
||
status: .engaged, lastMessage: "Thank you for the brochure. I will review with my partner.",
|
||
lastActive: Date().addingTimeInterval(-1800), unreadCount: 0,
|
||
qualification: "potential", budget: "AED 5–8M", interest: "2BR Sea View"),
|
||
Lead(id: "3", name: "James Wilson", phone: "+971 52 456 7890", source: .website,
|
||
status: .new, lastMessage: "Interested in investment opportunities.",
|
||
lastActive: Date().addingTimeInterval(-7200), unreadCount: 1,
|
||
qualification: "potential", budget: "AED 3–5M", interest: "1BR Investment"),
|
||
Lead(id: "4", name: "Fatima Hassan", phone: "+971 54 321 0987", source: .whatsapp,
|
||
status: .qualified,lastMessage: "What are the payment plan options?",
|
||
lastActive: Date().addingTimeInterval(-14400), unreadCount: 0,
|
||
qualification: "whale", budget: "AED 12M+", interest: "3BR + Maid"),
|
||
Lead(id: "5", name: "David Kumar", phone: "+971 56 789 0123", source: .walkin,
|
||
status: .closed, lastMessage: "Contract signed. Thank you!",
|
||
lastActive: Date().addingTimeInterval(-86400), unreadCount: 0,
|
||
qualification: "whale", budget: "AED 20M", interest: "Full Floor"),
|
||
]
|
||
|
||
var messages: [String: [ChatMessage]] = [
|
||
"1": [
|
||
ChatMessage(id: "m1", sender: "user", content: "Hi, I am interested in the penthouse units.",
|
||
timestamp: Date().addingTimeInterval(-7200)),
|
||
ChatMessage(id: "m2", sender: "oracle",
|
||
content: "Welcome! Our penthouse collection features 4 exclusive units with panoramic sea views. Prices start at AED 15M.",
|
||
timestamp: Date().addingTimeInterval(-7200 + 30)),
|
||
ChatMessage(id: "m3", sender: "user", content: "Can we schedule a viewing tomorrow?",
|
||
timestamp: Date().addingTimeInterval(-300)),
|
||
],
|
||
"2": [
|
||
ChatMessage(id: "m4", sender: "oracle",
|
||
content: "Hello Sarah! Here is the digital brochure for the 2-bedroom units we discussed.",
|
||
timestamp: Date().addingTimeInterval(-14400)),
|
||
ChatMessage(id: "m5", sender: "user", content: "Thank you. I will review with my partner.",
|
||
timestamp: Date().addingTimeInterval(-1800)),
|
||
],
|
||
]
|
||
|
||
var activeLeadId: String? = "1"
|
||
var isOracleThinking = false
|
||
|
||
func addDashboardMessage(sender: String, content: String) {
|
||
let msg = ChatMessage(id: UUID().uuidString, sender: sender, content: content, timestamp: Date())
|
||
dashboardMessages.append(msg)
|
||
}
|
||
|
||
func addOracleMessage(leadId: String, sender: String, content: String) {
|
||
let msg = ChatMessage(id: UUID().uuidString, sender: sender, content: content, timestamp: Date())
|
||
if messages[leadId] == nil { messages[leadId] = [] }
|
||
messages[leadId]!.append(msg)
|
||
}
|
||
|
||
// ── Live ticker ───────────────────────────────────────────────
|
||
private var timerTask: AnyCancellable?
|
||
private var alertTask: DispatchWorkItem?
|
||
|
||
private func startTimer() {
|
||
timerTask = Timer.publish(every: 5, on: .main, in: .common)
|
||
.autoconnect()
|
||
.sink { [weak self] _ in self?.tick() }
|
||
}
|
||
|
||
private func tick() {
|
||
// jitter visitor count ±1
|
||
let delta = Int.random(in: -1...1)
|
||
metrics.activeVisitors = max(10, metrics.activeVisitors + delta)
|
||
|
||
// jitter sentiment ±2
|
||
let sDelta = Double.random(in: -2...2)
|
||
metrics.sentimentScore = min(100, max(40, metrics.sentimentScore + sDelta))
|
||
|
||
// jitter system health
|
||
metrics.systemHealth.cpu = Double.random(in: 0.30...0.65)
|
||
metrics.systemHealth.gpu = Double.random(in: 0.45...0.75)
|
||
metrics.systemHealth.memory = Double.random(in: 0.40...0.70)
|
||
|
||
// Random alert (same 10% chance as WebOS every tick)
|
||
if !isAlertActive && Double.random(in: 0...1) > 0.85 {
|
||
triggerAlert("Confusion detected in Zone B – Penthouse Gallery")
|
||
let work = DispatchWorkItem { [weak self] in self?.clearAlert() }
|
||
alertTask = work
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: work)
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: – Helpers
|
||
|
||
extension Date {
|
||
var relativeShort: String {
|
||
let diff = Int(Date().timeIntervalSince(self))
|
||
if diff < 60 { return "now" }
|
||
if diff < 3600 { return "\(diff / 60)m ago" }
|
||
if diff < 86400 { return "\(diff / 3600)h ago" }
|
||
return "\(diff / 86400)d ago"
|
||
}
|
||
}
|