mirror of
https://github.com/ggerganov/llama.cpp.git
synced 2024-12-24 10:24:35 +00:00
llama.swiftui : update models layout (#4826)
* Updated Models Layout - Added a models drawer - Added downloading directly from Hugging Face - Load custom models from local folder - Delete models by swiping left * trimmed trailing white space * Updated Models Layout
This commit is contained in:
parent
5537d9d36b
commit
e790eef21c
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
549479CB2AC9E16000E0F78B /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549479CA2AC9E16000E0F78B /* Metal.framework */; };
|
549479CB2AC9E16000E0F78B /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 549479CA2AC9E16000E0F78B /* Metal.framework */; };
|
||||||
|
79E1D9CD2B4CD16E005F8E46 /* InputButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79E1D9CC2B4CD16E005F8E46 /* InputButton.swift */; };
|
||||||
7FA3D2B32B2EA2F600543F92 /* DownloadButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FA3D2B22B2EA2F600543F92 /* DownloadButton.swift */; };
|
7FA3D2B32B2EA2F600543F92 /* DownloadButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FA3D2B22B2EA2F600543F92 /* DownloadButton.swift */; };
|
||||||
8A1C83772AC328BD0096AF73 /* llama_swiftuiApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C83762AC328BD0096AF73 /* llama_swiftuiApp.swift */; };
|
8A1C83772AC328BD0096AF73 /* llama_swiftuiApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C83762AC328BD0096AF73 /* llama_swiftuiApp.swift */; };
|
||||||
8A1C83792AC328BD0096AF73 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C83782AC328BD0096AF73 /* ContentView.swift */; };
|
8A1C83792AC328BD0096AF73 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A1C83782AC328BD0096AF73 /* ContentView.swift */; };
|
||||||
@ -22,6 +23,7 @@
|
|||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
549479CA2AC9E16000E0F78B /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
|
549479CA2AC9E16000E0F78B /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
|
||||||
|
79E1D9CC2B4CD16E005F8E46 /* InputButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputButton.swift; sourceTree = "<group>"; };
|
||||||
7FA3D2B22B2EA2F600543F92 /* DownloadButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadButton.swift; sourceTree = "<group>"; };
|
7FA3D2B22B2EA2F600543F92 /* DownloadButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadButton.swift; sourceTree = "<group>"; };
|
||||||
8A1C83732AC328BD0096AF73 /* llama.swiftui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = llama.swiftui.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
8A1C83732AC328BD0096AF73 /* llama.swiftui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = llama.swiftui.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
8A1C83762AC328BD0096AF73 /* llama_swiftuiApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = llama_swiftuiApp.swift; sourceTree = "<group>"; };
|
8A1C83762AC328BD0096AF73 /* llama_swiftuiApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = llama_swiftuiApp.swift; sourceTree = "<group>"; };
|
||||||
@ -119,6 +121,7 @@
|
|||||||
7FA3D2B22B2EA2F600543F92 /* DownloadButton.swift */,
|
7FA3D2B22B2EA2F600543F92 /* DownloadButton.swift */,
|
||||||
8A1C83782AC328BD0096AF73 /* ContentView.swift */,
|
8A1C83782AC328BD0096AF73 /* ContentView.swift */,
|
||||||
F1FE20E12B465EC900B45541 /* LoadCustomButton.swift */,
|
F1FE20E12B465EC900B45541 /* LoadCustomButton.swift */,
|
||||||
|
79E1D9CC2B4CD16E005F8E46 /* InputButton.swift */,
|
||||||
);
|
);
|
||||||
path = UI;
|
path = UI;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -213,6 +216,7 @@
|
|||||||
8A1C83792AC328BD0096AF73 /* ContentView.swift in Sources */,
|
8A1C83792AC328BD0096AF73 /* ContentView.swift in Sources */,
|
||||||
8A1C83772AC328BD0096AF73 /* llama_swiftuiApp.swift in Sources */,
|
8A1C83772AC328BD0096AF73 /* llama_swiftuiApp.swift in Sources */,
|
||||||
7FA3D2B32B2EA2F600543F92 /* DownloadButton.swift in Sources */,
|
7FA3D2B32B2EA2F600543F92 /* DownloadButton.swift in Sources */,
|
||||||
|
79E1D9CD2B4CD16E005F8E46 /* InputButton.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -345,7 +349,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = STLSG3FG8Q;
|
DEVELOPMENT_TEAM = K5UQJPP73A;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
@ -377,7 +381,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = STLSG3FG8Q;
|
DEVELOPMENT_TEAM = K5UQJPP73A;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
struct Model: Identifiable {
|
||||||
|
var id = UUID()
|
||||||
|
var name: String
|
||||||
|
var url: String
|
||||||
|
var filename: String
|
||||||
|
var status: String?
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class LlamaState: ObservableObject {
|
class LlamaState: ObservableObject {
|
||||||
@Published var messageLog = ""
|
@Published var messageLog = ""
|
||||||
@Published var cacheCleared = false
|
@Published var cacheCleared = false
|
||||||
|
@Published var downloadedModels: [Model] = []
|
||||||
|
@Published var undownloadedModels: [Model] = []
|
||||||
let NS_PER_S = 1_000_000_000.0
|
let NS_PER_S = 1_000_000_000.0
|
||||||
|
|
||||||
private var llamaContext: LlamaContext?
|
private var llamaContext: LlamaContext?
|
||||||
@ -13,23 +23,102 @@ class LlamaState: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
loadModelsFromDisk()
|
||||||
|
loadDefaultModels()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadModelsFromDisk() {
|
||||||
|
do {
|
||||||
|
let documentsURL = getDocumentsDirectory()
|
||||||
|
let modelURLs = try FileManager.default.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants])
|
||||||
|
for modelURL in modelURLs {
|
||||||
|
let modelName = modelURL.deletingPathExtension().lastPathComponent
|
||||||
|
downloadedModels.append(Model(name: modelName, url: "", filename: modelURL.lastPathComponent, status: "downloaded"))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error loading models from disk: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadDefaultModels() {
|
||||||
do {
|
do {
|
||||||
try loadModel(modelUrl: defaultModelUrl)
|
try loadModel(modelUrl: defaultModelUrl)
|
||||||
} catch {
|
} catch {
|
||||||
messageLog += "Error!\n"
|
messageLog += "Error!\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for model in defaultModels {
|
||||||
|
let fileURL = getDocumentsDirectory().appendingPathComponent(model.filename)
|
||||||
|
if FileManager.default.fileExists(atPath: fileURL.path) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var undownloadedModel = model
|
||||||
|
undownloadedModel.status = "download"
|
||||||
|
undownloadedModels.append(undownloadedModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDocumentsDirectory() -> URL {
|
||||||
|
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
||||||
|
return paths[0]
|
||||||
|
}
|
||||||
|
private let defaultModels: [Model] = [
|
||||||
|
Model(name: "TinyLlama-1.1B (Q4_0, 0.6 GiB)",url: "https://huggingface.co/TheBloke/TinyLlama-1.1B-1T-OpenOrca-GGUF/resolve/main/tinyllama-1.1b-1t-openorca.Q4_0.gguf?download=true",filename: "tinyllama-1.1b-1t-openorca.Q4_0.gguf", status: "download"),
|
||||||
|
Model(
|
||||||
|
name: "TinyLlama-1.1B Chat (Q8_0, 1.1 GiB)",
|
||||||
|
url: "https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main/tinyllama-1.1b-chat-v1.0.Q8_0.gguf?download=true",
|
||||||
|
filename: "tinyllama-1.1b-chat-v1.0.Q8_0.gguf", status: "download"
|
||||||
|
),
|
||||||
|
|
||||||
|
Model(
|
||||||
|
name: "TinyLlama-1.1B (F16, 2.2 GiB)",
|
||||||
|
url: "https://huggingface.co/ggml-org/models/resolve/main/tinyllama-1.1b/ggml-model-f16.gguf?download=true",
|
||||||
|
filename: "tinyllama-1.1b-f16.gguf", status: "download"
|
||||||
|
),
|
||||||
|
|
||||||
|
Model(
|
||||||
|
name: "Phi-2.7B (Q4_0, 1.6 GiB)",
|
||||||
|
url: "https://huggingface.co/ggml-org/models/resolve/main/phi-2/ggml-model-q4_0.gguf?download=true",
|
||||||
|
filename: "phi-2-q4_0.gguf", status: "download"
|
||||||
|
),
|
||||||
|
|
||||||
|
Model(
|
||||||
|
name: "Phi-2.7B (Q8_0, 2.8 GiB)",
|
||||||
|
url: "https://huggingface.co/ggml-org/models/resolve/main/phi-2/ggml-model-q8_0.gguf?download=true",
|
||||||
|
filename: "phi-2-q8_0.gguf", status: "download"
|
||||||
|
),
|
||||||
|
|
||||||
|
Model(
|
||||||
|
name: "Mistral-7B-v0.1 (Q4_0, 3.8 GiB)",
|
||||||
|
url: "https://huggingface.co/TheBloke/Mistral-7B-v0.1-GGUF/resolve/main/mistral-7b-v0.1.Q4_0.gguf?download=true",
|
||||||
|
filename: "mistral-7b-v0.1.Q4_0.gguf", status: "download"
|
||||||
|
),
|
||||||
|
Model(
|
||||||
|
name: "OpenHermes-2.5-Mistral-7B (Q3_K_M, 3.52 GiB)",
|
||||||
|
url: "https://huggingface.co/TheBloke/OpenHermes-2.5-Mistral-7B-GGUF/resolve/main/openhermes-2.5-mistral-7b.Q3_K_M.gguf?download=true",
|
||||||
|
filename: "openhermes-2.5-mistral-7b.Q3_K_M.gguf", status: "download"
|
||||||
|
)
|
||||||
|
]
|
||||||
func loadModel(modelUrl: URL?) throws {
|
func loadModel(modelUrl: URL?) throws {
|
||||||
if let modelUrl {
|
if let modelUrl {
|
||||||
messageLog += "Loading model...\n"
|
messageLog += "Loading model...\n"
|
||||||
llamaContext = try LlamaContext.create_context(path: modelUrl.path())
|
llamaContext = try LlamaContext.create_context(path: modelUrl.path())
|
||||||
messageLog += "Loaded model \(modelUrl.lastPathComponent)\n"
|
messageLog += "Loaded model \(modelUrl.lastPathComponent)\n"
|
||||||
|
|
||||||
|
// Assuming that the model is successfully loaded, update the downloaded models
|
||||||
|
updateDownloadedModels(modelName: modelUrl.lastPathComponent, status: "downloaded")
|
||||||
} else {
|
} else {
|
||||||
messageLog += "Load a model from the list below\n"
|
messageLog += "Load a model from the list below\n"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func updateDownloadedModels(modelName: String, status: String) {
|
||||||
|
undownloadedModels.removeAll { $0.name == modelName }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func complete(text: String) async {
|
func complete(text: String) async {
|
||||||
guard let llamaContext else {
|
guard let llamaContext else {
|
||||||
return
|
return
|
||||||
|
@ -2,115 +2,57 @@ import SwiftUI
|
|||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@StateObject var llamaState = LlamaState()
|
@StateObject var llamaState = LlamaState()
|
||||||
|
|
||||||
@State private var multiLineText = ""
|
@State private var multiLineText = ""
|
||||||
|
@State private var showingHelp = false // To track if Help Sheet should be shown
|
||||||
private static func cleanupModelCaches() {
|
|
||||||
// Delete all models (*.gguf)
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
||||||
do {
|
|
||||||
let fileURLs = try fileManager.contentsOfDirectory(at: documentsUrl, includingPropertiesForKeys: nil)
|
|
||||||
for fileURL in fileURLs {
|
|
||||||
if fileURL.pathExtension == "gguf" {
|
|
||||||
try fileManager.removeItem(at: fileURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Error while enumerating files \(documentsUrl.path): \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
NavigationView {
|
||||||
ScrollView(.vertical, showsIndicators: true) {
|
VStack {
|
||||||
Text(llamaState.messageLog)
|
ScrollView(.vertical, showsIndicators: true) {
|
||||||
.font(.system(size: 12))
|
Text(llamaState.messageLog)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.font(.system(size: 12))
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding()
|
||||||
|
.onTapGesture {
|
||||||
|
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEditor(text: $multiLineText)
|
||||||
|
.frame(height: 80)
|
||||||
|
.padding()
|
||||||
|
.border(Color.gray, width: 0.5)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Button("Send") {
|
||||||
|
sendText()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Bench") {
|
||||||
|
bench()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Clear") {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button("Copy") {
|
||||||
|
UIPasteboard.general.string = llamaState.messageLog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
.padding()
|
.padding()
|
||||||
.onTapGesture {
|
|
||||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextEditor(text: $multiLineText)
|
NavigationLink(destination: DrawerView(llamaState: llamaState)) {
|
||||||
.frame(height: 80)
|
Text("View Models")
|
||||||
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.border(Color.gray, width: 0.5)
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Button("Send") {
|
|
||||||
sendText()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button("Bench") {
|
|
||||||
bench()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button("Clear") {
|
|
||||||
clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button("Copy") {
|
|
||||||
UIPasteboard.general.string = llamaState.messageLog
|
|
||||||
}
|
|
||||||
}.buttonStyle(.bordered)
|
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
DownloadButton(
|
|
||||||
llamaState: llamaState,
|
|
||||||
modelName: "TinyLlama-1.1B (Q4_0, 0.6 GiB)",
|
|
||||||
modelUrl: "https://huggingface.co/TheBloke/TinyLlama-1.1B-1T-OpenOrca-GGUF/resolve/main/tinyllama-1.1b-1t-openorca.Q4_0.gguf?download=true",
|
|
||||||
filename: "tinyllama-1.1b-1t-openorca.Q4_0.gguf"
|
|
||||||
)
|
|
||||||
|
|
||||||
DownloadButton(
|
|
||||||
llamaState: llamaState,
|
|
||||||
modelName: "TinyLlama-1.1B (Q8_0, 1.1 GiB)",
|
|
||||||
modelUrl: "https://huggingface.co/TheBloke/TinyLlama-1.1B-1T-OpenOrca-GGUF/resolve/main/tinyllama-1.1b-1t-openorca.Q8_0.gguf?download=true",
|
|
||||||
filename: "tinyllama-1.1b-1t-openorca.Q8_0.gguf"
|
|
||||||
)
|
|
||||||
|
|
||||||
DownloadButton(
|
|
||||||
llamaState: llamaState,
|
|
||||||
modelName: "TinyLlama-1.1B (F16, 2.2 GiB)",
|
|
||||||
modelUrl: "https://huggingface.co/ggml-org/models/resolve/main/tinyllama-1.1b/ggml-model-f16.gguf?download=true",
|
|
||||||
filename: "tinyllama-1.1b-f16.gguf"
|
|
||||||
)
|
|
||||||
|
|
||||||
DownloadButton(
|
|
||||||
llamaState: llamaState,
|
|
||||||
modelName: "Phi-2.7B (Q4_0, 1.6 GiB)",
|
|
||||||
modelUrl: "https://huggingface.co/ggml-org/models/resolve/main/phi-2/ggml-model-q4_0.gguf?download=true",
|
|
||||||
filename: "phi-2-q4_0.gguf"
|
|
||||||
)
|
|
||||||
|
|
||||||
DownloadButton(
|
|
||||||
llamaState: llamaState,
|
|
||||||
modelName: "Phi-2.7B (Q8_0, 2.8 GiB)",
|
|
||||||
modelUrl: "https://huggingface.co/ggml-org/models/resolve/main/phi-2/ggml-model-q8_0.gguf?download=true",
|
|
||||||
filename: "phi-2-q8_0.gguf"
|
|
||||||
)
|
|
||||||
|
|
||||||
DownloadButton(
|
|
||||||
llamaState: llamaState,
|
|
||||||
modelName: "Mistral-7B-v0.1 (Q4_0, 3.8 GiB)",
|
|
||||||
modelUrl: "https://huggingface.co/TheBloke/Mistral-7B-v0.1-GGUF/resolve/main/mistral-7b-v0.1.Q4_0.gguf?download=true",
|
|
||||||
filename: "mistral-7b-v0.1.Q4_0.gguf"
|
|
||||||
)
|
|
||||||
|
|
||||||
Button("Clear downloaded models") {
|
|
||||||
ContentView.cleanupModelCaches()
|
|
||||||
llamaState.cacheCleared = true
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadCustomButton(llamaState: llamaState)
|
|
||||||
}
|
}
|
||||||
.padding(.top, 4)
|
.padding()
|
||||||
.font(.system(size: 12))
|
.navigationBarTitle("Model Settings", displayMode: .inline)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendText() {
|
func sendText() {
|
||||||
@ -131,8 +73,73 @@ struct ContentView: View {
|
|||||||
await llamaState.clear()
|
await llamaState.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
struct DrawerView: View {
|
||||||
|
|
||||||
|
@ObservedObject var llamaState: LlamaState
|
||||||
|
@State private var showingHelp = false
|
||||||
|
func delete(at offsets: IndexSet) {
|
||||||
|
offsets.forEach { offset in
|
||||||
|
let model = llamaState.downloadedModels[offset]
|
||||||
|
let fileURL = getDocumentsDirectory().appendingPathComponent(model.filename)
|
||||||
|
do {
|
||||||
|
try FileManager.default.removeItem(at: fileURL)
|
||||||
|
} catch {
|
||||||
|
print("Error deleting file: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove models from downloadedModels array
|
||||||
|
llamaState.downloadedModels.remove(atOffsets: offsets)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDocumentsDirectory() -> URL {
|
||||||
|
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
||||||
|
return paths[0]
|
||||||
|
}
|
||||||
|
var body: some View {
|
||||||
|
List {
|
||||||
|
Section(header: Text("Download Models From Hugging Face")) {
|
||||||
|
HStack {
|
||||||
|
InputButton(llamaState: llamaState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Section(header: Text("Downloaded Models")) {
|
||||||
|
ForEach(llamaState.downloadedModels) { model in
|
||||||
|
DownloadButton(llamaState: llamaState, modelName: model.name, modelUrl: model.url, filename: model.filename)
|
||||||
|
}
|
||||||
|
.onDelete(perform: delete)
|
||||||
|
}
|
||||||
|
Section(header: Text("Default Models")) {
|
||||||
|
ForEach(llamaState.undownloadedModels) { model in
|
||||||
|
DownloadButton(llamaState: llamaState, modelName: model.name, modelUrl: model.url, filename: model.filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.listStyle(GroupedListStyle())
|
||||||
|
.navigationBarTitle("Model Settings", displayMode: .inline).toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
Button("Help") {
|
||||||
|
showingHelp = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.sheet(isPresented: $showingHelp) { // Sheet for help modal
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("1. Make sure the model is in GGUF Format")
|
||||||
|
.padding()
|
||||||
|
Text("2. Copy the download link of the quantized model")
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//#Preview {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
// ContentView()
|
static var previews: some View {
|
||||||
//}
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -53,6 +53,8 @@ struct DownloadButton: View {
|
|||||||
|
|
||||||
llamaState.cacheCleared = false
|
llamaState.cacheCleared = false
|
||||||
|
|
||||||
|
let model = Model(name: modelName, url: modelUrl, filename: filename, status: "downloaded")
|
||||||
|
llamaState.downloadedModels.append(model)
|
||||||
status = "downloaded"
|
status = "downloaded"
|
||||||
}
|
}
|
||||||
} catch let err {
|
} catch let err {
|
||||||
|
131
examples/llama.swiftui/llama.swiftui/UI/InputButton.swift
Normal file
131
examples/llama.swiftui/llama.swiftui/UI/InputButton.swift
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct InputButton: View {
|
||||||
|
@ObservedObject var llamaState: LlamaState
|
||||||
|
@State private var inputLink: String = ""
|
||||||
|
@State private var status: String = "download"
|
||||||
|
@State private var filename: String = ""
|
||||||
|
|
||||||
|
@State private var downloadTask: URLSessionDownloadTask?
|
||||||
|
@State private var progress = 0.0
|
||||||
|
@State private var observation: NSKeyValueObservation?
|
||||||
|
|
||||||
|
private static func extractModelInfo(from link: String) -> (modelName: String, filename: String)? {
|
||||||
|
guard let url = URL(string: link),
|
||||||
|
let lastPathComponent = url.lastPathComponent.components(separatedBy: ".").first,
|
||||||
|
let modelName = lastPathComponent.components(separatedBy: "-").dropLast().joined(separator: "-").removingPercentEncoding,
|
||||||
|
let filename = lastPathComponent.removingPercentEncoding else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return (modelName, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func getFileURL(filename: String) -> URL {
|
||||||
|
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func download() {
|
||||||
|
guard let extractedInfo = InputButton.extractModelInfo(from: inputLink) else {
|
||||||
|
// Handle invalid link or extraction failure
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let (modelName, filename) = extractedInfo
|
||||||
|
self.filename = filename // Set the state variable
|
||||||
|
|
||||||
|
status = "downloading"
|
||||||
|
print("Downloading model \(modelName) from \(inputLink)")
|
||||||
|
guard let url = URL(string: inputLink) else { return }
|
||||||
|
let fileURL = InputButton.getFileURL(filename: filename)
|
||||||
|
|
||||||
|
downloadTask = URLSession.shared.downloadTask(with: url) { temporaryURL, response, error in
|
||||||
|
if let error = error {
|
||||||
|
print("Error: \(error.localizedDescription)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
|
||||||
|
print("Server error!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if let temporaryURL = temporaryURL {
|
||||||
|
try FileManager.default.copyItem(at: temporaryURL, to: fileURL)
|
||||||
|
print("Writing to \(filename) completed")
|
||||||
|
|
||||||
|
llamaState.cacheCleared = false
|
||||||
|
|
||||||
|
let model = Model(name: modelName, url: self.inputLink, filename: filename, status: "downloaded")
|
||||||
|
llamaState.downloadedModels.append(model)
|
||||||
|
status = "downloaded"
|
||||||
|
}
|
||||||
|
} catch let err {
|
||||||
|
print("Error: \(err.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observation = downloadTask?.progress.observe(\.fractionCompleted) { progress, _ in
|
||||||
|
self.progress = progress.fractionCompleted
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadTask?.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
TextField("Paste Quantized Download Link", text: $inputLink)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
downloadTask?.cancel()
|
||||||
|
status = "download"
|
||||||
|
}) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == "download" {
|
||||||
|
Button(action: download) {
|
||||||
|
Text("Download Custom Model")
|
||||||
|
}
|
||||||
|
} else if status == "downloading" {
|
||||||
|
Button(action: {
|
||||||
|
downloadTask?.cancel()
|
||||||
|
status = "download"
|
||||||
|
}) {
|
||||||
|
Text("Downloading \(Int(progress * 100))%")
|
||||||
|
}
|
||||||
|
} else if status == "downloaded" {
|
||||||
|
Button(action: {
|
||||||
|
let fileURL = InputButton.getFileURL(filename: self.filename)
|
||||||
|
if !FileManager.default.fileExists(atPath: fileURL.path) {
|
||||||
|
download()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
try llamaState.loadModel(modelUrl: fileURL)
|
||||||
|
} catch let err {
|
||||||
|
print("Error: \(err.localizedDescription)")
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Load Custom Model")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("Unknown status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear() {
|
||||||
|
downloadTask?.cancel()
|
||||||
|
}
|
||||||
|
.onChange(of: llamaState.cacheCleared) { newValue in
|
||||||
|
if newValue {
|
||||||
|
downloadTask?.cancel()
|
||||||
|
let fileURL = InputButton.getFileURL(filename: self.filename)
|
||||||
|
status = FileManager.default.fileExists(atPath: fileURL.path) ? "downloaded" : "download"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user