-
Notifications
You must be signed in to change notification settings - Fork 229
/
Copy pathPromptTextField.swift
141 lines (128 loc) · 4.58 KB
/
PromptTextField.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//
// PromptTextField.swift
// Diffusion-macOS
//
// Created by Dolmere on 22/06/2023.
// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE
//
import SwiftUI
import Combine
import StableDiffusion
struct PromptTextField: View {
@State private var output: String = ""
@State private var input: String = ""
@State private var typing = false
@State private var tokenCount: Int = 0
@State var isPositivePrompt: Bool = true
@State private var tokenizer: BPETokenizer?
@State private var currentModelVersion: String = ""
@Binding var textBinding: String
@Binding var model: String // the model version as it's stored in Settings
private let maxTokenCount = 77
private var modelInfo: ModelInfo? {
ModelInfo.from(modelVersion: $model.wrappedValue)
}
private var pipelineLoader: PipelineLoader? {
guard let modelInfo = modelInfo else { return nil }
return PipelineLoader(model: modelInfo)
}
private var compiledURL: URL? {
return pipelineLoader?.compiledURL
}
private var textColor: Color {
switch tokenCount {
case 0...65:
return .green
case 66...75:
return .orange
default:
return .red
}
}
// macOS initializer
init(text: Binding<String>, isPositivePrompt: Bool, model: Binding<String>) {
_textBinding = text
self.isPositivePrompt = isPositivePrompt
_model = model
}
// iOS initializer
init(text: Binding<String>, isPositivePrompt: Bool, model: String) {
_textBinding = text
self.isPositivePrompt = isPositivePrompt
_model = .constant(model)
}
var body: some View {
VStack {
#if os(macOS)
TextField(isPositivePrompt ? "Positive prompt" : "Negative Prompt", text: $textBinding,
axis: .vertical)
.lineLimit(20)
.textFieldStyle(.squareBorder)
.listRowInsets(EdgeInsets(top: 0, leading: -20, bottom: 0, trailing: 20))
.foregroundColor(textColor == .green ? .primary : textColor)
.frame(minHeight: 30)
if modelInfo != nil && tokenizer != nil {
HStack {
Spacer()
if !textBinding.isEmpty {
Text("\(tokenCount)")
.foregroundColor(textColor)
Text(" / \(maxTokenCount)")
}
}
.onReceive(Just(textBinding)) { text in
updateTokenCount(newText: text)
}
.font(.caption)
}
#else
TextField("Prompt", text: $textBinding, axis: .vertical)
.lineLimit(20)
.listRowInsets(EdgeInsets(top: 0, leading: -20, bottom: 0, trailing: 20))
.foregroundColor(textColor == .green ? .primary : textColor)
.frame(minHeight: 30)
HStack {
if !textBinding.isEmpty {
Text("\(tokenCount)")
.foregroundColor(textColor)
Text(" / \(maxTokenCount)")
}
Spacer()
}
.onReceive(Just(textBinding)) { text in
updateTokenCount(newText: text)
}
.font(.caption)
#endif
}
.onChange(of: model) { model in
updateTokenCount(newText: textBinding)
}
.onAppear {
updateTokenCount(newText: textBinding)
}
}
private func updateTokenCount(newText: String) {
// ensure that the compiled URL exists
guard let compiledURL = compiledURL else { return }
// Initialize the tokenizer only when it's not created yet or the model changes
// Check if the model version has changed
let modelVersion = $model.wrappedValue
if modelVersion != currentModelVersion {
do {
tokenizer = try BPETokenizer(
mergesAt: compiledURL.appendingPathComponent("merges.txt"),
vocabularyAt: compiledURL.appendingPathComponent("vocab.json")
)
currentModelVersion = modelVersion
} catch {
print("Failed to create tokenizer: \(error)")
return
}
}
let (tokens, _) = tokenizer?.tokenize(input: newText) ?? ([], [])
DispatchQueue.main.async {
self.tokenCount = tokens.count
}
}
}