SPB Git

spb/zyquo-local Public MIT

Native macOS AI chat that runs LLMs 100% locally on Apple Silicon with MLX — no cloud, no API keys.

Swift 97.2% Shell 1.8% Makefile 1%
2.7 KB · 87 lines swift
Raw Blame History
1//2//  ChatView.swift3//  Zyquo Local4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//89import SwiftUI1011/// The chat detail column: 52 pt header, centered transcript, floating input.12struct ChatView: View {13    @Environment(AppModel.self) private var app1415    var body: some View {16        Group {17            if app.store.models.isEmpty && app.downloads.tasks.isEmpty {18                OnboardingHeroView()19            } else if let conversation = app.selectedConversation {20                chatBody(conversation)21            } else {22                emptySelection23            }24        }25        .background(ZyquoTheme.background)26    }2728    @ViewBuilder29    private func chatBody(_ conversation: Conversation) -> some View {30        VStack(spacing: 0) {31            ChatHeaderView(conversation: conversation)32            Rectangle().fill(ZyquoTheme.border).frame(height: ZyquoTheme.hairline)3334            ZStack {35                TranscriptView(conversation: conversation)36                if case .loading(let repoID) = app.engineState {37                    ModelLoadingCard(repoID: repoID)38                }39            }4041            InputBar(conversation: conversation)42        }43    }4445    private var emptySelection: some View {46        VStack(spacing: ZyquoTheme.Spacing.s) {47            ZyquoGlyph(size: 40, color: ZyquoTheme.textTertiary)48            Text("Select a chat or press ⌘N")49                .font(ZyquoTheme.body)50                .foregroundStyle(ZyquoTheme.textSecondary)51        }52        .frame(maxWidth: .infinity, maxHeight: .infinity)53    }54}5556/// Elegant centered card while a model loads; input is disabled meanwhile.57struct ModelLoadingCard: View {58    let repoID: String59    @State private var appeared = false6061    var body: some View {62        VStack(spacing: ZyquoTheme.Spacing.m) {63            ProgressView()64                .controlSize(.large)65                .tint(ZyquoTheme.accent)66            Text("Loading \(shortModelName(repoID))")67                .font(ZyquoTheme.bodyEmphasis)68                .foregroundStyle(ZyquoTheme.textPrimary)69            Text("Allocating unified memory for the model weights…")70                .font(ZyquoTheme.caption)71                .foregroundStyle(ZyquoTheme.textSecondary)72        }73        .padding(ZyquoTheme.Spacing.xxl)74        .background(ZyquoTheme.surface, in: RoundedRectangle(cornerRadius: ZyquoTheme.Radius.l))75        .overlay(76            RoundedRectangle(cornerRadius: ZyquoTheme.Radius.l)77                .stroke(ZyquoTheme.border, lineWidth: ZyquoTheme.hairline)78        )79        .floatingShadow()80        .opacity(appeared ? 1 : 0)81        .offset(y: appeared ? 0 : 6)82        .onAppear {83            withAnimation(.easeOut(duration: 0.15)) { appeared = true }84        }85    }86}87