Make user_data configurable: add --user-data-dir flag, auto-detect ../user_data

If --user-data-dir is not set, auto-detect: use ../user_data when
./user_data doesn't exist, making it easy to share user data across
portable builds by placing it one folder up.
This commit is contained in:
oobabooga 2026-03-05 19:26:21 -08:00
parent 4c406e024f
commit e2548f69a9
21 changed files with 166 additions and 133 deletions

View file

@ -1126,9 +1126,9 @@ def start_new_chat(state):
def get_history_file_path(unique_id, character, mode):
if mode == 'instruct':
p = Path(f'user_data/logs/instruct/{unique_id}.json')
p = shared.user_data_dir / 'logs' / 'instruct' / f'{unique_id}.json'
else:
p = Path(f'user_data/logs/chat/{character}/{unique_id}.json')
p = shared.user_data_dir / 'logs' / 'chat' / character / f'{unique_id}.json'
return p
@ -1164,13 +1164,13 @@ def rename_history(old_id, new_id, character, mode):
def get_paths(state):
if state['mode'] == 'instruct':
return Path('user_data/logs/instruct').glob('*.json')
return (shared.user_data_dir / 'logs' / 'instruct').glob('*.json')
else:
character = state['character_menu']
# Handle obsolete filenames and paths
old_p = Path(f'user_data/logs/{character}_persistent.json')
new_p = Path(f'user_data/logs/persistent_{character}.json')
old_p = shared.user_data_dir / 'logs' / f'{character}_persistent.json'
new_p = shared.user_data_dir / 'logs' / f'persistent_{character}.json'
if old_p.exists():
logger.warning(f"Renaming \"{old_p}\" to \"{new_p}\"")
old_p.rename(new_p)
@ -1182,7 +1182,7 @@ def get_paths(state):
p.parent.mkdir(exist_ok=True)
new_p.rename(p)
return Path(f'user_data/logs/chat/{character}').glob('*.json')
return (shared.user_data_dir / 'logs' / 'chat' / character).glob('*.json')
def find_all_histories(state):
@ -1307,7 +1307,7 @@ def get_chat_state_key(character, mode):
def load_last_chat_state():
"""Load the last chat state from file"""
state_file = Path('user_data/logs/chat_state.json')
state_file = shared.user_data_dir / 'logs' / 'chat_state.json'
if state_file.exists():
try:
with open(state_file, 'r', encoding='utf-8') as f:
@ -1327,7 +1327,7 @@ def save_last_chat_state(character, mode, unique_id):
key = get_chat_state_key(character, mode)
state["last_chats"][key] = unique_id
state_file = Path('user_data/logs/chat_state.json')
state_file = shared.user_data_dir / 'logs' / 'chat_state.json'
state_file.parent.mkdir(exist_ok=True)
with open(state_file, 'w', encoding='utf-8') as f:
f.write(json.dumps(state, indent=2))
@ -1403,7 +1403,7 @@ def generate_pfp_cache(character):
if not cache_folder.exists():
cache_folder.mkdir()
for path in [Path(f"user_data/characters/{character}.{extension}") for extension in ['png', 'jpg', 'jpeg']]:
for path in [shared.user_data_dir / 'characters' / f"{character}.{extension}" for extension in ['png', 'jpg', 'jpeg']]:
if path.exists():
original_img = Image.open(path)
# Define file paths
@ -1428,12 +1428,12 @@ def load_character(character, name1, name2):
filepath = None
for extension in ["yml", "yaml", "json"]:
filepath = Path(f'user_data/characters/{character}.{extension}')
filepath = shared.user_data_dir / 'characters' / f'{character}.{extension}'
if filepath.exists():
break
if filepath is None or not filepath.exists():
logger.error(f"Could not find the character \"{character}\" inside user_data/characters. No character has been loaded.")
logger.error(f"Could not find the character \"{character}\" inside {shared.user_data_dir}/characters. No character has been loaded.")
raise ValueError
file_contents = open(filepath, 'r', encoding='utf-8').read()
@ -1509,7 +1509,7 @@ def load_instruction_template(template):
if template == 'None':
return ''
for filepath in [Path(f'user_data/instruction-templates/{template}.yaml'), Path('user_data/instruction-templates/Alpaca.yaml')]:
for filepath in [shared.user_data_dir / 'instruction-templates' / f'{template}.yaml', shared.user_data_dir / 'instruction-templates' / 'Alpaca.yaml']:
if filepath.exists():
break
else:
@ -1552,17 +1552,17 @@ def upload_character(file, img_path, tavern=False):
outfile_name = name
i = 1
while Path(f'user_data/characters/{outfile_name}.yaml').exists():
while (shared.user_data_dir / 'characters' / f'{outfile_name}.yaml').exists():
outfile_name = f'{name}_{i:03d}'
i += 1
with open(Path(f'user_data/characters/{outfile_name}.yaml'), 'w', encoding='utf-8') as f:
with open(shared.user_data_dir / 'characters' / f'{outfile_name}.yaml', 'w', encoding='utf-8') as f:
f.write(yaml_data)
if img is not None:
img.save(Path(f'user_data/characters/{outfile_name}.png'))
img.save(shared.user_data_dir / 'characters' / f'{outfile_name}.png')
logger.info(f'New character saved to "user_data/characters/{outfile_name}.yaml".')
logger.info(f'New character saved to "{shared.user_data_dir}/characters/{outfile_name}.yaml".')
return gr.update(value=outfile_name, choices=get_available_characters())
@ -1643,9 +1643,9 @@ def save_character(name, greeting, context, picture, filename):
return
data = generate_character_yaml(name, greeting, context)
filepath = Path(f'user_data/characters/{filename}.yaml')
filepath = shared.user_data_dir / 'characters' / f'{filename}.yaml'
save_file(filepath, data)
path_to_img = Path(f'user_data/characters/{filename}.png')
path_to_img = shared.user_data_dir / 'characters' / f'{filename}.png'
if picture is not None:
# Copy the image file from its source path to the character folder
shutil.copy(picture, path_to_img)
@ -1655,11 +1655,11 @@ def save_character(name, greeting, context, picture, filename):
def delete_character(name, instruct=False):
# Check for character data files
for extension in ["yml", "yaml", "json"]:
delete_file(Path(f'user_data/characters/{name}.{extension}'))
delete_file(shared.user_data_dir / 'characters' / f'{name}.{extension}')
# Check for character image files
for extension in ["png", "jpg", "jpeg"]:
delete_file(Path(f'user_data/characters/{name}.{extension}'))
delete_file(shared.user_data_dir / 'characters' / f'{name}.{extension}')
def generate_user_pfp_cache(user):
@ -1668,7 +1668,7 @@ def generate_user_pfp_cache(user):
if not cache_folder.exists():
cache_folder.mkdir()
for path in [Path(f"user_data/users/{user}.{extension}") for extension in ['png', 'jpg', 'jpeg']]:
for path in [shared.user_data_dir / 'users' / f"{user}.{extension}" for extension in ['png', 'jpg', 'jpeg']]:
if path.exists():
original_img = Image.open(path)
# Define file paths
@ -1690,12 +1690,12 @@ def load_user(user_name, name1, user_bio):
filepath = None
for extension in ["yml", "yaml", "json"]:
filepath = Path(f'user_data/users/{user_name}.{extension}')
filepath = shared.user_data_dir / 'users' / f'{user_name}.{extension}'
if filepath.exists():
break
if filepath is None or not filepath.exists():
logger.error(f"Could not find the user \"{user_name}\" inside user_data/users. No user has been loaded.")
logger.error(f"Could not find the user \"{user_name}\" inside {shared.user_data_dir}/users. No user has been loaded.")
raise ValueError
with open(filepath, 'r', encoding='utf-8') as f:
@ -1741,14 +1741,14 @@ def save_user(name, user_bio, picture, filename):
return
# Ensure the users directory exists
users_dir = Path('user_data/users')
users_dir = shared.user_data_dir / 'users'
users_dir.mkdir(parents=True, exist_ok=True)
data = generate_user_yaml(name, user_bio)
filepath = Path(f'user_data/users/{filename}.yaml')
filepath = shared.user_data_dir / 'users' / f'{filename}.yaml'
save_file(filepath, data)
path_to_img = Path(f'user_data/users/{filename}.png')
path_to_img = shared.user_data_dir / 'users' / f'{filename}.png'
if picture is not None:
# Copy the image file from its source path to the users folder
shutil.copy(picture, path_to_img)
@ -1759,11 +1759,11 @@ def delete_user(name):
"""Delete user profile files"""
# Check for user data files
for extension in ["yml", "yaml", "json"]:
delete_file(Path(f'user_data/users/{name}.{extension}'))
delete_file(shared.user_data_dir / 'users' / f'{name}.{extension}')
# Check for user image files
for extension in ["png", "jpg", "jpeg"]:
delete_file(Path(f'user_data/users/{name}.{extension}'))
delete_file(shared.user_data_dir / 'users' / f'{name}.{extension}')
def update_user_menu_after_deletion(idx):
@ -2224,7 +2224,7 @@ def handle_save_template_click(instruction_template_str):
contents = generate_instruction_template_yaml(instruction_template_str)
return [
"My Template.yaml",
"user_data/instruction-templates/",
str(shared.user_data_dir / 'instruction-templates') + '/',
contents,
gr.update(visible=True)
]
@ -2233,7 +2233,7 @@ def handle_save_template_click(instruction_template_str):
def handle_delete_template_click(template):
return [
f"{template}.yaml",
"user_data/instruction-templates/",
str(shared.user_data_dir / 'instruction-templates') + '/',
gr.update(visible=False)
]