Temporary stuff.

This commit is contained in:
OK 2023-08-05 12:21:30 +02:00
parent bcfb41917a
commit 9dde163a3c
5 changed files with 148 additions and 77 deletions

View File

@ -231,7 +231,6 @@
); );
mainGroup = 4F772AF82A4706F600D3266B; mainGroup = 4F772AF82A4706F600D3266B;
packageReferences = ( packageReferences = (
4F50EF3F2A49CA5E009BD94E /* XCRemoteSwiftPackageReference "Down" */,
4F50EF422A49CE31009BD94E /* XCRemoteSwiftPackageReference "MarkdownView" */, 4F50EF422A49CE31009BD94E /* XCRemoteSwiftPackageReference "MarkdownView" */,
4F50EF452A49D012009BD94E /* XCRemoteSwiftPackageReference "Highlightr" */, 4F50EF452A49D012009BD94E /* XCRemoteSwiftPackageReference "Highlightr" */,
); );
@ -444,6 +443,7 @@
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ChatMasterMind/Info.plist; INFOPLIST_FILE = ChatMasterMind/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@ -485,6 +485,7 @@
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ChatMasterMind/Info.plist; INFOPLIST_FILE = ChatMasterMind/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@ -644,14 +645,6 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
4F50EF3F2A49CA5E009BD94E /* XCRemoteSwiftPackageReference "Down" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/iwasrobbed/Down";
requirement = {
branch = master;
kind = branch;
};
};
4F50EF422A49CE31009BD94E /* XCRemoteSwiftPackageReference "MarkdownView" */ = { 4F50EF422A49CE31009BD94E /* XCRemoteSwiftPackageReference "MarkdownView" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LiYanan2004/MarkdownView.git"; repositoryURL = "https://github.com/LiYanan2004/MarkdownView.git";

View File

@ -1,14 +1,5 @@
{ {
"pins" : [ "pins" : [
{
"identity" : "down",
"kind" : "remoteSourceControl",
"location" : "https://github.com/iwasrobbed/Down",
"state" : {
"branch" : "master",
"revision" : "e754ab1c80920dd51a8e08290c912ac1c2ac8b58"
}
},
{ {
"identity" : "highlightr", "identity" : "highlightr",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@ -9,30 +9,26 @@ import Foundation
import SwiftData import SwiftData
@Model @Model
final class ChatPair: Identifiable { final class ChatPair {
let id: UUID
var timestamp: Date var timestamp: Date
var question: String var question: String
var answer: String? var answer: String?
var previousVersions: [ChatPair] var disabled: Bool
init(question: String, answer: String? = nil, timestamp: Date = Date(), previousVersions: [ChatPair] = []) { init(question: String, answer: String? = nil, timestamp: Date = Date(), disabled: Bool = false) {
self.id = UUID()
self.question = question self.question = question
self.answer = answer self.answer = answer
self.timestamp = timestamp self.timestamp = timestamp
self.previousVersions = previousVersions self.disabled = disabled
} }
} }
@Model @Model
final class ChatHistory: Identifiable { final class ChatHistory {
let id: UUID
var name: String var name: String
var chatPairs: [ChatPair] var chatPairs: [ChatPair]
init(name: String, chatPairs: [ChatPair] = []) { init(name: String, chatPairs: [ChatPair] = []) {
self.id = UUID()
self.name = name self.name = name
self.chatPairs = chatPairs self.chatPairs = chatPairs
} }
@ -42,7 +38,7 @@ final class ChatHistory: Identifiable {
chatPairs.append(newPair) chatPairs.append(newPair)
} }
func editChatPair(withId id: UUID, question: String? = nil, answer: String? = nil) { func editChatPair(withId id: PersistentIdentifier, question: String? = nil, answer: String? = nil) {
guard let index = chatPairs.firstIndex(where: { $0.id == id }) else { return } guard let index = chatPairs.firstIndex(where: { $0.id == id }) else { return }
let newChatPair = chatPairs[index] let newChatPair = chatPairs[index]
newChatPair.previousVersions.append(chatPairs[index]) newChatPair.previousVersions.append(chatPairs[index])
@ -55,4 +51,8 @@ final class ChatHistory: Identifiable {
newChatPair.timestamp = Date() newChatPair.timestamp = Date()
chatPairs[index] = newChatPair chatPairs[index] = newChatPair
} }
func moveChatPair(from source: IndexSet, to destination: Int) {
chatPairs.move(fromOffsets: source, toOffset: destination)
}
} }

View File

@ -55,9 +55,73 @@ struct ContentView: View {
} }
} }
struct ChatPairView: View {
let chatPair: ChatPair
let editAction: () -> Void
let toggleAction: (Bool) -> Void
let dateFormatter: DateFormatter
var body: some View {
VStack(alignment: .leading) {
GroupBox {
VStack(alignment: .leading) {
MarkdownView(text: chatPair.question)
Divider()
if let answer = chatPair.answer {
MarkdownView(text: answer)
}
}
}
.onTapGesture {
editAction()
}
.opacity(chatPair.disabled ? 0.5 : 1)
HStack {
Toggle("", isOn: Binding(
get: { !chatPair.disabled },
set: { toggleAction($0) }))
.toggleStyle(CheckboxToggleStyle())
.labelsHidden()
Text("\(chatPair.timestamp, formatter: dateFormatter)")
.foregroundColor(.secondary)
.font(.footnote)
}
}
.padding(.vertical)
}
}
struct ChatPairEditor: View {
@Binding var chatPair: ChatPair?
@Binding var question: String
@Binding var answer: String
let saveAction: () -> Void
let cancelAction: () -> Void
var body: some View {
VStack {
TextEditor(text: $question)
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.gray))
.frame(maxHeight: .infinity)
TextEditor(text: $answer)
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.gray))
.frame(maxHeight: .infinity)
HStack {
Button(action: cancelAction) {
Image(systemName: "xmark.circle.fill")
}
Button(action: saveAction) {
Image(systemName: "checkmark.circle.fill")
}
}
}
.padding()
}
}
struct ChatHistoryDetailView: View { struct ChatHistoryDetailView: View {
@Environment(\.modelContext) private var modelContext @Environment(\.modelContext) private var modelContext
var chatHistory: ChatHistory @State var chatHistory: ChatHistory
@State private var newQuestion: String = "" @State private var newQuestion: String = ""
@State private var pairToEdit: ChatPair? = nil @State private var pairToEdit: ChatPair? = nil
@State private var newAnswer: String = "" @State private var newAnswer: String = ""
@ -65,94 +129,117 @@ struct ChatHistoryDetailView: View {
var body: some View { var body: some View {
VStack { VStack {
List { List {
ForEach(chatHistory.chatPairs) { chatPair in ForEach(Array(chatHistory.chatPairs.enumerated()), id: \.element) { index, chatPair in
VStack(alignment: .leading) { ChatPairView(chatPair: chatPair,
MarkdownView(text: chatPair.question) editAction: {
if let answer = chatPair.answer { pairToEdit = chatPair
MarkdownView(text: answer) newQuestion = chatPair.question
.tint(.secondary) newAnswer = chatPair.answer ?? ""
} },
Button(action: { toggleAction: { isEnabled in
pairToEdit = chatPair chatPair.disabled = !isEnabled
newAnswer = chatPair.answer ?? "" saveContext()
}) { },
Text("Edit") dateFormatter: itemFormatter)
.foregroundColor(.blue)
}
}
Text("Timestamp: \(chatPair.timestamp)")
.foregroundColor(.secondary)
.font(.footnote)
} }
.onDelete(perform: deleteChatPair)
.onMove(perform: moveChatPair)
} }
HStack { HStack {
TextEditor(text: $newQuestion) TextEditor(text: $newQuestion)
.frame(height: 100) .frame(height: 50)
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.gray)) .overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.gray))
Button(action: { Button(action: {
addChatPair() addChatPair()
}) { }) {
Text("Add") Image(systemName: "plus.circle.fill")
}
Button(action: {
cancelEdit()
}) {
Text("Cancel")
} }
} }
.padding() .padding()
} }
.navigationTitle(chatHistory.name) .navigationTitle(chatHistory.name)
.sheet(item: $pairToEdit) { pairToEdit in .sheet(item: $pairToEdit) { pairToEdit in
VStack { ChatPairEditor(chatPair: $pairToEdit,
Text(pairToEdit.question) question: $newQuestion,
TextEditor(text: $newAnswer) answer: $newAnswer,
.overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.gray)) saveAction: { editChatPair(pairToEdit) },
.frame(maxHeight: .infinity) cancelAction: cancelEdit)
Button(action: {
editChatPair(pairToEdit)
}) {
Text("Save")
}
}
.padding()
} }
} }
private func saveContext() {
do {
try modelContext.save()
} catch {
print("Error saving model context: \(error)")
}
}
private func deleteChatPair(at offsets: IndexSet) {
withAnimation {
offsets.forEach { index in
let chatPair = chatHistory.chatPairs[index]
modelContext.delete(chatPair)
}
saveContext()
}
}
func moveChatPair(from source: IndexSet, to destination: Int) {
withAnimation {
chatHistory.moveChatPair(from: source, to: destination)
saveContext()
}
}
private var itemFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter
}
private func addChatPair() { private func addChatPair() {
guard !newQuestion.isEmpty else { return } guard !newQuestion.isEmpty else { return }
withAnimation { withAnimation {
let newPair = ChatPair(question: newQuestion) let newPair = ChatPair(question: newQuestion)
chatHistory.chatPairs.append(newPair) chatHistory.chatPairs.append(newPair)
newQuestion = "" newQuestion = ""
do { saveContext()
try modelContext.save()
} catch {
print("Error saving model context: \(error)")
}
} }
} }
private func editChatPair(_ chatPair: ChatPair) { private func editChatPair(_ chatPair: ChatPair) {
guard !newAnswer.isEmpty else { return } guard !newAnswer.isEmpty else { return }
withAnimation { withAnimation {
chatHistory.editChatPair(withId: chatPair.id, question: nil, answer: newAnswer) chatHistory.editChatPair(withId: chatPair.id, question: newQuestion, answer: newAnswer)
newAnswer = "" newAnswer = ""
pairToEdit = nil pairToEdit = nil
do { saveContext()
try modelContext.save()
} catch {
print("Error saving model context: \(error)")
}
} }
} }
private func cancelEdit() { private func cancelEdit() {
newQuestion = ""
newAnswer = "" newAnswer = ""
pairToEdit = nil pairToEdit = nil
} }
} }
struct CheckboxToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
HStack {
configuration.label
Spacer()
Image(systemName: configuration.isOn ? "eye" : "eye.slash")
.foregroundColor(configuration.isOn ? .blue : .gray)
.onTapGesture { configuration.isOn.toggle() }
}
}
}
#Preview { #Preview {
ContentView() ContentView()
.modelContainer(for: ChatHistory.self, inMemory: true) .modelContainer(for: ChatHistory.self, inMemory: true)