👨‍💻 dev Eliza swift port

development

fukurou

the supreme coder
ADMIN
Swift:
class LimUniqueResponder {
    private var responses: [String] = []
    private var urg: UniqueRandomGenerator
    private let lim: Int

    // Constructor
    init(lim: Int) {
        self.lim = lim
        self.urg = UniqueRandomGenerator(range: 0)
    }

    // Method to get a response
    func getAResponse() -> String {
        if responses.isEmpty {
            return ""
        }
        return responses[urg.getUniqueRandom()]
    }

    // Method to check if responses contain a string
    func responsesContainsStr(_ item: String) -> Bool {
        return responses.contains(item)
    }

    // Method to check if a string contains any response
    func strContainsResponse(_ item: String) -> Bool {
        for response in responses {
            if response.isEmpty {
                continue
            }
            if item.contains(response) {
                return true
            }
        }
        return false
    }

    // Method to add a response
    func addResponse(_ s1: String) {
        if responses.count > lim - 1 {
            responses.removeFirst()
        }
        if !responses.contains(s1) {
            responses.append(s1)
            urg = UniqueRandomGenerator(range: responses.count)
        }
    }

    func addResponses(_ replies: String...) {
        for value in replies {
            addResponse(value)
        }
    }

    func getSavableStr() -> String {
        return responses.joined(separator: "_")
    }

    func getLastItem() -> String {
        if responses.isEmpty {
            return ""
        }
        return responses.last!
    }
}
 
Last edited:

fukurou

the supreme coder
ADMIN
Swift:
class EventChatV2 {
    private var dic: [String: LimUniqueResponder] = [:]
    private var modifiedKeys: Set<String> = []
    private let lim: Int

    // Constructor
    init(lim: Int) {
        self.lim = lim
    }

    func getModifiedKeys() -> Set<String> {
        return modifiedKeys
    }

    func keyExists(_ key: String) -> Bool {
        // if the key was active true is returned
        return modifiedKeys.contains(key)
    }

    // Add items
    func addItems(_ ur: LimUniqueResponder, _ args: String...) {
        for arg in args {
            dic[arg] = ur
        }
    }

    func addFromDB(_ key: String, _ value: String) {
        if value.isEmpty || value == "null" {
            return
        }
        let tool1 = AXStringSplit()
        let values = tool1.split(value)
        if dic[key] == nil {
            dic[key] = LimUniqueResponder(lim: lim)
        }
        for item in values {
            dic[key]?.addResponse(item)
        }
    }

    // Add key-value pair
    func addKeyValue(_ key: String, _ value: String) {
        modifiedKeys.insert(key)
        if dic[key] != nil {
            dic[key]?.addResponse(value)
        } else {
            dic[key] = LimUniqueResponder(lim: lim)
            dic[key]?.addResponse(value)
        }
    }

    func addKeyValues(_ elizaResults: [AXKeyValuePair]) {
        for pair in elizaResults {
            // Access the key and value of each AXKeyValuePair object
            addKeyValue(pair.getKey(), pair.getValue())
        }
    }

    // Get response
    func response(_ in1: String) -> String {
        return dic[in1]?.getAResponse() ?? ""
    }

    func responseLatest(_ in1: String) -> String {
        return dic[in1]?.getLastItem() ?? ""
    }

    func getSaveStr(_ key: String) -> String {
        return dic[key]?.getSavableStr() ?? ""
    }
}

example usage:
Swift:
let eventChat = EventChatV2(lim: 10)
eventChat.addKeyValue("greeting", "Hello")
eventChat.addKeyValue("greeting", "Hi")
print(eventChat.response("greeting")) // Prints a random response like "Hello" or "Hi"
print(eventChat.responseLatest("greeting")) // Prints the latest response, e.g., "Hi"
 
Last edited:

fukurou

the supreme coder
ADMIN
Swift:
class ElizaDeducer {
    /*
    * this class populates a special chat dictionary
    * based on the matches added via its addPhraseMatcher function
    * see subclass ElizaDeducerInitializer for example:
    * ElizaDeducer ed = new ElizaDeducerInitializer(2); // 2 = limit of replies per input
    */
    var babble2: [PhraseMatcher] = []
    private var patternIndex: [String: [PhraseMatcher]] = [:]
    private var responseCache: [String: [AXKeyValuePair]] = [:]
    private let ec2: EventChatV2 // chat dictionary, use getter for access. hardcoded replies can also be added

    init(lim: Int) {
        self.ec2 = EventChatV2(lim: lim)
    }

    func getEc2() -> EventChatV2 {
        return ec2
    }

    func learn(_ msg: String) {
        // populate EventChat dictionary
        // Check cache first
        if let cachedResponses = responseCache[msg] {
            ec2.addKeyValues(cachedResponses)
        }

        // Search for matching patterns
        let potentialMatchers = getPotentialMatchers(msg)
        for pm in potentialMatchers {
            if pm.matches(msg) {
                let response = pm.respond(msg)
                responseCache[msg] = response
                ec2.addKeyValues(response)
            }
        }
    }

    func learnedBool(_ msg: String) -> Bool {
        // same as learn method but returns true if it learned new replies
        var learned = false
        // populate EventChat dictionary
        // Check cache first
        if let cachedResponses = responseCache[msg] {
            ec2.addKeyValues(cachedResponses)
            learned = true
        }

        // Search for matching patterns
        let potentialMatchers = getPotentialMatchers(msg)
        for pm in potentialMatchers {
            if pm.matches(msg) {
                let response = pm.respond(msg)
                responseCache[msg] = response
                ec2.addKeyValues(response)
                learned = true
            }
        }
        return learned
    }

    func respond(_ str1: String) -> String {
        return ec2.response(str1)
    }

    func respondLatest(_ str1: String) -> String {
        // get most recent reply/data
        return ec2.responseLatest(str1)
    }

    private func getPotentialMatchers(_ msg: String) -> [PhraseMatcher] {
        var potentialMatchers: [PhraseMatcher] = []
        for (key, matchers) in patternIndex {
            if msg.contains(key) {
                potentialMatchers.append(contentsOf: matchers)
            }
        }
        return potentialMatchers
    }

    func addPhraseMatcher(_ pattern: String, _ kvPairs: String...) {
        var kvs: [AXKeyValuePair] = []
        for i in stride(from: 0, to: kvPairs.count, by: 2) {
            kvs.append(AXKeyValuePair(key: kvPairs[i], value: kvPairs[i + 1]))
        }
        let matcher = PhraseMatcher(matcher: pattern, responses: kvs)
        babble2.append(matcher)
        indexPattern(pattern, matcher)
    }

    private func indexPattern(_ pattern: String, _ matcher: PhraseMatcher) {
        for word in pattern.components(separatedBy: .whitespaces) {
            if patternIndex[word] == nil {
                patternIndex[word] = []
            }
            patternIndex[word]?.append(matcher)
        }
    }

    class PhraseMatcher {
        let matcher: NSRegularExpression
        let responses: [AXKeyValuePair]

        init(matcher: String, responses: [AXKeyValuePair]) {
            self.matcher = try! NSRegularExpression(pattern: matcher)
            self.responses = responses
        }

        func matches(_ str: String) -> Bool {
            let range = NSRange(location: 0, length: str.utf16.count)
            return matcher.firstMatch(in: str, options: [], range: range) != nil
        }

        func respond(_ str: String) -> [AXKeyValuePair] {
            let range = NSRange(location: 0, length: str.utf16.count)
            var result: [AXKeyValuePair] = []
            if let match = matcher.firstMatch(in: str, options: [], range: range) {
                let tmp = match.numberOfRanges - 1
                for kv in self.responses {
                    var tempKV = AXKeyValuePair(key: kv.getKey(), value: kv.getValue())
                    for i in 0..<tmp {
                        if let groupRange = Range(match.range(at: i + 1), in: str) {
                            let s = String(str[groupRange])
                            tempKV.setKey(tempKV.getKey().replacingOccurrences(of: "{\(i)}", with: s).lowercased())
                            tempKV.setValue(tempKV.getValue().replacingOccurrences(of: "{\(i)}", with: s).lowercased())
                        }
                    }
                    result.append(tempKV)
                }
            }
            return result
        }
    }
}
example usage:
Swift:
let deducer = ElizaDeducer(lim: 2)
deducer.addPhraseMatcher("hello", "greeting", "Hello there!")
deducer.learn("hello")
print(deducer.respond("greeting")) // Prints "Hello there!"
 

fukurou

the supreme coder
ADMIN
Swift:
class ElizaDeducerInitializer: ElizaDeducer {

    init(lim: Int) {
        // recommended lim = 5; it's the limit of responses per key in the eventchat dictionary
        // the purpose of the lim is to make saving and loading data easier
        super.init(lim: lim)
        initializeBabble2()
    }

    private func initializeBabble2() {
        addPhraseMatcher(
            "(.*) is (.*)",
            "what is {0}", "{0} is {1}",
            "explain {0}", "{0} is {1}"
        )

        addPhraseMatcher(
            "if (.*) or (.*) than (.*)",
            "{0}", "{2}",
            "{1}", "{2}"
        )

        addPhraseMatcher(
            "if (.*) and (.*) than (.*)",
            "{0}", "{1}"
        )

        addPhraseMatcher(
            "(.*) because (.*)",
            "{1}", "i guess {0}"
        )
    }
}

example usage:
Swift:
let deducer = ElizaDeducerInitializer(lim: 5)
deducer.learn("a is b")
print(deducer.respond("what is a")) // Prints "cats are animals"
 

fukurou

the supreme coder
ADMIN
Swift:
class ElizaDBWrapper {
    // this (function wrapper) class adds save load functionality to the ElizaDeducer Object
    /*
        ElizaDeducer ed = new ElizaDeducerInitializer(2);
        ed.getEc2().addFromDB("test","one_two_three"); // manual load for testing
        Kokoro k = new Kokoro(new AbsDictionaryDB()); // use skill's kokoro attribute
        ElizaDBWrapper ew = new ElizaDBWrapper();
        System.out.println(ew.respond("test", ed.getEc2(), k)); // get reply for input, tries loading reply from DB
        System.out.println(ew.respond("test", ed.getEc2(), k)); // doesn't try DB load on second run
        ed.learn("a is b"); // learn only after respond
        ew.sleepNSave(ed.getEc2(), k); // save when bot is sleeping, not on every skill input method visit
    */
    private var modifiedKeys: Set<String> = []

    func respond(_ in1: String, _ ec: EventChatV2, _ kokoro: Kokoro) -> String {
        if modifiedKeys.contains(in1) {
            return ec.response(in1)
        }
        modifiedKeys.insert(in1)
        // load
        ec.addFromDB(in1, kokoro.grimoireMemento.simpleLoad(in1))
        return ec.response(in1)
    }

    func respondLatest(_ in1: String, _ ec: EventChatV2, _ kokoro: Kokoro) -> String {
        if modifiedKeys.contains(in1) {
            return ec.responseLatest(in1)
        }
        modifiedKeys.insert(in1)
        // load and get latest reply for input
        ec.addFromDB(in1, kokoro.grimoireMemento.simpleLoad(in1))
        return ec.responseLatest(in1)
    }

    func sleepNSave(_ ecv2: EventChatV2, _ kokoro: Kokoro) {
        for element in ecv2.getModifiedKeys() {
            kokoro.grimoireMemento.simpleSave(element, ecv2.getSaveStr(element))
        }
    }
}

usage:
Swift:
let ed = ElizaDeducerInitializer(lim: 2)
ed.getEc2().addFromDB("test", "one_two_three") // manual load for testing
let k = Kokoro(grimoireMemento: AbsDictionaryDB()) // use skill's kokoro attribute
let ew = ElizaDBWrapper()
print(ew.respond("test", ed.getEc2(), k)) // get reply for input, tries loading reply from DB
print(ew.respond("test", ed.getEc2(), k)) // doesn't try DB load on second run
ed.learn("a is b") // learn only after respond
ew.sleepNSave(ed.getEc2(), k) // save when bot is sleeping, not on every skill input method visit
 
Top