Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
KoboldAI
GitHub Repository: KoboldAI/KoboldAI-Client
Path: blob/main/userscripts/kaipreset_location_scanner.lua
473 views
1
-- Location scanner
2
-- Activates world info entries based on what the AI thinks the current location
3
-- is.
4
5
-- This file is part of KoboldAI.
6
--
7
-- KoboldAI is free software: you can redistribute it and/or modify
8
-- it under the terms of the GNU Affero General Public License as published by
9
-- the Free Software Foundation, either version 3 of the License, or
10
-- (at your option) any later version.
11
--
12
-- This program is distributed in the hope that it will be useful,
13
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
14
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
-- GNU Affero General Public License for more details.
16
--
17
-- You should have received a copy of the GNU Affero General Public License
18
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
19
20
kobold = require("bridge")() -- This line is optional and is only for EmmyLua type annotations
21
local userscript = {} ---@class KoboldUserScript
22
23
24
local example_config = [[;-- Location scanner
25
;--
26
;-- Usage instructions:
27
;--
28
;-- 1. Create a world info folder with name containing the string
29
;-- "<||ls||>" (without the double quotes). The name can be anything as
30
;-- long as it contains that inside it somewhere -- for example, you could
31
;-- set the name to "Locations <||ls||>".
32
;--
33
;-- 2. Create a non-selective, constant world info key _in that folder_ with key
34
;-- "<||lslocation||>" (without the double quotes). Every once in a while,
35
;-- this script will generate 20 tokens using "The current location"
36
;-- as the submission and save the output into the <||lslocation||> entry.
37
;--
38
;-- 3. Put some other world info entries into the world info folder. These
39
;-- entries will _only_ be triggered by the contents of the <||lslocation||>
40
;-- entry and not by your story itself, or if it has constant key turned on.
41
;--
42
;-- You can edit some of the configuration values below to modify some of this
43
;-- behaviour:
44
;--
45
return {
46
location_folder = "<||ls||>",
47
location_key = "<||lslocation||>",
48
submission = "\n\nThe current location:",
49
n_wait = 12, -- The script will run its extra generation every time your story grows by this many chunks.
50
n_tokens = 20, -- Number of tokens to generate in extra generation
51
singleline = true, -- true or false; true will result in the extra generation's output being cut off after the first line.
52
trim = true, -- true or false; true will result in the extra generation's output being cut off after the end of its last sentence.
53
include = false, -- true or false; true will result in the <||lslocation||> entry's content being included in the story.
54
template = "<|>", -- Allows you to format the extra generation's output; for example, to surround the output in square brackets, set this to "[<|>]"
55
}
56
]]
57
58
local cfg ---@type table<string, any>
59
do
60
-- If config file is empty, write example config
61
local f <close> = kobold.get_config_file()
62
f:seek("set")
63
if f:read(1) == nil then
64
f:write(example_config)
65
end
66
f:seek("set")
67
example_config = nil
68
69
-- Read config
70
local err
71
cfg, err = load(f:read("a"))
72
if err ~= nil then
73
error(err)
74
end
75
cfg = cfg()
76
end
77
78
if cfg.include == nil then
79
cfg.include = false
80
elseif cfg.template == nil then
81
cfg.template = "<|>"
82
end
83
84
85
local folder ---@type KoboldWorldInfoFolder|nil
86
local entry ---@type KoboldWorldInfoEntry|nil
87
local location = ""
88
local orig_entry_map = {} ---@type table<integer, KoboldWorldInfoEntry>
89
local repeated = false
90
local last_quotient = math.huge
91
92
local genamt = 0
93
94
function userscript.inmod()
95
if repeated then
96
kobold.submission = cfg.submission
97
genamt = kobold.settings.genamt
98
kobold.settings.genamt = cfg.n_tokens
99
end
100
101
if entry == nil or folder == nil or not entry:is_valid() or not folder:is_valid() then
102
folder = nil
103
entry = nil
104
for i, f in ipairs(kobold.worldinfo.folders) do
105
if f.name:find(cfg.location_folder, 1, true) ~= nil then
106
folder = f
107
break
108
end
109
end
110
if folder ~= nil then
111
for i, e in ipairs(folder) do
112
if e.key:find(cfg.location_key, 1, true) ~= nil then
113
entry = e
114
break
115
end
116
end
117
end
118
end
119
120
orig_entry_map = {}
121
122
if entry ~= nil then
123
location = entry.content
124
entry.constant = not not cfg.include
125
end
126
127
if folder ~= nil then
128
for i, e in ipairs(folder) do
129
if entry == nil or e.uid ~= entry.uid then
130
orig_entry_map[e.uid] = {
131
constant = e.constant,
132
key = e.key,
133
keysecondary = e.keysecondary,
134
}
135
e.constant = e.constant or (not repeated and e:compute_context("", {scan_story=false}) ~= e:compute_context(location, {scan_story=false}))
136
e.key = ""
137
e.keysecondary = ""
138
end
139
end
140
end
141
end
142
143
function userscript.outmod()
144
if entry ~= nil and entry:is_valid() then
145
entry.constant = true
146
end
147
148
if repeated then
149
local output = kobold.outputs[1]
150
kobold.outputs[1] = ""
151
152
for chunk in kobold.story:reverse_iter() do
153
if chunk.content ~= "" then
154
chunk.content = ""
155
break
156
end
157
end
158
159
kobold.settings.genamt = genamt
160
161
output = output:match("^%s*(.*)%s*$")
162
163
print("Extra generation result (prior to formatting): " .. output)
164
165
if cfg.singleline then
166
output = output:match("^[^\n]*")
167
end
168
169
if cfg.trim then
170
local i = 0
171
while true do
172
local j = output:find("[.?!)]", i + 1)
173
if j == nil then
174
break
175
end
176
i = j
177
end
178
if i > 0 then
179
if output:sub(i+1, i+1) == '"' then
180
i = i + 1
181
end
182
output = output:sub(1, i)
183
end
184
end
185
186
location = cfg.template:gsub("<|>", output)
187
188
print("Extra generation result (after formatting): " .. location)
189
190
if entry ~= nil and entry:is_valid() then
191
entry.content = location
192
end
193
end
194
195
local size = 0
196
for _ in kobold.story:forward_iter() do
197
size = size + 1
198
end
199
200
for uid, orig in pairs(orig_entry_map) do
201
for k, v in pairs(orig) do
202
kobold.worldinfo:finduid(uid)[k] = v
203
end
204
end
205
206
local quotient = math.floor(size / cfg.n_wait)
207
if repeated then
208
repeated = false
209
elseif quotient > last_quotient then
210
print("Running extra generation")
211
kobold.restart_generation()
212
repeated = true
213
end
214
last_quotient = quotient
215
end
216
217
return userscript
218
219