2025-02-10 19:39:48 +08:00
|
|
|
//
|
|
|
|
// ModelListView.swift
|
|
|
|
// MNNLLMiOS
|
|
|
|
//
|
|
|
|
// Created by 游薪渝(揽清) on 2025/1/3.
|
|
|
|
//
|
|
|
|
|
|
|
|
import SwiftUI
|
|
|
|
|
|
|
|
struct ModelListView: View {
|
2025-02-12 11:45:28 +08:00
|
|
|
|
2025-02-10 19:39:48 +08:00
|
|
|
@State private var scrollOffset: CGFloat = 0
|
|
|
|
@State private var showHelp = false
|
|
|
|
@State private var showHistory = false
|
|
|
|
@State private var selectedHistory: ChatHistory?
|
|
|
|
@State private var histories: [ChatHistory] = []
|
2025-02-20 16:28:06 +08:00
|
|
|
@State private var showSettings = false
|
|
|
|
@State private var showWebView = false
|
|
|
|
@State private var webViewURL: URL?
|
2025-02-10 19:39:48 +08:00
|
|
|
|
2025-02-12 11:45:28 +08:00
|
|
|
@StateObject private var viewModel = ModelListViewModel()
|
|
|
|
|
2025-02-10 19:39:48 +08:00
|
|
|
var body: some View {
|
|
|
|
ZStack {
|
|
|
|
NavigationView {
|
|
|
|
List {
|
|
|
|
SearchBar(text: $viewModel.searchText)
|
|
|
|
.listRowInsets(EdgeInsets())
|
|
|
|
.listRowSeparator(.hidden)
|
|
|
|
.padding(.horizontal)
|
|
|
|
|
|
|
|
ForEach(viewModel.filteredModels, id: \.modelId) { model in
|
|
|
|
ModelRowView(model: model,
|
|
|
|
downloadProgress: viewModel.downloadProgress[model.modelId] ?? 0,
|
|
|
|
isDownloading: viewModel.currentlyDownloading == model.modelId,
|
|
|
|
isOtherDownloading: viewModel.currentlyDownloading != nil) {
|
|
|
|
if model.isDownloaded {
|
|
|
|
viewModel.selectModel(model)
|
|
|
|
} else {
|
|
|
|
Task {
|
|
|
|
await viewModel.downloadModel(model)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
|
|
|
if model.isDownloaded {
|
|
|
|
Button(role: .destructive) {
|
|
|
|
Task {
|
|
|
|
await viewModel.deleteModel(model)
|
|
|
|
}
|
|
|
|
} label: {
|
|
|
|
Label("Delete", systemImage: "trash")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.listStyle(.plain)
|
|
|
|
.navigationTitle("Models")
|
|
|
|
.navigationBarTitleDisplayMode(.large)
|
|
|
|
.navigationBarItems(
|
|
|
|
leading: Button(action: {
|
|
|
|
showHistory.toggle()
|
|
|
|
updateHistory()
|
|
|
|
}) {
|
|
|
|
Image(systemName: "clock.arrow.circlepath")
|
|
|
|
.resizable()
|
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
|
.frame(width: 22, height: 22)
|
|
|
|
},
|
2025-02-20 16:28:06 +08:00
|
|
|
trailing: settingsButton
|
2025-02-10 19:39:48 +08:00
|
|
|
)
|
|
|
|
.sheet(isPresented: $showHelp) {
|
|
|
|
HelpView()
|
|
|
|
}
|
2025-02-20 16:28:06 +08:00
|
|
|
.sheet(isPresented: $showWebView) {
|
|
|
|
if let url = webViewURL {
|
|
|
|
WebView(url: url)
|
|
|
|
}
|
|
|
|
}
|
2025-02-10 19:39:48 +08:00
|
|
|
.refreshable {
|
|
|
|
await viewModel.fetchModels()
|
|
|
|
}
|
|
|
|
.alert("Error", isPresented: $viewModel.showError) {
|
|
|
|
Button("OK", role: .cancel) {}
|
|
|
|
} message: {
|
|
|
|
Text(viewModel.errorMessage)
|
|
|
|
}
|
|
|
|
.background(
|
|
|
|
NavigationLink(
|
|
|
|
destination: {
|
|
|
|
if let selectedModel = viewModel.selectedModel {
|
|
|
|
return AnyView(LLMChatView(modelInfo: selectedModel))
|
|
|
|
} else if let selectedHistory = selectedHistory {
|
|
|
|
return AnyView(LLMChatView(modelInfo: ModelInfo(
|
|
|
|
modelId: selectedHistory.modelId,
|
|
|
|
createdAt: selectedHistory.createdAt.formatAgo(),
|
|
|
|
downloads: 0,
|
|
|
|
tags: [],
|
|
|
|
isDownloaded: true
|
|
|
|
), history: selectedHistory))
|
|
|
|
}
|
|
|
|
return AnyView(EmptyView())
|
|
|
|
}(),
|
|
|
|
isActive: Binding(
|
|
|
|
get: { viewModel.selectedModel != nil || selectedHistory != nil },
|
|
|
|
set: { if !$0 { viewModel.selectedModel = nil; selectedHistory = nil } }
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
EmptyView()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
.disabled(showHistory)
|
|
|
|
|
|
|
|
|
|
|
|
if showHistory {
|
|
|
|
Color.black.opacity(0.5)
|
|
|
|
.edgesIgnoringSafeArea(.all)
|
|
|
|
.onTapGesture {
|
|
|
|
withAnimation {
|
|
|
|
showHistory = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SideMenuView(isOpen: $showHistory, selectedHistory: $selectedHistory, histories: $histories)
|
|
|
|
.edgesIgnoringSafeArea(.all)
|
|
|
|
}
|
|
|
|
.onAppear {
|
|
|
|
updateHistory()
|
|
|
|
}
|
2025-02-20 16:28:06 +08:00
|
|
|
.actionSheet(isPresented: $showSettings) {
|
|
|
|
ActionSheet(title: Text("Settings"), buttons: [
|
|
|
|
.default(Text(ModelSource.modelScope.description)) {
|
|
|
|
ModelSourceManager.shared.updateSelectedSource(.modelScope)
|
|
|
|
},
|
|
|
|
.default(Text(ModelSource.huggingFace.description)) {
|
|
|
|
ModelSourceManager.shared.updateSelectedSource(.huggingFace)
|
|
|
|
},
|
|
|
|
.default(Text("Report an Issue")) {
|
|
|
|
webViewURL = URL(string: "https://github.com/alibaba/MNN/issues")
|
|
|
|
showWebView = true
|
|
|
|
},
|
|
|
|
.default(Text("Go to MNN Homepage")) {
|
|
|
|
webViewURL = URL(string: "https://github.com/alibaba/MNN")
|
|
|
|
showWebView = true
|
|
|
|
},
|
|
|
|
.cancel()
|
|
|
|
])
|
|
|
|
}
|
2025-02-10 19:39:48 +08:00
|
|
|
}
|
2025-02-12 11:45:28 +08:00
|
|
|
|
|
|
|
private func updateHistory() {
|
2025-02-10 19:39:48 +08:00
|
|
|
histories = ChatHistoryManager.shared.getAllHistory()
|
|
|
|
}
|
2025-02-20 16:28:06 +08:00
|
|
|
|
|
|
|
private var settingsButton: some View {
|
|
|
|
Button(action: {
|
|
|
|
showSettings.toggle()
|
|
|
|
}) {
|
|
|
|
Image(systemName: "gear")
|
|
|
|
.resizable()
|
|
|
|
.aspectRatio(contentMode: .fit)
|
|
|
|
.frame(width: 22, height: 22)
|
|
|
|
}
|
|
|
|
}
|
2025-02-10 19:39:48 +08:00
|
|
|
}
|