mirror of
https://github.com/KoboldAI/KoboldAI-Client.git
synced 2025-06-05 21:59:24 +02:00
Merge branch 'UI2' of https://github.com/ebolam/KoboldAI into UI2
This commit is contained in:
466
aiserver.py
466
aiserver.py
@@ -7,6 +7,7 @@
|
||||
|
||||
# External packages
|
||||
from dataclasses import dataclass
|
||||
import shutil
|
||||
import eventlet
|
||||
eventlet.monkey_patch(all=True, thread=False, os=False)
|
||||
import os, inspect
|
||||
@@ -509,6 +510,9 @@ class ImportBuffer:
|
||||
refresh_story()
|
||||
|
||||
import_buffer = ImportBuffer()
|
||||
|
||||
with open("data/genres.json", "r") as file:
|
||||
genre_list = json.load(file)
|
||||
|
||||
# Set logging level to reduce chatter from Flask
|
||||
import logging
|
||||
@@ -7232,28 +7236,34 @@ def loadfromfile():
|
||||
def loadRequest(loadpath, filename=None):
|
||||
logger.debug("Load Request")
|
||||
logger.debug("Called from {}".format(inspect.stack()[1].function))
|
||||
|
||||
if not loadpath:
|
||||
return
|
||||
|
||||
if os.path.isdir(loadpath):
|
||||
if not valid_v3_story(loadpath):
|
||||
raise RuntimeError(f"Tried to load {loadpath}, a non-save directory.")
|
||||
loadpath = os.path.join(loadpath, "story.json")
|
||||
|
||||
start_time = time.time()
|
||||
if(loadpath):
|
||||
# Leave Edit/Memory mode before continuing
|
||||
exitModes()
|
||||
|
||||
|
||||
# Read file contents into JSON object
|
||||
start_time = time.time()
|
||||
if(isinstance(loadpath, str)):
|
||||
with open(loadpath, "r") as file:
|
||||
js = json.load(file)
|
||||
if(filename is None):
|
||||
filename = path.basename(loadpath)
|
||||
else:
|
||||
js = loadpath
|
||||
if(filename is None):
|
||||
filename = "untitled.json"
|
||||
js['v1_loadpath'] = loadpath
|
||||
js['v1_filename'] = filename
|
||||
logger.debug("Loading JSON data took {}s".format(time.time()-start_time))
|
||||
loadJSON(js)
|
||||
logger.debug("Time to load story: {}s".format(time.time()-start_time))
|
||||
# Leave Edit/Memory mode before continuing
|
||||
exitModes()
|
||||
|
||||
# Read file contents into JSON object
|
||||
start_time = time.time()
|
||||
if(isinstance(loadpath, str)):
|
||||
with open(loadpath, "r") as file:
|
||||
js = json.load(file)
|
||||
if(filename is None):
|
||||
filename = path.basename(loadpath)
|
||||
else:
|
||||
js = loadpath
|
||||
if(filename is None):
|
||||
filename = "untitled.json"
|
||||
js['v1_loadpath'] = loadpath
|
||||
js['v1_filename'] = filename
|
||||
logger.debug("Loading JSON data took {}s".format(time.time()-start_time))
|
||||
loadJSON(js)
|
||||
|
||||
def loadJSON(json_text_or_dict):
|
||||
logger.debug("Loading JSON Story")
|
||||
@@ -7925,6 +7935,31 @@ def upload_file(data):
|
||||
f.write(data['data'])
|
||||
get_files_folders(session['current_folder'])
|
||||
|
||||
@app.route("/upload_kai_story/<string:file_name>", methods=["POST"])
|
||||
@logger.catch
|
||||
def UI_2_upload_kai_story(file_name: str):
|
||||
|
||||
assert "/" not in file_name
|
||||
|
||||
raw_folder_name = file_name.replace(".kaistory", "")
|
||||
folder_path = path.join("stories", raw_folder_name)
|
||||
disambiguator = 0
|
||||
|
||||
while path.exists(folder_path):
|
||||
disambiguator += 1
|
||||
folder_path = path.join("stories", f"{raw_folder_name} ({disambiguator})")
|
||||
|
||||
buffer = BytesIO()
|
||||
dat = request.get_data()
|
||||
with open("debug.zip", "wb") as file:
|
||||
file.write(dat)
|
||||
buffer.write(dat)
|
||||
|
||||
with zipfile.ZipFile(buffer, "r") as zipf:
|
||||
zipf.extractall(folder_path)
|
||||
|
||||
return ":)"
|
||||
|
||||
@socketio.on('popup_change_folder')
|
||||
@logger.catch
|
||||
def popup_change_folder(data):
|
||||
@@ -7965,38 +8000,46 @@ def popup_rename(data):
|
||||
@logger.catch
|
||||
def popup_rename_story(data):
|
||||
if 'popup_renameable' not in session:
|
||||
print("Someone is trying to rename a file in your server. Blocked.")
|
||||
logger.warning("Someone is trying to rename a file in your server. Blocked.")
|
||||
return
|
||||
if not session['popup_renameable']:
|
||||
print("Someone is trying to rename a file in your server. Blocked.")
|
||||
logger.warning("Someone is trying to rename a file in your server. Blocked.")
|
||||
return
|
||||
|
||||
if session['popup_jailed_dir'] is None:
|
||||
#if we're using a v2 file we can't just rename the file as the story name is in the file
|
||||
with open(data['file'], 'r') as f:
|
||||
json_data = json.load(f)
|
||||
if 'story_name' in json_data:
|
||||
json_data['story_name'] = data['new_name']
|
||||
|
||||
new_filename = os.path.join(os.path.dirname(os.path.abspath(data['file'])), data['new_name']+".json")
|
||||
os.remove(data['file'])
|
||||
with open(new_filename, "w") as f:
|
||||
json.dump(json_data, f)
|
||||
get_files_folders(os.path.dirname(data['file']))
|
||||
elif session['popup_jailed_dir'] in data['file']:
|
||||
#if we're using a v2 file we can't just rename the file as the story name is in the file
|
||||
with open(data['file'], 'r') as f:
|
||||
json_data = json.load(f)
|
||||
if 'story_name' in json_data:
|
||||
json_data['story_name'] = data['new_name']
|
||||
|
||||
new_filename = os.path.join(os.path.dirname(os.path.abspath(data['file'])), data['new_name']+".json")
|
||||
os.remove(data['file'])
|
||||
with open(new_filename, "w") as f:
|
||||
json.dump(json_data, f)
|
||||
get_files_folders(os.path.dirname(data['file']))
|
||||
if session['popup_jailed_dir'] and session["popup_jailed_dir"] not in data["file"]:
|
||||
logger.warning("User is trying to rename files in your server outside the jail. Blocked. Jailed Dir: {} Requested Dir: {}".format(session['popup_jailed_dir'], data['file']))
|
||||
return
|
||||
|
||||
path = data["file"]
|
||||
new_name = data["new_name"]
|
||||
json_path = path
|
||||
is_v3 = False
|
||||
|
||||
# Handle directory for v3 save
|
||||
if os.path.isdir(path):
|
||||
if not valid_v3_story(path):
|
||||
return
|
||||
is_v3 = True
|
||||
json_path = os.path.join(path, "story.json")
|
||||
|
||||
#if we're using a v2 file we can't just rename the file as the story name is in the file
|
||||
with open(json_path, 'r') as f:
|
||||
json_data = json.load(f)
|
||||
if 'story_name' in json_data:
|
||||
json_data['story_name'] = new_name
|
||||
|
||||
# For v3 we move the directory, not the json file.
|
||||
if is_v3:
|
||||
target = os.path.join(os.path.dirname(path), new_name)
|
||||
shutil.move(path, target)
|
||||
|
||||
with open(os.path.join(target, "story.json"), "w") as file:
|
||||
json.dump(json_data, file)
|
||||
else:
|
||||
print("User is trying to rename files in your server outside the jail. Blocked. Jailed Dir: {} Requested Dir: {}".format(session['popup_jailed_dir'], data['file']))
|
||||
new_filename = os.path.join(os.path.dirname(os.path.abspath(data['file'])), new_name+".json")
|
||||
os.remove(data['file'])
|
||||
with open(new_filename, "w") as f:
|
||||
json.dump(json_data, f)
|
||||
get_files_folders(os.path.dirname(path))
|
||||
|
||||
@socketio.on('popup_delete')
|
||||
@logger.catch
|
||||
@@ -8159,10 +8202,12 @@ def get_files_folders(starting_folder):
|
||||
folders = []
|
||||
files = []
|
||||
base_path = os.path.abspath(starting_folder).replace("\\", "/")
|
||||
|
||||
if advanced_sort is not None:
|
||||
files_to_check = advanced_sort(base_path, desc=desc)
|
||||
else:
|
||||
files_to_check = get_files_sorted(base_path, sort, desc=desc)
|
||||
|
||||
for item in files_to_check:
|
||||
item_full_path = os.path.join(base_path, item).replace("\\", "/")
|
||||
if hasattr(os.stat(item_full_path), "st_file_attributes"):
|
||||
@@ -8179,8 +8224,15 @@ def get_files_folders(starting_folder):
|
||||
extra_parameters = extra_parameter_function(item_full_path, item, valid_selection)
|
||||
|
||||
if (show_hidden and hidden) or not hidden:
|
||||
if os.path.isdir(os.path.join(base_path, item)):
|
||||
folders.append([True, item_full_path, item, valid_selection, extra_parameters])
|
||||
if os.path.isdir(item_full_path):
|
||||
folders.append([
|
||||
# While v3 saves are directories, we should not show them as such.
|
||||
not valid_v3_story(item_full_path),
|
||||
item_full_path,
|
||||
item,
|
||||
valid_selection,
|
||||
extra_parameters
|
||||
])
|
||||
else:
|
||||
if hide_extention:
|
||||
item = ".".join(item.split(".")[:-1])
|
||||
@@ -8320,18 +8372,55 @@ def UI_2_save_story(data):
|
||||
else:
|
||||
#We have an ack that it's OK to save over the file if one exists
|
||||
koboldai_vars.save_story()
|
||||
|
||||
|
||||
def directory_to_zip_data(directory: str, overrides: Optional[dict]) -> bytes:
|
||||
overrides = overrides or {}
|
||||
buffer = BytesIO()
|
||||
|
||||
with zipfile.ZipFile(buffer, "w") as zipf:
|
||||
for root, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
p = os.path.join(root, file)
|
||||
z_path = os.path.join(*p.split(os.path.sep)[2:])
|
||||
|
||||
if z_path in overrides:
|
||||
continue
|
||||
|
||||
zipf.write(p, z_path)
|
||||
|
||||
for path, contents in overrides.items():
|
||||
zipf.writestr(path, contents)
|
||||
|
||||
return buffer.getvalue()
|
||||
|
||||
#==================================================================#
|
||||
# Save story to json
|
||||
#==================================================================#
|
||||
@app.route("/json")
|
||||
@app.route("/story_download")
|
||||
@logger.catch
|
||||
def UI_2_save_to_json():
|
||||
def UI_2_download_story():
|
||||
save_exists = path.exists(koboldai_vars.save_paths.base)
|
||||
if koboldai_vars.gamesaved and save_exists:
|
||||
# Disk is up to date; download from disk
|
||||
data = directory_to_zip_data(koboldai_vars.save_paths.base)
|
||||
elif save_exists:
|
||||
# We aren't up to date but we are saved; patch what disk gives us
|
||||
data = directory_to_zip_data(
|
||||
koboldai_vars.save_paths.base,
|
||||
{"story.json": koboldai_vars.to_json("story_settings")}
|
||||
)
|
||||
else:
|
||||
# We are not saved; send json in zip from memory
|
||||
buffer = BytesIO()
|
||||
with zipfile.ZipFile(buffer, "w") as zipf:
|
||||
zipf.writestr("story.json", koboldai_vars.to_json("story_settings"))
|
||||
data = buffer.getvalue()
|
||||
|
||||
return Response(
|
||||
koboldai_vars.to_json('story_settings'),
|
||||
mimetype="application/json",
|
||||
headers={"Content-disposition":
|
||||
"attachment; filename={}.v2.json".format(koboldai_vars.story_name)})
|
||||
data,
|
||||
mimetype="application/octet-stream",
|
||||
headers={"Content-disposition": f"attachment; filename={koboldai_vars.story_name}.kaistory"}
|
||||
)
|
||||
|
||||
|
||||
#==================================================================#
|
||||
@@ -8523,6 +8612,11 @@ def get_story_listing_data(item_full_path, item, valid_selection):
|
||||
|
||||
if not valid_selection:
|
||||
return [title, action_count, last_loaded]
|
||||
|
||||
if os.path.isdir(item_full_path):
|
||||
if not valid_v3_story(item_full_path):
|
||||
return [title, action_count, last_loaded]
|
||||
item_full_path = os.path.join(item_full_path, "story.json")
|
||||
|
||||
with open(item_full_path, 'rb') as f:
|
||||
parse_event = ijson.parse(f)
|
||||
@@ -8574,38 +8668,68 @@ def get_story_listing_data(item_full_path, item, valid_selection):
|
||||
return [title, action_count, last_loaded]
|
||||
|
||||
@logger.catch
|
||||
def valid_story(file):
|
||||
if file.endswith(".json"):
|
||||
try:
|
||||
valid = False
|
||||
with open(file, 'rb') as f:
|
||||
parser = ijson.parse(f)
|
||||
for prefix, event, value in parser:
|
||||
if prefix == 'memory':
|
||||
valid=True
|
||||
break
|
||||
except:
|
||||
pass
|
||||
return valid
|
||||
def valid_story(path: str):
|
||||
if os.path.isdir(path):
|
||||
return valid_v3_story(path)
|
||||
|
||||
if not path.endswith(".json"):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(path, 'rb') as file:
|
||||
parser = ijson.parse(file)
|
||||
for prefix, event, value in parser:
|
||||
if prefix == 'memory':
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
@logger.catch
|
||||
def valid_v3_story(path: str) -> bool:
|
||||
if not os.path.exists(path): return False
|
||||
if not os.path.isdir(path): return False
|
||||
if not os.path.exists(os.path.join(path, "story.json")): return False
|
||||
return True
|
||||
|
||||
@logger.catch
|
||||
def story_sort(base_path, desc=False):
|
||||
files = {}
|
||||
for file in os.scandir(path=base_path):
|
||||
if file.name.endswith(".json"):
|
||||
filename = os.path.join(base_path, file.name).replace("\\", "/")
|
||||
if os.path.getsize(filename) < 2*1024*1024: #2MB
|
||||
with open(filename, "r") as f:
|
||||
try:
|
||||
js = json.load(f)
|
||||
if 'story_name' in js and js['story_name'] in koboldai_vars.story_loads:
|
||||
files[file.name] = datetime.datetime.strptime(koboldai_vars.story_loads[js['story_name']], "%m/%d/%Y, %H:%M:%S")
|
||||
else:
|
||||
files[file.name] = datetime.datetime.fromtimestamp(file.stat().st_mtime)
|
||||
except:
|
||||
pass
|
||||
if file.is_dir():
|
||||
if not valid_v3_story(file.path):
|
||||
continue
|
||||
|
||||
story_path = os.path.join(file.path, "story.json")
|
||||
story_stat = os.stat(story_path)
|
||||
|
||||
if os.path.getsize(story_path) < 2*1024*1024: #2MB
|
||||
with open(story_path, "r") as f:
|
||||
j = json.load(f)
|
||||
if j.get("story_name") in koboldai_vars.story_loads:
|
||||
files[file.name] = datetime.datetime.strptime(koboldai_vars.story_loads[j["story_name"]], "%m/%d/%Y, %H:%M:%S")
|
||||
else:
|
||||
files[file.name] = datetime.datetime.fromtimestamp(story_stat.st_mtime)
|
||||
else:
|
||||
files[file.name] = datetime.datetime.fromtimestamp(file.stat().st_mtime)
|
||||
files[file.name] = datetime.datetime.fromtimestamp(story_stat.st_mtime)
|
||||
continue
|
||||
|
||||
if not file.name.endswith(".json"):
|
||||
continue
|
||||
|
||||
filename = os.path.join(base_path, file.name).replace("\\", "/")
|
||||
if os.path.getsize(filename) < 2*1024*1024: #2MB
|
||||
with open(filename, "r") as f:
|
||||
try:
|
||||
js = json.load(f)
|
||||
if 'story_name' in js and js['story_name'] in koboldai_vars.story_loads:
|
||||
files[file.name] = datetime.datetime.strptime(koboldai_vars.story_loads[js['story_name']], "%m/%d/%Y, %H:%M:%S")
|
||||
else:
|
||||
files[file.name] = datetime.datetime.fromtimestamp(file.stat().st_mtime)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
files[file.name] = datetime.datetime.fromtimestamp(file.stat().st_mtime)
|
||||
return [key[0] for key in sorted(files.items(), key=lambda kv: (kv[1], kv[0]), reverse=desc)]
|
||||
|
||||
|
||||
@@ -8843,6 +8967,7 @@ def UI_2_set_wi_image(uid):
|
||||
else:
|
||||
# Otherwise assign image
|
||||
koboldai_vars.worldinfo_v2.image_store[uid] = data
|
||||
koboldai_vars.gamesaved = False
|
||||
return ":)"
|
||||
|
||||
@app.route("/get_wi_image/<int(signed=True):uid>", methods=["GET"])
|
||||
@@ -9189,8 +9314,10 @@ def UI_2_generate_image_from_story(data):
|
||||
#get latest action
|
||||
if len(koboldai_vars.actions) > 0:
|
||||
action = koboldai_vars.actions[-1]
|
||||
action_id = len(koboldai_vars.actions) - 1
|
||||
else:
|
||||
action = koboldai_vars.prompt
|
||||
action_id = -1
|
||||
#Get matching world info entries
|
||||
keys = []
|
||||
for wi in koboldai_vars.worldinfo_v2:
|
||||
@@ -9231,64 +9358,122 @@ def UI_2_generate_image_from_story(data):
|
||||
keys = [summarize(text, max_length=max_length)]
|
||||
logger.debug("Text from summarizer: {}".format(keys[0]))
|
||||
|
||||
generate_story_image(", ".join(keys), art_guide=art_guide)
|
||||
prompt = ", ".join(keys)
|
||||
generate_story_image(
|
||||
", ".join([part for part in [prompt, art_guide] if part]),
|
||||
file_prefix=f"action_{action_id}",
|
||||
display_prompt=prompt,
|
||||
log_data={"actionId": action_id}
|
||||
)
|
||||
|
||||
@socketio.on("generate_image_from_prompt")
|
||||
@logger.catch
|
||||
def UI_2_generate_image_from_prompt(prompt: str):
|
||||
eventlet.sleep(0)
|
||||
generate_story_image(prompt)
|
||||
generate_story_image(prompt, file_prefix="prompt", generation_type="direct_prompt")
|
||||
|
||||
def generate_story_image(prompt: str, art_guide: str = "") -> None:
|
||||
def log_image_generation(
|
||||
prompt: str,
|
||||
display_prompt: str,
|
||||
file_name: str,
|
||||
generation_type: str,
|
||||
other_data: Optional[dict] = None
|
||||
) -> None:
|
||||
# In the future it might be nice to have some UI where you can search past
|
||||
# generations or something like that
|
||||
db_path = os.path.join(koboldai_vars.save_paths.generated_images, "db.json")
|
||||
|
||||
try:
|
||||
with open(db_path, "r") as file:
|
||||
j = json.load(file)
|
||||
except FileNotFoundError:
|
||||
j = []
|
||||
|
||||
if not isinstance(j, list):
|
||||
logger.warning("Image database is corrupted! Will not add new entry.")
|
||||
return
|
||||
|
||||
|
||||
log_data = {
|
||||
"prompt": prompt,
|
||||
"fileName": file_name,
|
||||
"type": generation_type or None,
|
||||
"displayPrompt": display_prompt
|
||||
}
|
||||
log_data.update(other_data or {})
|
||||
j.append(log_data)
|
||||
|
||||
with open(db_path, "w") as file:
|
||||
json.dump(j, file)
|
||||
|
||||
def generate_story_image(
|
||||
prompt: str,
|
||||
file_prefix: str = "image",
|
||||
generation_type: str = "",
|
||||
display_prompt: Optional[str] = None,
|
||||
log_data: Optional[dict] = None
|
||||
|
||||
) -> None:
|
||||
# This function is a wrapper around generate_image() that integrates the
|
||||
# result with the story (read: puts it in the corner of the screen).
|
||||
|
||||
if not display_prompt:
|
||||
display_prompt = prompt
|
||||
koboldai_vars.picture_prompt = display_prompt
|
||||
|
||||
start_time = time.time()
|
||||
koboldai_vars.generating_image = True
|
||||
|
||||
b64_data = generate_image(prompt, art_guide=art_guide)
|
||||
image = generate_image(prompt)
|
||||
koboldai_vars.generating_image = False
|
||||
|
||||
if not image:
|
||||
return
|
||||
|
||||
if os.path.exists(koboldai_vars.save_paths.generated_images):
|
||||
# Only save image if this is a saved story
|
||||
file_name = f"{file_prefix}_{int(time.time())}.png"
|
||||
image.save(os.path.join(koboldai_vars.save_paths.generated_images, file_name))
|
||||
log_image_generation(prompt, display_prompt, file_name, generation_type, log_data)
|
||||
|
||||
logger.debug("Time to Generate Image {}".format(time.time()-start_time))
|
||||
|
||||
koboldai_vars.picture = b64_data
|
||||
koboldai_vars.picture_prompt = prompt
|
||||
koboldai_vars.generating_image = False
|
||||
buffer = BytesIO()
|
||||
image.save(buffer, format="JPEG")
|
||||
b64_data = base64.b64encode(buffer.getvalue()).decode("ascii")
|
||||
|
||||
def generate_image(prompt: str, art_guide: str = "") -> Optional[str]:
|
||||
koboldai_vars.picture = b64_data
|
||||
|
||||
|
||||
def generate_image(prompt: str) -> Optional[Image.Image]:
|
||||
if koboldai_vars.img_gen_priority == 4:
|
||||
# Check if stable-diffusion-webui API option selected and use that if found.
|
||||
return text2img_api(prompt, art_guide=art_guide)
|
||||
return text2img_api(prompt)
|
||||
elif ((not koboldai_vars.hascuda or not os.path.exists("models/stable-diffusion-v1-4")) and koboldai_vars.img_gen_priority != 0) or koboldai_vars.img_gen_priority == 3:
|
||||
# If we don't have a GPU, use horde if we're allowed to
|
||||
return text2img_horde(prompt, art_guide=art_guide)
|
||||
return text2img_horde(prompt)
|
||||
|
||||
memory = torch.cuda.get_device_properties(0).total_memory
|
||||
|
||||
# We aren't being forced to use horde, so now let's figure out if we should use local
|
||||
if memory - torch.cuda.memory_reserved(0) >= 6000000000:
|
||||
# We have enough vram, just do it locally
|
||||
return text2img_local(prompt, art_guide=art_guide)
|
||||
return text2img_local(prompt)
|
||||
elif memory > 6000000000 and koboldai_vars.img_gen_priority <= 1:
|
||||
# We could do it locally by swapping the model out
|
||||
print("Could do local or online")
|
||||
return text2img_horde(prompt, art_guide=art_guide)
|
||||
return text2img_horde(prompt)
|
||||
elif koboldai_vars.img_gen_priority != 0:
|
||||
return text2img_horde(prompt, art_guide=art_guide)
|
||||
return text2img_horde(prompt)
|
||||
|
||||
raise RuntimeError("Unable to decide image generation backend. Please report this.")
|
||||
|
||||
|
||||
@logger.catch
|
||||
def text2img_local(prompt,
|
||||
art_guide="",
|
||||
filename="new.png"):
|
||||
def text2img_local(prompt: str) -> Optional[Image.Image]:
|
||||
start_time = time.time()
|
||||
logger.debug("Generating Image")
|
||||
koboldai_vars.aibusy = True
|
||||
koboldai_vars.generating_image = True
|
||||
from diffusers import StableDiffusionPipeline
|
||||
import base64
|
||||
from io import BytesIO
|
||||
if koboldai_vars.image_pipeline is None:
|
||||
pipe = tpool.execute(StableDiffusionPipeline.from_pretrained, "CompVis/stable-diffusion-v1-4", revision="fp16", torch_dtype=torch.float16, cache="models/stable-diffusion-v1-4").to("cuda")
|
||||
else:
|
||||
@@ -9301,9 +9486,6 @@ def text2img_local(prompt,
|
||||
with autocast("cuda"):
|
||||
return pipe(prompt, num_inference_steps=num_inference_steps).images[0]
|
||||
image = tpool.execute(get_image, pipe, prompt, num_inference_steps=koboldai_vars.img_gen_steps)
|
||||
buffered = BytesIO()
|
||||
image.save(buffered, format="JPEG")
|
||||
img_str = base64.b64encode(buffered.getvalue()).decode('ascii')
|
||||
logger.debug("time to generate: {}".format(time.time() - start_time))
|
||||
start_time = time.time()
|
||||
if koboldai_vars.keep_img_gen_in_memory:
|
||||
@@ -9314,61 +9496,52 @@ def text2img_local(prompt,
|
||||
koboldai_vars.image_pipeline = None
|
||||
del pipe
|
||||
torch.cuda.empty_cache()
|
||||
koboldai_vars.generating_image = False
|
||||
koboldai_vars.aibusy = False
|
||||
logger.debug("time to unload: {}".format(time.time() - start_time))
|
||||
return img_str
|
||||
return image
|
||||
|
||||
@logger.catch
|
||||
def text2img_horde(prompt,
|
||||
art_guide = "",
|
||||
filename = "story_art.png"):
|
||||
def text2img_horde(prompt: str) -> Optional[Image.Image]:
|
||||
logger.debug("Generating Image using Horde")
|
||||
koboldai_vars.generating_image = True
|
||||
|
||||
|
||||
final_submit_dict = {
|
||||
"prompt": "{}, {}".format(prompt, art_guide),
|
||||
"prompt": prompt,
|
||||
"trusted_workers": False,
|
||||
"models": [
|
||||
"stable_diffusion"
|
||||
],
|
||||
"params": {
|
||||
"n":1,
|
||||
"n": 1,
|
||||
"nsfw": True,
|
||||
"sampler_name": "k_euler_a",
|
||||
"karras": True,
|
||||
"cfg_scale": koboldai_vars.img_gen_cfg_scale,
|
||||
"steps":koboldai_vars.img_gen_steps,
|
||||
"width":512,
|
||||
"height":512}
|
||||
"steps": koboldai_vars.img_gen_steps,
|
||||
"width": 512,
|
||||
"height": 512
|
||||
}
|
||||
}
|
||||
|
||||
cluster_headers = {'apikey': koboldai_vars.sh_apikey if koboldai_vars.sh_apikey != '' else "0000000000",}
|
||||
|
||||
logger.debug(final_submit_dict)
|
||||
submit_req = requests.post('https://stablehorde.net/api/v2/generate/sync', json = final_submit_dict, headers=cluster_headers)
|
||||
if submit_req.ok:
|
||||
results = submit_req.json()
|
||||
for iter in range(len(results['generations'])):
|
||||
b64img = results['generations'][iter]["img"]
|
||||
base64_bytes = b64img.encode('utf-8')
|
||||
img_bytes = base64.b64decode(base64_bytes)
|
||||
img = Image.open(BytesIO(img_bytes))
|
||||
if len(results) > 1:
|
||||
final_filename = f"{iter}_{filename}"
|
||||
else:
|
||||
final_filename = filename
|
||||
img.save(final_filename)
|
||||
logger.debug("Saved Image")
|
||||
koboldai_vars.generating_image = False
|
||||
return(b64img)
|
||||
else:
|
||||
koboldai_vars.generating_image = False
|
||||
submit_req = requests.post('https://stablehorde.net/api/v2/generate/sync', json=final_submit_dict, headers=cluster_headers)
|
||||
|
||||
if not submit_req.ok:
|
||||
logger.error(submit_req.text)
|
||||
return
|
||||
|
||||
results = submit_req.json()
|
||||
if len(results["generations"]) > 1:
|
||||
logger.warning(f"Got too many generations, discarding extras. Got {len(results['generations'])}, expected 1.")
|
||||
|
||||
b64img = results["generations"][0]["img"]
|
||||
base64_bytes = b64img.encode("utf-8")
|
||||
img_bytes = base64.b64decode(base64_bytes)
|
||||
img = Image.open(BytesIO(img_bytes))
|
||||
return img
|
||||
|
||||
@logger.catch
|
||||
def text2img_api(prompt, art_guide=""):
|
||||
def text2img_api(prompt, art_guide="") -> Image.Image:
|
||||
logger.debug("Generating Image using Local SD-WebUI API")
|
||||
koboldai_vars.generating_image = True
|
||||
#The following list are valid properties with their defaults, to add/modify in final_imgen_params. Will refactor configuring values into UI element in future.
|
||||
@@ -9402,7 +9575,7 @@ def text2img_api(prompt, art_guide=""):
|
||||
#"override_settings": {},
|
||||
#"sampler_index": "Euler"
|
||||
final_imgen_params = {
|
||||
"prompt": ", ".join(filter(bool, [prompt, art_guide])),
|
||||
"prompt": prompt,
|
||||
"n_iter": 1,
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
@@ -9451,7 +9624,7 @@ def text2img_api(prompt, art_guide=""):
|
||||
show_error_notification("SD Web API Failure", "SD Web API returned no images", do_log=True)
|
||||
return None
|
||||
|
||||
return base64_image
|
||||
return Image.open(BytesIO(base64.b64decode(base64_image)))
|
||||
|
||||
@socketio.on("clear_generated_image")
|
||||
@logger.catch
|
||||
@@ -9630,6 +9803,15 @@ def UI_2_privacy_mode(data):
|
||||
koboldai_vars.privacy_mode = False
|
||||
|
||||
#==================================================================#
|
||||
# Genres
|
||||
#==================================================================#
|
||||
@app.route("/genre_data.json", methods=["GET"])
|
||||
def UI_2_get_applicable_genres():
|
||||
return Response(json.dumps({
|
||||
"list": genre_list,
|
||||
"init": koboldai_vars.genres,
|
||||
}))
|
||||
#==================================================================#
|
||||
# Soft Prompt Tuning
|
||||
#==================================================================#
|
||||
@socketio.on("create_new_softprompt")
|
||||
@@ -9701,7 +9883,7 @@ def UI_2_test_match():
|
||||
@logger.catch
|
||||
def UI_2_audio():
|
||||
action_id = int(request.args['id']) if 'id' in request.args else len(koboldai_vars.actions)
|
||||
filename="stories/{}/{}.ogg".format(koboldai_vars.story_id, action_id)
|
||||
filename = os.path.join(koboldai_vars.save_paths.generated_audio, f"{action_id}.ogg")
|
||||
if not os.path.exists(filename):
|
||||
koboldai_vars.actions.gen_audio(action_id)
|
||||
return send_file(
|
||||
|
307
data/genres.json
Normal file
307
data/genres.json
Normal file
@@ -0,0 +1,307 @@
|
||||
[
|
||||
"Absurdist",
|
||||
"Action & Adventure",
|
||||
"Adaptations & Pastiche",
|
||||
"African American & Black/General",
|
||||
"African American & Black/Christian",
|
||||
"African American & Black/Erotica",
|
||||
"African American & Black/Historical",
|
||||
"African American & Black/Mystery & Detective",
|
||||
"African American & Black/Urban & Street Lit",
|
||||
"African American & Black/Women",
|
||||
"Alternative History",
|
||||
"Amish & Mennonite",
|
||||
"Animals",
|
||||
"Anthologies (multiple authors)",
|
||||
"Asian American",
|
||||
"Biographical",
|
||||
"Buddhist",
|
||||
"Christian/General",
|
||||
"Christian/Biblical",
|
||||
"Christian/Classic & Allegory",
|
||||
"Christian/Collections & Anthologies",
|
||||
"Christian/Contemporary",
|
||||
"Christian/Fantasy",
|
||||
"Christian/Futuristic",
|
||||
"Christian/Historical",
|
||||
"Christian/Romance/General",
|
||||
"Christian/Romance/Historical",
|
||||
"Christian/Romance/Suspense",
|
||||
"Christian/Suspense",
|
||||
"Christian/Western",
|
||||
"City Life",
|
||||
"Classics",
|
||||
"Coming of Age",
|
||||
"Crime",
|
||||
"Cultural Heritage",
|
||||
"Disabilities & Special Needs",
|
||||
"Disaster",
|
||||
"Dystopian",
|
||||
"Epistolary",
|
||||
"Erotica/General",
|
||||
"Erotica/BDSM",
|
||||
"Erotica/Collections & Anthologies",
|
||||
"Erotica/Historical",
|
||||
"Erotica/LGBTQ+/General",
|
||||
"Erotica/LGBTQ+/Bisexual",
|
||||
"Erotica/LGBTQ+/Gay",
|
||||
"Erotica/LGBTQ+/Lesbian",
|
||||
"Erotica/LGBTQ+/Transgender",
|
||||
"Erotica/Science Fiction, Fantasy & Horror",
|
||||
"Fairy Tales, Folk Tales, Legends & Mythology",
|
||||
"Family Life/General",
|
||||
"Family Life/Marriage & Divorce",
|
||||
"Family Life/Siblings",
|
||||
"Fantasy/General",
|
||||
"Fantasy/Action & Adventure",
|
||||
"Fantasy/Arthurian",
|
||||
"Fantasy/Collections & Anthologies",
|
||||
"Fantasy/Contemporary",
|
||||
"Fantasy/Dark Fantasy",
|
||||
"Fantasy/Dragons & Mythical Creatures",
|
||||
"Fantasy/Epic",
|
||||
"Fantasy/Gaslamp",
|
||||
"Fantasy/Historical",
|
||||
"Fantasy/Humorous",
|
||||
"Fantasy/Military",
|
||||
"Fantasy/Paranormal",
|
||||
"Fantasy/Romance",
|
||||
"Fantasy/Urban",
|
||||
"Feminist",
|
||||
"Friendship",
|
||||
"Ghost",
|
||||
"Gothic",
|
||||
"Hispanic & Latino",
|
||||
"Historical/General",
|
||||
"Historical/Ancient",
|
||||
"Historical/Civil War Era",
|
||||
"Historical/Colonial America & Revolution",
|
||||
"Historical/Medieval",
|
||||
"Historical/Renaissance",
|
||||
"Historical/World War I",
|
||||
"Historical/World War II",
|
||||
"Holidays",
|
||||
"Horror",
|
||||
"Humorous/General",
|
||||
"Humorous/Black Humor",
|
||||
"Indigenous",
|
||||
"Jewish",
|
||||
"Legal",
|
||||
"LGBTQ+/General",
|
||||
"LGBTQ+/Bisexual",
|
||||
"LGBTQ+/Gay",
|
||||
"LGBTQ+/Lesbian",
|
||||
"LGBTQ+/Transgender",
|
||||
"Literary",
|
||||
"LitRPG (Literary Role-Playing Game)",
|
||||
"Magical Realism",
|
||||
"Mashups",
|
||||
"Media Tie-In",
|
||||
"Medical",
|
||||
"Multiple Timelines",
|
||||
"Muslim",
|
||||
"Mystery & Detective/General",
|
||||
"Mystery & Detective/Amateur Sleuth",
|
||||
"Mystery & Detective/Collections & Anthologies",
|
||||
"Mystery & Detective/Cozy/General",
|
||||
"Mystery & Detective/Cozy/Animals",
|
||||
"Mystery & Detective/Cozy/Crafts",
|
||||
"Mystery & Detective/Cozy/Culinary",
|
||||
"Mystery & Detective/Cozy/Holidays & Vacation",
|
||||
"Mystery & Detective/Cozy/Paranormal",
|
||||
"Mystery & Detective/Hard-Boiled",
|
||||
"Mystery & Detective/Historical",
|
||||
"Mystery & Detective/International Crime & Mystery",
|
||||
"Mystery & Detective/Jewish",
|
||||
"Mystery & Detective/Police Procedural",
|
||||
"Mystery & Detective/Private Investigators",
|
||||
"Mystery & Detective/Traditional",
|
||||
"Mystery & Detective/Women Sleuths",
|
||||
"Nature & the Environment",
|
||||
"Noir",
|
||||
"Occult & Supernatural",
|
||||
"Own Voices",
|
||||
"Political",
|
||||
"Psychological",
|
||||
"Religious",
|
||||
"Romance/General",
|
||||
"Romance/Action & Adventure",
|
||||
"Romance/African American & Black",
|
||||
"Romance/Billionaires",
|
||||
"Romance/Clean & Wholesome",
|
||||
"Romance/Collections & Anthologies",
|
||||
"Romance/Contemporary",
|
||||
"Romance/Erotic",
|
||||
"Romance/Fantasy",
|
||||
"Romance/Firefighters",
|
||||
"Romance/Historical/General",
|
||||
"Romance/Historical/American",
|
||||
"Romance/Historical/Ancient World",
|
||||
"Romance/Historical/Gilded Age",
|
||||
"Romance/Historical/Medieval",
|
||||
"Romance/Historical/Regency",
|
||||
"Romance/Historical/Renaissance",
|
||||
"Romance/Historical/Scottish",
|
||||
"Romance/Historical/Tudor",
|
||||
"Romance/Historical/20th Century",
|
||||
"Romance/Historical/Victorian",
|
||||
"Romance/Historical/Viking",
|
||||
"Romance/Holiday",
|
||||
"Romance/Later in Life",
|
||||
"Romance/LGBTQ+/General",
|
||||
"Romance/LGBTQ+/Bisexual",
|
||||
"Romance/LGBTQ+/Gay",
|
||||
"Romance/LGBTQ+/Lesbian",
|
||||
"Romance/LGBTQ+/Transgender",
|
||||
"Romance/Medical",
|
||||
"Romance/Military",
|
||||
"Romance/Multicultural & Interracial",
|
||||
"Romance/New Adult",
|
||||
"Romance/Paranormal/General",
|
||||
"Romance/Paranormal/Shifters",
|
||||
"Romance/Paranormal/Vampires",
|
||||
"Romance/Paranormal/Witches",
|
||||
"Romance/Police & Law Enforcement",
|
||||
"Romance/Polyamory",
|
||||
"Romance/Rock Stars",
|
||||
"Romance/Romantic Comedy",
|
||||
"Romance/Royalty",
|
||||
"Romance/Science Fiction",
|
||||
"Romance/Sports",
|
||||
"Romance/Suspense",
|
||||
"Romance/Time Travel",
|
||||
"Romance/Western",
|
||||
"Romance/Workplace",
|
||||
"Sagas",
|
||||
"Satire",
|
||||
"Science Fiction/General",
|
||||
"Science Fiction/Action & Adventure",
|
||||
"Science Fiction/Alien Contact",
|
||||
"Science Fiction/Apocalyptic & Post-Apocalyptic",
|
||||
"Science Fiction/Collections & Anthologies",
|
||||
"Science Fiction/Crime & Mystery",
|
||||
"Science Fiction/Cyberpunk",
|
||||
"Science Fiction/Genetic Engineering",
|
||||
"Science Fiction/Hard Science Fiction",
|
||||
"Science Fiction/Humorous",
|
||||
"Science Fiction/Military",
|
||||
"Science Fiction/Space Exploration",
|
||||
"Science Fiction/Space Opera",
|
||||
"Science Fiction/Steampunk",
|
||||
"Science Fiction/Time Travel",
|
||||
"Sea Stories",
|
||||
"Short Stories (single author)",
|
||||
"Small Town & Rural",
|
||||
"Southern",
|
||||
"Sports",
|
||||
"Superheroes",
|
||||
"Thrillers/General",
|
||||
"Thrillers/Crime",
|
||||
"Thrillers/Domestic",
|
||||
"Thrillers/Espionage",
|
||||
"Thrillers/Historical",
|
||||
"Thrillers/Legal",
|
||||
"Thrillers/Medical",
|
||||
"Thrillers/Military",
|
||||
"Thrillers/Political",
|
||||
"Thrillers/Psychological",
|
||||
"Thrillers/Supernatural",
|
||||
"Thrillers/Suspense",
|
||||
"Thrillers/Technological",
|
||||
"Thrillers/Terrorism",
|
||||
"Urban & Street Lit",
|
||||
"Visionary & Metaphysical",
|
||||
"War & Military",
|
||||
"Westerns",
|
||||
"Women",
|
||||
"World Literature/Africa/General",
|
||||
"World Literature/Africa/East Africa",
|
||||
"World Literature/Africa/Nigeria",
|
||||
"World Literature/Africa/Southern Africa",
|
||||
"World Literature/Africa/West Africa",
|
||||
"World Literature/American/General",
|
||||
"World Literature/American/Colonial & Revolutionary Periods",
|
||||
"World Literature/American/19th Century",
|
||||
"World Literature/American/20th Century",
|
||||
"World Literature/American/21st Century",
|
||||
"World Literature/Argentina",
|
||||
"World Literature/Asia (General)",
|
||||
"World Literature/Australia",
|
||||
"World Literature/Austria",
|
||||
"World Literature/Brazil",
|
||||
"World Literature/Canada/General",
|
||||
"World Literature/Canada/Colonial & 19th Century",
|
||||
"World Literature/Canada/20th Century",
|
||||
"World Literature/Canada/21st Century",
|
||||
"World Literature/Caribbean & West Indies",
|
||||
"World Literature/Central America",
|
||||
"World Literature/Chile",
|
||||
"World Literature/China/General",
|
||||
"World Literature/China/19th Century",
|
||||
"World Literature/China/20th Century",
|
||||
"World Literature/China/21st Century",
|
||||
"World Literature/Colombia",
|
||||
"World Literature/Czech Republic",
|
||||
"World Literature/Denmark",
|
||||
"World Literature/England/General",
|
||||
"World Literature/England/Early & Medieval Periods",
|
||||
"World Literature/England/16th & 17th Century",
|
||||
"World Literature/England/18th Century",
|
||||
"World Literature/England/19th Century",
|
||||
"World Literature/England/20th Century",
|
||||
"World Literature/England/21st Century",
|
||||
"World Literature/Europe (General)",
|
||||
"World Literature/Finland",
|
||||
"World Literature/France/General",
|
||||
"World Literature/France/18th Century",
|
||||
"World Literature/France/19th Century",
|
||||
"World Literature/France/20th Century",
|
||||
"World Literature/France/21st Century",
|
||||
"World Literature/Germany/General",
|
||||
"World Literature/Germany/20th Century",
|
||||
"World Literature/Germany/21st Century",
|
||||
"World Literature/Greece",
|
||||
"World Literature/Hungary",
|
||||
"World Literature/India/General",
|
||||
"World Literature/India/19th Century",
|
||||
"World Literature/India/20th Century",
|
||||
"World Literature/India/21st Century",
|
||||
"World Literature/Ireland/General",
|
||||
"World Literature/Ireland/19th Century",
|
||||
"World Literature/Ireland/20th Century",
|
||||
"World Literature/Ireland/21st Century",
|
||||
"World Literature/Italy",
|
||||
"World Literature/Japan",
|
||||
"World Literature/Korea",
|
||||
"World Literature/Mexico",
|
||||
"World Literature/Middle East/General",
|
||||
"World Literature/Middle East/Arabian Peninsula",
|
||||
"World Literature/Middle East/Egypt & North Africa",
|
||||
"World Literature/Middle East/Israel",
|
||||
"World Literature/Netherlands",
|
||||
"World Literature/New Zealand",
|
||||
"World Literature/Norway",
|
||||
"World Literature/Oceania",
|
||||
"World Literature/Pakistan",
|
||||
"World Literature/Peru",
|
||||
"World Literature/Poland",
|
||||
"World Literature/Portugal",
|
||||
"World Literature/Russia/General",
|
||||
"World Literature/Russia/19th Century",
|
||||
"World Literature/Russia/20th Century",
|
||||
"World Literature/Russia/21st Century",
|
||||
"World Literature/Scotland/General",
|
||||
"World Literature/Scotland/19th Century",
|
||||
"World Literature/Scotland/20th Century",
|
||||
"World Literature/Scotland/21st Century",
|
||||
"World Literature/South America (General)",
|
||||
"World Literature/Southeast Asia",
|
||||
"World Literature/Spain/General",
|
||||
"World Literature/Spain/19th Century",
|
||||
"World Literature/Spain/20th Century",
|
||||
"World Literature/Spain/21st Century",
|
||||
"World Literature/Sweden",
|
||||
"World Literature/Turkey",
|
||||
"World Literature/Uruguay",
|
||||
"World Literature/Wales"
|
||||
]
|
File diff suppressed because one or more lines are too long
@@ -1975,6 +1975,7 @@ body {
|
||||
|
||||
|
||||
.context-sp {background-color: var(--context_colors_soft_prompt);}
|
||||
.context-genre {background-color: var(--context_colors_genre);}
|
||||
.context-prompt {background-color: var(--context_colors_prompt);}
|
||||
.context-wi {background-color: var(--context_colors_world_info);}
|
||||
.context-memory {background-color: var(--context_colors_memory);}
|
||||
@@ -2718,6 +2719,71 @@ body {
|
||||
max-width: 99%;
|
||||
}
|
||||
|
||||
/* Genres */
|
||||
#genre-input {
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#genre-suggestion-super-container { position: relative; }
|
||||
#genre-suggestion-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
background-color: var(--input_background);
|
||||
z-index: 2;
|
||||
max-height: calc((30px + 2px) * 5);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.genre-suggestion {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 5px 0px;
|
||||
background-color: var(--wi_tag_color);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.genre-suggestion.highlighted {
|
||||
background-color: #151e28;
|
||||
}
|
||||
|
||||
#genre-container {
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
|
||||
background-color: #151e28;
|
||||
}
|
||||
|
||||
.genre {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: var(--wi_tag_color);
|
||||
border-radius: var(--radius_wi_card);
|
||||
padding: 4px 0px;
|
||||
margin: 2px 2px;
|
||||
}
|
||||
|
||||
.genre-inner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0px 10px;
|
||||
top: 1px;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
.genre-inner > .x {
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.genre-inner > .x:hover { opacity: 1; }
|
||||
|
||||
|
||||
/*---------------------------------- Global ------------------------------------------------*/
|
||||
.hidden {
|
||||
display: none;
|
||||
|
@@ -70,12 +70,16 @@ var scroll_trigger_element = undefined; //undefined means not currently set. If
|
||||
var drag_id = null;
|
||||
const on_colab = $el("#on_colab").textContent == "true";
|
||||
|
||||
// Each entry into this array should be an object that looks like:
|
||||
// {class: "class", key: "key", func: callback}
|
||||
let sync_hooks = [];
|
||||
|
||||
// name, desc, icon, func
|
||||
var finder_actions = [
|
||||
{name: "Load Model", icon: "folder_open", type: "action", func: function() { socket.emit('load_model_button', {}); }},
|
||||
{name: "New Story", icon: "description", type: "action", func: function() { socket.emit('new_story', ''); }},
|
||||
{name: "Load Story", icon: "folder_open", type: "action", func: function() { socket.emit('load_story_list', ''); }},
|
||||
{name: "Save Story", icon: "save", type: "action", func: function() { socket.emit("save_story", null, (response) => {save_as_story(response);}); }},
|
||||
{name: "Load Story", icon: "folder_open", type: "action", func: load_story_list},
|
||||
{name: "Save Story", icon: "save", type: "action", func: save_story},
|
||||
{name: "Download Story", icon: "file_download", type: "action", func: function() { document.getElementById('download_iframe').src = 'json'; }},
|
||||
{name: "Import Story", icon: "file_download", desc: "Import a prompt from aetherroom.club, formerly prompts.aidg.club", type: "action", func: openClubImport },
|
||||
|
||||
@@ -119,6 +123,7 @@ const context_menu_actions = {
|
||||
"generated-image": [
|
||||
{label: "View", icon: "search", enabledOn: "ALWAYS", click: imgGenView},
|
||||
{label: "Download", icon: "download", enabledOn: "ALWAYS", click: imgGenDownload},
|
||||
{label: "Retry", icon: "refresh", enabledOn: "ALWAYS", click: imgGenRetry},
|
||||
{label: "Clear", icon: "clear", enabledOn: "ALWAYS", click: imgGenClear},
|
||||
],
|
||||
"wi-img-upload-button": [
|
||||
@@ -129,8 +134,8 @@ const context_menu_actions = {
|
||||
|
||||
// CTRL-[X]
|
||||
const shortcuts = [
|
||||
{key: "k", desc: "Finder", func: open_finder},
|
||||
{key: "/", desc: "Help screen", func: () => openPopup("shortcuts-popup")},
|
||||
{key: "s", desc: "Save Story", func: save_story},
|
||||
{key: "o", desc: "Open Story", func: load_story_list},
|
||||
{key: "z", desc: "Undoes last story action", func: () => socket.emit("back", {}), criteria: canNavigateStoryHistory},
|
||||
{key: "y", desc: "Redoes last story action", func: () => socket.emit("redo", {}), criteria: canNavigateStoryHistory},
|
||||
{key: "e", desc: "Retries last story action", func: () => socket.emit("retry", {}), criteria: canNavigateStoryHistory},
|
||||
@@ -138,6 +143,8 @@ const shortcuts = [
|
||||
{key: "u", desc: "Focuses Author's Note", func: () => focusEl("#authors_notes")}, // CTRL-N is reserved :^(
|
||||
{key: "g", desc: "Focuses game text", func: () => focusEl("#input_text")},
|
||||
{key: "l", desc: '"Lock" screen (Not secure)', func: () => socket.emit("privacy_mode", {'enabled': true})},
|
||||
{key: "k", desc: "Finder", func: open_finder},
|
||||
{key: "/", desc: "Help screen", func: () => openPopup("shortcuts-popup")},
|
||||
]
|
||||
|
||||
const chat = {
|
||||
@@ -651,6 +658,9 @@ function do_probabilities(action) {
|
||||
|
||||
}
|
||||
|
||||
function save_story() { socket.emit("save_story", null, response => save_as_story(response)); }
|
||||
function load_story_list() { socket.emit("load_story_list", ""); }
|
||||
|
||||
function do_presets(data) {
|
||||
for (select of document.getElementsByClassName('presets')) {
|
||||
//clear out the preset list
|
||||
@@ -754,6 +764,12 @@ function var_changed(data) {
|
||||
//if (data.name == "sp") {
|
||||
// console.log({"name": data.name, "data": data});
|
||||
//}
|
||||
|
||||
for (const entry of sync_hooks) {
|
||||
if (data.classname !== entry.class) continue;
|
||||
if (data.name !== entry.name) continue;
|
||||
entry.func(data.value);
|
||||
}
|
||||
|
||||
if (data.name in vars_sync_time) {
|
||||
if (vars_sync_time[data.name] > Date.parse(data.transmit_time)) {
|
||||
@@ -761,7 +777,7 @@ function var_changed(data) {
|
||||
}
|
||||
}
|
||||
vars_sync_time[data.name] = Date.parse(data.transmit_time);
|
||||
|
||||
|
||||
if ((data.classname == 'actions') && (data.name == 'Action Count')) {
|
||||
current_action = data.value;
|
||||
if (current_action <= 0) {
|
||||
@@ -1008,7 +1024,7 @@ function load_popup(data) {
|
||||
for (file of fileList) {
|
||||
reader = new FileReader();
|
||||
reader.onload = function (event) {
|
||||
socket.emit("upload_file", {'filename': file.name, "data": event.target.result});
|
||||
socket.emit("upload_file", {'filename': file.name, "data": event.target.result, 'upload_no_save': true});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
@@ -2612,80 +2628,86 @@ function create_new_softprompt() {
|
||||
async function download_story_to_json() {
|
||||
//document.getElementById('download_iframe').src = 'json';
|
||||
downloaded = false;
|
||||
async function download_story() {
|
||||
if (socket.connected) {
|
||||
try {
|
||||
let r = await fetch("json");
|
||||
let j = await r.json();
|
||||
downloadString(JSON.stringify(j), j['story_name']+".json")
|
||||
downloaded = true;
|
||||
let name = $el(".var_sync_story_story_name").innerText;
|
||||
let r = await fetch("story_download");
|
||||
downloadBlob(await r.blob(), `${name}.kaistory`);
|
||||
return;
|
||||
}
|
||||
catch(err) {
|
||||
downloaded = false;
|
||||
console.error("Error in online download");
|
||||
console.error(err);
|
||||
}
|
||||
} if (downloaded == false) {
|
||||
//first we're going to find all the var_sync_story_ classes used in the document.
|
||||
let allClasses = [];
|
||||
const allElements = document.querySelectorAll('*');
|
||||
}
|
||||
|
||||
for (let i = 0; i < allElements.length; i++) {
|
||||
let classes = allElements[i].classList;
|
||||
for (let j = 0; j < classes.length; j++) {
|
||||
if (!(allClasses.includes(classes[j].replace("var_sync_story_", ""))) && (classes[j].includes("var_sync_story_"))) {
|
||||
allClasses.push(classes[j].replace("var_sync_story_", ""));
|
||||
}
|
||||
}
|
||||
console.warn("Online download failed! Using offline download...")
|
||||
|
||||
/* Offline Download - Compile JSON file from what we have in ram */
|
||||
|
||||
//first we're going to find all the var_sync_story_ classes used in the document.
|
||||
let allClasses = [];
|
||||
const allElements = document.querySelectorAll('*');
|
||||
|
||||
for (let i = 0; i < allElements.length; i++) {
|
||||
let classes = allElements[i].classList;
|
||||
for (let j = 0; j < classes.length; j++) {
|
||||
if (!(allClasses.includes(classes[j].replace("var_sync_story_", ""))) && (classes[j].includes("var_sync_story_"))) {
|
||||
allClasses.push(classes[j].replace("var_sync_story_", ""));
|
||||
}
|
||||
|
||||
//OK, now we're going to go through each of those classes and get the values from the elements
|
||||
let j = {}
|
||||
for (class_name of allClasses) {
|
||||
for (item of document.getElementsByClassName("var_sync_story_"+class_name)) {
|
||||
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(item.tagName)) {
|
||||
if ((item.tagName == 'INPUT') && (item.type == "checkbox")) {
|
||||
j[class_name] = item.checked;
|
||||
} else {
|
||||
j[class_name] = item.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//OK, now we're going to go through each of those classes and get the values from the elements
|
||||
let j = {}
|
||||
for (class_name of allClasses) {
|
||||
for (item of document.getElementsByClassName("var_sync_story_"+class_name)) {
|
||||
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(item.tagName)) {
|
||||
if ((item.tagName == 'INPUT') && (item.type == "checkbox")) {
|
||||
j[class_name] = item.checked;
|
||||
} else {
|
||||
j[class_name] = item.textContent;
|
||||
j[class_name] = item.value;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
j[class_name] = item.textContent;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//We'll add actions and world info data next
|
||||
let temp = JSON.parse(JSON.stringify(actions_data));
|
||||
delete temp[-1];
|
||||
j['actions'] = {'action_count': document.getElementById('action_count').textContent, 'actions': temp};
|
||||
j['worldinfo_v2'] = {'entries': world_info_data, 'folders': world_info_folder_data};
|
||||
|
||||
//Biases
|
||||
let bias = {};
|
||||
for (item of document.getElementsByClassName('bias')) {
|
||||
let bias_phrase = item.querySelector(".bias_phrase").children[0].value;
|
||||
let bias_score = parseInt(item.querySelector(".bias_score").querySelector(".bias_slider_cur").textContent);
|
||||
let bias_comp_threshold = parseInt(item.querySelector(".bias_comp_threshold").querySelector(".bias_slider_cur").textContent);
|
||||
if (bias_phrase != "") {
|
||||
bias[bias_phrase] = [bias_score, bias_comp_threshold];
|
||||
}
|
||||
}
|
||||
|
||||
//We'll add actions and world info data next
|
||||
let temp = JSON.parse(JSON.stringify(actions_data));
|
||||
delete temp[-1];
|
||||
j['actions'] = {'action_count': document.getElementById('action_count').textContent, 'actions': temp};
|
||||
j['worldinfo_v2'] = {'entries': world_info_data, 'folders': world_info_folder_data};
|
||||
|
||||
//Biases
|
||||
let bias = {};
|
||||
for (item of document.getElementsByClassName('bias')) {
|
||||
let bias_phrase = item.querySelector(".bias_phrase").children[0].value;
|
||||
let bias_score = parseInt(item.querySelector(".bias_score").querySelector(".bias_slider_cur").textContent);
|
||||
let bias_comp_threshold = parseInt(item.querySelector(".bias_comp_threshold").querySelector(".bias_slider_cur").textContent);
|
||||
if (bias_phrase != "") {
|
||||
bias[bias_phrase] = [bias_score, bias_comp_threshold];
|
||||
}
|
||||
j['biases'] = bias;
|
||||
|
||||
//substitutions
|
||||
substitutions = [];
|
||||
for (item of document.getElementsByClassName('substitution-card')) {
|
||||
let target = item.children[0].querySelector(".target").value;
|
||||
let sub = item.children[1].querySelector(".target").value;
|
||||
let enabled = (item.children[1].querySelector(".material-icons-outlined").getAttribute("title") == 'Enabled');
|
||||
substitutions.push({'target': target, 'substitution': sub, 'enabled': enabled});
|
||||
}
|
||||
j['substitutions'] = substitutions;
|
||||
|
||||
j['file_version'] = 2;
|
||||
j['gamestarted'] = true;
|
||||
|
||||
downloadString(JSON.stringify(j), j['story_name']+".json")
|
||||
}
|
||||
}
|
||||
j['biases'] = bias;
|
||||
|
||||
//substitutions
|
||||
substitutions = [];
|
||||
for (item of document.getElementsByClassName('substitution-card')) {
|
||||
let target = item.children[0].querySelector(".target").value;
|
||||
let sub = item.children[1].querySelector(".target").value;
|
||||
let enabled = (item.children[1].querySelector(".material-icons-outlined").getAttribute("title") == 'Enabled');
|
||||
substitutions.push({'target': target, 'substitution': sub, 'enabled': enabled});
|
||||
}
|
||||
j['substitutions'] = substitutions;
|
||||
|
||||
j['file_version'] = 2;
|
||||
j['gamestarted'] = true;
|
||||
|
||||
downloadString(JSON.stringify(j), j['story_name']+".json")
|
||||
}
|
||||
|
||||
function unload_userscripts() {
|
||||
@@ -3197,6 +3219,7 @@ function autoResize(element, min_size=200) {
|
||||
|
||||
function calc_token_usage(
|
||||
soft_prompt_length,
|
||||
genre_length,
|
||||
memory_length,
|
||||
authors_note_length,
|
||||
prompt_length,
|
||||
@@ -3209,6 +3232,7 @@ function calc_token_usage(
|
||||
|
||||
const data = [
|
||||
{id: "soft_prompt_tokens", tokenCount: soft_prompt_length, label: "Soft Prompt"},
|
||||
{id: "genre_tokens", tokenCount: genre_length, label: "Genre"},
|
||||
{id: "memory_tokens", tokenCount: memory_length, label: "Memory"},
|
||||
{id: "authors_notes_tokens", tokenCount: authors_note_length, label: "Author's Note"},
|
||||
{id: "world_info_tokens", tokenCount: world_info_length, label: "World Info"},
|
||||
@@ -3525,6 +3549,7 @@ function update_context(data) {
|
||||
$(".context-block").remove();
|
||||
|
||||
let memory_length = 0;
|
||||
let genre_length = 0;
|
||||
let authors_notes_length = 0;
|
||||
let prompt_length = 0;
|
||||
let game_text_length = 0;
|
||||
@@ -3542,10 +3567,12 @@ function update_context(data) {
|
||||
|
||||
|
||||
for (const entry of data) {
|
||||
console.info(entry)
|
||||
let contextClass = "context-" + ({
|
||||
soft_prompt: "sp",
|
||||
prompt: "prompt",
|
||||
world_info: "wi",
|
||||
genre: "genre",
|
||||
memory: "memory",
|
||||
authors_note: "an",
|
||||
action: "action",
|
||||
@@ -3592,6 +3619,9 @@ function update_context(data) {
|
||||
document.getElementById('world_info_'+entry.uid).classList.add("used_in_game");
|
||||
}
|
||||
break;
|
||||
case 'memory':
|
||||
genre_length += entry.tokens.length;
|
||||
break;
|
||||
case 'memory':
|
||||
memory_length += entry.tokens.length;
|
||||
break;
|
||||
@@ -3616,6 +3646,7 @@ function update_context(data) {
|
||||
|
||||
calc_token_usage(
|
||||
soft_prompt_length,
|
||||
genre_length,
|
||||
memory_length,
|
||||
authors_notes_length,
|
||||
prompt_length,
|
||||
@@ -4344,6 +4375,14 @@ function downloadString(string, fileName) {
|
||||
a.click();
|
||||
}
|
||||
|
||||
function downloadBlob(blob, fileName) {
|
||||
const a = $e("a", null, {
|
||||
href: URL.createObjectURL(blob),
|
||||
download: fileName
|
||||
});
|
||||
a.click();
|
||||
}
|
||||
|
||||
function getRedactedValue(value) {
|
||||
if (typeof value === "string") return `[Redacted string with length ${value.length}]`;
|
||||
if (value instanceof Array) return `[Redacted array with length ${value.length}]`;
|
||||
@@ -4602,11 +4641,16 @@ async function loadNAILorebook(data, filename, image=null) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadKoboldData(data, filename) {
|
||||
async function loadKoboldJSON(data, filename) {
|
||||
if (data.gamestarted !== undefined) {
|
||||
// Story
|
||||
socket.emit("upload_file", {"filename": filename, "data": JSON.stringify(data)});
|
||||
socket.emit("upload_file", {
|
||||
filename: filename,
|
||||
data: new Blob([JSON.stringify(data)]),
|
||||
upload_no_save: true
|
||||
});
|
||||
socket.emit("load_story_list", "");
|
||||
load_story_list();
|
||||
} else if (data.folders !== undefined && data.entries !== undefined) {
|
||||
// World Info Folder
|
||||
await postWI(data);
|
||||
@@ -4685,9 +4729,16 @@ async function processDroppedFile(file) {
|
||||
readLoreCard(file);
|
||||
break;
|
||||
case "json":
|
||||
// KoboldAI file
|
||||
// KoboldAI file (old story, etc)
|
||||
data = JSON.parse(await file.text());
|
||||
loadKoboldData(data, file.name);
|
||||
loadKoboldJSON(data, file.name);
|
||||
break;
|
||||
case "kaistory":
|
||||
// KoboldAI story file
|
||||
let r = await fetch(`/upload_kai_story/${file.name}`, {
|
||||
method: "POST",
|
||||
body: file
|
||||
});
|
||||
break;
|
||||
case "lorebook":
|
||||
// NovelAI lorebook, JSON encoded.
|
||||
@@ -5467,6 +5518,8 @@ process_cookies();
|
||||
return;
|
||||
}
|
||||
|
||||
if (finder_mode !== "ui") return;
|
||||
|
||||
const actionsCount = actions.length;
|
||||
let future = finder_selection_index + delta;
|
||||
|
||||
@@ -6426,4 +6479,152 @@ function imgGenClear() {
|
||||
const container = $el("#action\\ image");
|
||||
container.removeAttribute("tooltip");
|
||||
socket.emit("clear_generated_image", {});
|
||||
}
|
||||
}
|
||||
|
||||
function imgGenRetry() {
|
||||
const image = $el(".action_image");
|
||||
if (!image) return;
|
||||
$el("#image-loading").classList.remove("hidden");
|
||||
socket.emit("retry_generated_image", {});
|
||||
}
|
||||
|
||||
/* Genres */
|
||||
(async function() {
|
||||
const genreContainer = $el("#genre-container");
|
||||
const genreInput = $el("#genre-input");
|
||||
const genreSuggestionContainer = $el("#genre-suggestion-container");
|
||||
let genreData = await (await fetch("/genre_data.json")).json();
|
||||
let allGenres = genreData.list;
|
||||
let genres = genreData.init;
|
||||
let highlightIndex = -1;
|
||||
|
||||
sync_hooks.push({
|
||||
class: "story",
|
||||
name: "genres",
|
||||
func: function(passedGenres) {
|
||||
genres = passedGenres;
|
||||
$(".genre").remove();
|
||||
for (const g of genres) {
|
||||
addGenreUI(g);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function addGenreUI(genre) {
|
||||
let div = $e("div", genreContainer, {classes: ["genre"]});
|
||||
let inner = $e("div", div, {classes: ["genre-inner"]});
|
||||
let xIcon = $e("span", inner, {innerText: "clear", classes: ["x", "material-icons-outlined"]});
|
||||
let label = $e("span", inner, {innerText: genre, classes: ["genre-label"]});
|
||||
|
||||
xIcon.addEventListener("click", function() {
|
||||
div.remove();
|
||||
genres = genres.filter(x => x !== genre);
|
||||
socket.emit("var_change", {"ID": "story_genres", "value": genres});
|
||||
});
|
||||
}
|
||||
|
||||
for (const initGenre of genreData.init) {
|
||||
addGenreUI(initGenre);
|
||||
}
|
||||
|
||||
function addGenre(genre) {
|
||||
if (genres.includes(genre)) return;
|
||||
|
||||
addGenreUI(genre);
|
||||
genreInput.value = "";
|
||||
nukeSuggestions();
|
||||
|
||||
genres.push(genre);
|
||||
socket.emit("var_change", {"ID": "story_genres", "value": genres});
|
||||
}
|
||||
|
||||
function nukeSuggestions() {
|
||||
genreSuggestionContainer.innerHTML = "";
|
||||
highlightIndex = -1;
|
||||
}
|
||||
|
||||
document.addEventListener("click", function(event) {
|
||||
// Listening for clicks all over the document kinda sucks but blur
|
||||
// fires you can click a suggestion so...
|
||||
if (!genreSuggestionContainer.children.length) return;
|
||||
if (event.target === genreInput) return;
|
||||
if (event.target.classList.contains("genre-suggestion")) return;
|
||||
nukeSuggestions();
|
||||
});
|
||||
|
||||
genreInput.addEventListener("keydown", function(event) {
|
||||
switch (event.key) {
|
||||
case "ArrowUp":
|
||||
highlightIndex--;
|
||||
break;
|
||||
case "Tab":
|
||||
highlightIndex += event.shiftKey ? -1 : 1;
|
||||
break;
|
||||
case "ArrowDown":
|
||||
highlightIndex++;
|
||||
break;
|
||||
case "Enter":
|
||||
if (highlightIndex === -1) {
|
||||
if (!genreInput.value.trim()) return;
|
||||
addGenre(genreInput.value);
|
||||
} else {
|
||||
genreSuggestionContainer.children[highlightIndex].click();
|
||||
}
|
||||
return;
|
||||
case "Escape":
|
||||
genreInput.value = "";
|
||||
nukeSuggestions();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (!genreSuggestionContainer.children.length) return;
|
||||
|
||||
const oldHighlighted = $el(".genre-suggestion.highlighted");
|
||||
if (oldHighlighted) oldHighlighted.classList.remove("highlighted");
|
||||
|
||||
// Wrap around
|
||||
let maxIndex = genreSuggestionContainer.children.length - 1;
|
||||
if (highlightIndex < 0) highlightIndex = maxIndex;
|
||||
if (highlightIndex > maxIndex) highlightIndex = 0;
|
||||
|
||||
const highlighted = genreSuggestionContainer.children[highlightIndex];
|
||||
highlighted.classList.add("highlighted");
|
||||
highlighted.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "center",
|
||||
inline: "center"
|
||||
});
|
||||
});
|
||||
|
||||
genreInput.addEventListener("input", function() {
|
||||
let showList = [];
|
||||
let lowerMatch = genreInput.value.toLowerCase();
|
||||
|
||||
nukeSuggestions();
|
||||
if (!lowerMatch) return;
|
||||
|
||||
for (const genre of allGenres) {
|
||||
if (!genre.toLowerCase().includes(lowerMatch)) continue;
|
||||
showList.push(genre);
|
||||
}
|
||||
|
||||
for (const genre of showList) {
|
||||
let suggestion = $e("span", genreSuggestionContainer, {
|
||||
innerText: genre,
|
||||
classes: ["genre-suggestion"]
|
||||
});
|
||||
|
||||
suggestion.addEventListener("click", function() {
|
||||
addGenre(this.innerText);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
})()
|
@@ -196,10 +196,11 @@
|
||||
<span class="noselect">Key:</span>
|
||||
<div>
|
||||
<span class="noselect context-block-example context-sp">Soft Prompt</span>
|
||||
<span class="noselect context-block-example context-prompt">Prompt</span>
|
||||
<span class="noselect context-block-example context-wi">World Info</span>
|
||||
<span class="noselect context-block-example context-genre">Genre</span>
|
||||
<span class="noselect context-block-example context-memory">Memory</span>
|
||||
<span class="noselect context-block-example context-wi">World Info</span>
|
||||
<span class="noselect context-block-example context-an">Author's Note</span>
|
||||
<span class="noselect context-block-example context-prompt">Prompt</span>
|
||||
<span class="noselect context-block-example context-action">Action</span>
|
||||
<span class="noselect context-block-example context-submit">Submit</span>
|
||||
</div>
|
||||
|
@@ -60,7 +60,7 @@
|
||||
<span class="var_sync_story_story_name fullwidth" contenteditable=true onblur="sync_to_server(this);"></span>
|
||||
</span>
|
||||
<span>
|
||||
<span class="material-icons-outlined cursor" style="padding-top: 8px;" tooltip="Download Story" onclick="download_story_to_json()">file_download</span>
|
||||
<span class="material-icons-outlined cursor" style="padding-top: 8px;" tooltip="Download Story" onclick="download_story();">file_download</span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="text_storyname">
|
||||
@@ -71,11 +71,11 @@
|
||||
<span class="material-icons-outlined cursor" tooltip="New Story">description</span>
|
||||
<span class="button_label">New Story</span>
|
||||
</button>
|
||||
<button class="settings_button" onclick="socket.emit('load_story_list', '');">
|
||||
<button class="settings_button" onclick="load_story_list();">
|
||||
<span class="material-icons-outlined cursor" tooltip="Load Story">folder_open</span>
|
||||
<span class="button_label">Load Story</span>
|
||||
</button>
|
||||
<button class="settings_button var_sync_alt_story_gamesaved" onclick='socket.emit("save_story", null, (response) => {save_as_story(response);});'>
|
||||
<button class="settings_button var_sync_alt_story_gamesaved" onclick='save_story();'>
|
||||
<span class="material-icons-outlined cursor var_sync_alt_story_gamesaved" tooltip="Save Story">save</span>
|
||||
<span class="button_label">Save Story</span>
|
||||
</button>
|
||||
|
@@ -50,6 +50,19 @@
|
||||
<label for="authors_notes">Author's Notes:</label><br/>
|
||||
<textarea autocomplete="off" rows=16 id="authors_notes" class="var_sync_story_authornote var_sync_alt_story_authornote_length fullwidth" oninput="autoResize(this)" onchange='sync_to_server(this);'></textarea><br/>
|
||||
|
||||
<h4 class="section_header">Genre</h4>
|
||||
<div class="help_text">Styles the AI will attempt to imitate. Effectiveness depends on model.</div>
|
||||
<input id="genre-input" class="fullwidth" autocomplete="off" spellcheck="false">
|
||||
<div id="genre-suggestion-super-container">
|
||||
<div id="genre-suggestion-container">
|
||||
<span class="genre-suggestion">Animals</span>
|
||||
<span class="genre-suggestion">Amish</span>
|
||||
<span class="genre-suggestion">Asian</span>
|
||||
<span class="genre-suggestion">Buddhist</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="genre-container"></div>
|
||||
|
||||
<div id="An-Attention-Bias" class="var_sync_alt_system_experimental_features">
|
||||
<h4 class="section_header"><label for="An-Attention-Bias">Attention Bias Test</label></h4>
|
||||
<span class="help_text">See disclaimer in memory page.</span>
|
||||
@@ -112,6 +125,7 @@
|
||||
<div id="token-breakdown-container" class="settings_footer" style="padding-top: 10px;">
|
||||
<div class="token_breakdown" onclick='socket.emit("update_tokens", document.getElementById("input_text").value);openPopup("context-viewer");'>
|
||||
<div id="soft_prompt_tokens" style="width:0%; background-color: var(--context_colors_soft_prompt);"></div>
|
||||
<div id="genre_tokens" style="width:40%; background-color: var(--context_colors_genre);"></div>
|
||||
<div id="memory_tokens" style="width:40%; background-color: var(--context_colors_memory);"></div>
|
||||
<div id="authors_notes_tokens" style="width:10%; background-color: var(--context_colors_authors_notes);"></div>
|
||||
<div id="world_info_tokens" style="width:20%; background-color: var(--context_colors_world_info);"></div>
|
||||
|
@@ -151,6 +151,7 @@
|
||||
--context_colors_submit: #ffffff00;
|
||||
--context_colors_unused: #ffffff24;
|
||||
--context_colors_soft_prompt: #141414;
|
||||
--context_colors_genre: #2c5c88;
|
||||
|
||||
/*Parameters*/
|
||||
--scrollbar-size: 6px;
|
||||
|
Reference in New Issue
Block a user