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%
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