Feature #8 is almost fully implemented. Needs pronouns and it would be good. Check settings.yml
This commit is contained in:
parent
61053f6177
commit
ea8983ddb0
10 changed files with 302 additions and 1693 deletions
1665
bot.log
1665
bot.log
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/user_profiles.cpython-310.pyc
Normal file
BIN
src/__pycache__/user_profiles.cpython-310.pyc
Normal file
Binary file not shown.
24
src/ai.py
24
src/ai.py
|
|
@ -7,6 +7,7 @@ import requests
|
|||
import re
|
||||
from dotenv import load_dotenv
|
||||
from personality import load_persona
|
||||
from user_profiles import format_profile_for_block
|
||||
from logger import setup_logger
|
||||
|
||||
# Set up logger specifically for AI operations
|
||||
|
|
@ -82,28 +83,33 @@ def get_current_model():
|
|||
return get_model_name()
|
||||
|
||||
# Main LLM interaction — injects personality and sends prompt to Ollama
|
||||
def get_ai_response(user_prompt, context=None):
|
||||
def get_ai_response(user_prompt, context=None, user_profile=None):
|
||||
model_name = get_model_name()
|
||||
load_model(model_name)
|
||||
|
||||
persona = load_persona()
|
||||
full_prompt = ""
|
||||
|
||||
# Inject persona first if available
|
||||
# Inject Delta's base persona
|
||||
if persona:
|
||||
safe_inject = persona["prompt_inject"].replace("“", "\"").replace("”", "\"").replace("’", "'")
|
||||
full_prompt += f"{safe_inject}\n"
|
||||
|
||||
# Add recent conversation context, if available
|
||||
# Inject custom user profile prompt as override or influence
|
||||
if user_profile and user_profile.get("custom_prompt"):
|
||||
full_prompt += f"[User Instruction]\n{user_profile['custom_prompt']}\n"
|
||||
logger.info(f"🧠 Injected user custom prompt:\n{user_profile['custom_prompt']}")
|
||||
|
||||
# Add recent chat context (this already includes the profile block!)
|
||||
if context:
|
||||
logger.info("🧠 Injected context block (pre-prompt):\n" + context)
|
||||
full_prompt += f"[Recent Conversation]\n{context}\n\n"
|
||||
full_prompt += f"[Recent Conversation]\n{context}\n"
|
||||
|
||||
# Add user prompt + character or plain ending
|
||||
# Add user's message and expected bot reply prefix
|
||||
if persona:
|
||||
full_prompt += f"User: {user_prompt}\n{persona['name']}:"
|
||||
full_prompt += f"\nUser: {user_prompt}\n{persona['name']}:"
|
||||
else:
|
||||
full_prompt += user_prompt
|
||||
full_prompt += f"\nUser: {user_prompt}\nResponse:"
|
||||
|
||||
payload = {
|
||||
"model": model_name,
|
||||
|
|
@ -117,11 +123,9 @@ def get_ai_response(user_prompt, context=None):
|
|||
try:
|
||||
response = requests.post(GEN_ENDPOINT, json=payload)
|
||||
logger.info(f"📨 Raw response: {response.text}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
response_text = result.get("response", "[No message in response]")
|
||||
return strip_thinking_block(response_text) if not SHOW_THINKING_BLOCKS else response_text
|
||||
return result.get("response", "[No message in response]")
|
||||
else:
|
||||
return f"[Error {response.status_code}] {response.text}"
|
||||
except Exception as e:
|
||||
|
|
|
|||
184
src/bot.py
184
src/bot.py
|
|
@ -3,39 +3,10 @@
|
|||
import os
|
||||
import discord
|
||||
import yaml
|
||||
from discord.ext import commands
|
||||
from textwrap import wrap
|
||||
from dotenv import load_dotenv
|
||||
import random
|
||||
import yaml
|
||||
from scheduler import start_scheduler
|
||||
from profilepic import set_avatar_from_bytes
|
||||
from context import fetch_recent_context, format_context
|
||||
from logger import setup_logger
|
||||
logger = setup_logger("bot")
|
||||
|
||||
from ai import unload_model, load_model, get_current_model, get_ai_response
|
||||
|
||||
dotenv_path = os.path.join(os.path.dirname(__file__), '..', '.env')
|
||||
load_dotenv(dotenv_path)
|
||||
logger.info(f"🔍 Loaded MODEL_NAME from .env: {os.getenv('MODEL_NAME')}")
|
||||
|
||||
MODEL_NAME = os.getenv("MODEL_NAME", "llama3:latest")
|
||||
logger.info(f"🔍 Loaded MODEL_NAME from .env: {MODEL_NAME}")
|
||||
|
||||
# 🧽 Try to unload any currently loaded model
|
||||
logger.info(f"🧹 Attempting to clear VRAM before loading {MODEL_NAME}...")
|
||||
unload_model(MODEL_NAME)
|
||||
|
||||
# 🚀 Load target model from .env
|
||||
if load_model(MODEL_NAME):
|
||||
logger.info(f"🚀 Model `{MODEL_NAME}` preloaded on startup.")
|
||||
else:
|
||||
logger.warning(f"⚠️ Failed to preload model `{MODEL_NAME}`.")
|
||||
|
||||
logger.info(f"✅ Final model in use: {MODEL_NAME}")
|
||||
|
||||
from personality import apply_personality, set_persona
|
||||
from dotenv import load_dotenv
|
||||
from textwrap import wrap
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import (
|
||||
cooldown,
|
||||
BucketType,
|
||||
|
|
@ -43,9 +14,50 @@ from discord.ext.commands import (
|
|||
CommandOnCooldown
|
||||
)
|
||||
|
||||
# Local imports
|
||||
from scheduler import start_scheduler
|
||||
from profilepic import set_avatar_from_bytes
|
||||
from context import fetch_recent_context, format_context
|
||||
from user_profiles import (
|
||||
load_user_profile,
|
||||
update_last_seen,
|
||||
increment_interactions,
|
||||
format_profile_for_block,
|
||||
set_pronouns,
|
||||
set_custom_prompt
|
||||
)
|
||||
from personality import apply_personality, set_persona
|
||||
from logger import setup_logger
|
||||
from ai import (
|
||||
unload_model,
|
||||
load_model,
|
||||
get_current_model,
|
||||
get_ai_response,
|
||||
TAGS_ENDPOINT
|
||||
)
|
||||
|
||||
from user_profiles import format_profile_for_block as format_user_profile_block
|
||||
|
||||
# Setup logger and environment
|
||||
logger = setup_logger("bot")
|
||||
dotenv_path = os.path.join(os.path.dirname(__file__), '..', '.env')
|
||||
load_dotenv(dotenv_path)
|
||||
|
||||
# Load model settings
|
||||
MODEL_NAME = os.getenv("MODEL_NAME", "llama3:latest")
|
||||
logger.info(f"🔍 Loaded MODEL_NAME from .env: {MODEL_NAME}")
|
||||
logger.info(f"🧹 Attempting to clear VRAM before loading {MODEL_NAME}...")
|
||||
unload_model(MODEL_NAME)
|
||||
|
||||
if load_model(MODEL_NAME):
|
||||
logger.info(f"🚀 Model `{MODEL_NAME}` preloaded on startup.")
|
||||
else:
|
||||
logger.warning(f"⚠️ Failed to preload model `{MODEL_NAME}`.")
|
||||
logger.info(f"✅ Final model in use: {MODEL_NAME}")
|
||||
|
||||
# Load YAML settings
|
||||
base_dir = os.path.dirname(__file__)
|
||||
settings_path = os.path.join(base_dir, "settings.yml")
|
||||
|
||||
with open(settings_path, "r", encoding="utf-8") as f:
|
||||
settings = yaml.safe_load(f)
|
||||
|
||||
|
|
@ -53,6 +65,7 @@ ROAST_COOLDOWN_SECONDS = settings["cooldowns"]["roast"]
|
|||
GLOBAL_COOLDOWN_SECONDS = settings["cooldowns"]["global"]
|
||||
COOLDOWN_MSG_TEMPLATE = settings["messages"]["cooldown"]
|
||||
|
||||
# Configure Discord bot
|
||||
TOKEN = os.getenv("DISCORD_TOKEN")
|
||||
if not TOKEN:
|
||||
logger.error("❌ DISCORD_TOKEN not set in .env file.")
|
||||
|
|
@ -60,61 +73,75 @@ if not TOKEN:
|
|||
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
bot = commands.Bot(command_prefix="!", intents=intents)
|
||||
|
||||
# Handle cooldown errors globally
|
||||
@bot.event
|
||||
async def on_command_error(ctx, error):
|
||||
if isinstance(error, CommandOnCooldown):
|
||||
retry_secs = round(error.retry_after, 1)
|
||||
template = random.choice(COOLDOWN_MSG_TEMPLATE) if isinstance(COOLDOWN_MSG_TEMPLATE, list) else COOLDOWN_MSG_TEMPLATE
|
||||
msg = template.replace("{seconds}", str(retry_secs))
|
||||
|
||||
logger.info(f"Command {ctx.command} on cooldown. Retry after {retry_secs} seconds.")
|
||||
await ctx.send(msg)
|
||||
else:
|
||||
raise error
|
||||
|
||||
# Global cooldown bucket
|
||||
# Global cooldown
|
||||
global_cooldown = CooldownMapping.from_cooldown(1, GLOBAL_COOLDOWN_SECONDS, BucketType.user)
|
||||
|
||||
@bot.check
|
||||
async def global_command_cooldown(ctx):
|
||||
bucket = global_cooldown.get_bucket(ctx.message)
|
||||
retry_after = bucket.update_rate_limit()
|
||||
|
||||
if retry_after:
|
||||
raise CommandOnCooldown(bucket, retry_after, BucketType.user)
|
||||
return True
|
||||
|
||||
# Handle direct bot mentions
|
||||
@bot.event
|
||||
async def on_message(message):
|
||||
# Ignore messages from the bot itself
|
||||
if message.author == bot.user:
|
||||
return
|
||||
|
||||
# Only respond if the bot is mentioned
|
||||
if bot.user.mentioned_in(message):
|
||||
# Strip the mention from the message to extract the actual prompt
|
||||
prompt = message.content.replace(f"<@{bot.user.id}>", "").strip()
|
||||
if not prompt:
|
||||
return # Nothing to respond to
|
||||
|
||||
# Update the user's interaction history
|
||||
user_id = str(message.author.id)
|
||||
update_last_seen(user_id)
|
||||
increment_interactions(user_id)
|
||||
profile = load_user_profile(message.author)
|
||||
|
||||
# Log summary info about the profile (but don’t inject it)
|
||||
logger.info(f"🧠 Profile loaded for {profile['display_name']} (interactions: {profile['interactions']})")
|
||||
|
||||
# Fetch recent messages for conversation context
|
||||
context_msgs = await fetch_recent_context(message.channel)
|
||||
formatted_context = format_context(context_msgs)
|
||||
|
||||
logger.info("🧠 Injected context block:\n" + formatted_context)
|
||||
# Log number of context messages, not the entire block (to reduce clutter)
|
||||
logger.info(f"📚 Retrieved {len(context_msgs)} messages for context")
|
||||
|
||||
# Let ai.py handle all prompt construction (persona + context + profile)
|
||||
async with message.channel.typing():
|
||||
reply = get_ai_response(prompt, context=formatted_context)
|
||||
reply = get_ai_response(prompt, context=formatted_context, user_profile=profile)
|
||||
await message.channel.send(reply)
|
||||
if prompt:
|
||||
async with message.channel.typing(): # 👈 Typing indicator!
|
||||
response = get_ai_response(prompt)
|
||||
await message.channel.send(response)
|
||||
|
||||
# Ensure bot commands still work (!setprompt, !roast, etc.)
|
||||
await bot.process_commands(message)
|
||||
|
||||
|
||||
# Bot startup event
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"✅ Logged in as {bot.user.name}")
|
||||
logger.info(f"Logged in as {bot.user.name}")
|
||||
|
||||
# Optional: rename itself in servers (if it has permission)
|
||||
for guild in bot.guilds:
|
||||
me = guild.me
|
||||
if me.nick != "Delta":
|
||||
|
|
@ -123,9 +150,22 @@ async def on_ready():
|
|||
logger.info(f"🔄 Renamed self to Delta in {guild.name}")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Failed to rename in {guild.name}: {e}")
|
||||
|
||||
bot.loop.create_task(start_scheduler(bot))
|
||||
|
||||
# Commands
|
||||
@bot.command(name="setprompt")
|
||||
async def set_prompt_cmd(ctx, *, prompt):
|
||||
set_custom_prompt(ctx.author.id, prompt)
|
||||
await ctx.send("✅ Custom prompt saved.")
|
||||
|
||||
@bot.command(name="setpronouns")
|
||||
async def set_pronouns_cmd(ctx, *, pronouns):
|
||||
success = set_pronouns(ctx.author.id, pronouns)
|
||||
if success:
|
||||
await ctx.send(f"✅ Got it, {ctx.author.display_name}! Your pronouns have been updated.")
|
||||
else:
|
||||
await ctx.send("⚠️ Failed to update pronouns. Try interacting with Delta first to generate your profile.")
|
||||
|
||||
@bot.command()
|
||||
async def ping(ctx):
|
||||
await ctx.send("🏓 Pong!")
|
||||
|
|
@ -134,16 +174,7 @@ async def ping(ctx):
|
|||
async def chat(ctx, *, prompt):
|
||||
await ctx.send("🤖 Thinking...")
|
||||
reply = get_ai_response(prompt)
|
||||
MAX_DISCORD_MESSAGE_LENGTH = 2000
|
||||
|
||||
# Split long replies into chunks that fit Discord limits
|
||||
chunks = wrap(reply, MAX_DISCORD_MESSAGE_LENGTH)
|
||||
|
||||
# Log only if the response is being chunked
|
||||
if len(chunks) > 1:
|
||||
logger.warning(f"💬 Splitting response into {len(chunks)} chunks due to length.")
|
||||
|
||||
for chunk in chunks:
|
||||
for chunk in wrap(reply, 2000):
|
||||
await ctx.send(chunk)
|
||||
|
||||
@bot.command()
|
||||
|
|
@ -154,21 +185,13 @@ async def setpersona(ctx, *, description):
|
|||
@bot.command(name='roast')
|
||||
@cooldown(rate=1, per=ROAST_COOLDOWN_SECONDS, type=BucketType.user)
|
||||
async def roast(ctx):
|
||||
# Get the mentioned user (or fallback to the author)
|
||||
target = ctx.message.mentions[0].mention if ctx.message.mentions else ctx.author.mention
|
||||
|
||||
# Build the roast prompt
|
||||
prompt = f"Roast {target}. Be dramatic, insulting, and sarcastic. Speak in your usual chaotic RGB catgirl personality."
|
||||
|
||||
# Get AI response
|
||||
response = get_ai_response(prompt)
|
||||
|
||||
# Send the roast back to the channel
|
||||
await ctx.send(f"😼 {response}")
|
||||
|
||||
@bot.command(name="clearmodel")
|
||||
async def clear_model(ctx):
|
||||
from ai import unload_model, get_current_model
|
||||
model = get_current_model()
|
||||
success = unload_model(model)
|
||||
msg = f"✅ Unloaded model: `{model}`" if success else f"❌ Failed to unload model: `{model}`"
|
||||
|
|
@ -176,41 +199,31 @@ async def clear_model(ctx):
|
|||
|
||||
@bot.command(name="model")
|
||||
async def current_model(ctx):
|
||||
from ai import get_current_model
|
||||
model = get_current_model()
|
||||
await ctx.send(f"📦 Current model: `{model}`")
|
||||
|
||||
@bot.command(name="setmodel")
|
||||
async def set_model(ctx, *, model_name):
|
||||
from ai import get_current_model, load_model, unload_model
|
||||
|
||||
current_model = get_current_model()
|
||||
if model_name == current_model:
|
||||
return await ctx.send(f"⚠️ `{model_name}` is already active.")
|
||||
|
||||
await ctx.send(f"🔄 Switching from `{current_model}` to `{model_name}`…")
|
||||
|
||||
# 1) Soft-unload old model from VRAM only
|
||||
if unload_model(current_model):
|
||||
await ctx.send(f"🧽 Unloaded `{current_model}` from VRAM.")
|
||||
else:
|
||||
await ctx.send(f"⚠️ Couldn’t unload `{current_model}` (it may not have been loaded).")
|
||||
await ctx.send(f"⚠️ Couldn’t unload `{current_model}`.")
|
||||
|
||||
# 2) Load the new one
|
||||
if not load_model(model_name):
|
||||
return await ctx.send(f"❌ Failed to pull `{model_name}`. Make sure it’s in `ollama list`.")
|
||||
return await ctx.send(f"❌ Failed to pull `{model_name}`.")
|
||||
|
||||
# 3) Update runtime AND .env on disk
|
||||
os.environ["MODEL_NAME"] = model_name
|
||||
env_path = os.path.join(os.path.dirname(__file__), '..', '.env')
|
||||
# Read and rewrite .env
|
||||
lines = []
|
||||
with open(env_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if line.startswith("MODEL_NAME="):
|
||||
lines.append(f"MODEL_NAME={model_name}\n")
|
||||
else:
|
||||
lines.append(line)
|
||||
lines.append(f"MODEL_NAME={model_name}\n" if line.startswith("MODEL_NAME=") else line)
|
||||
with open(env_path, 'w', encoding='utf-8') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
|
@ -219,8 +232,6 @@ async def set_model(ctx, *, model_name):
|
|||
@bot.command(name="models")
|
||||
async def list_models(ctx):
|
||||
import requests
|
||||
from ai import TAGS_ENDPOINT
|
||||
|
||||
try:
|
||||
resp = requests.get(TAGS_ENDPOINT)
|
||||
models = [m["name"] for m in resp.json().get("models", [])]
|
||||
|
|
@ -232,28 +243,19 @@ async def list_models(ctx):
|
|||
await ctx.send(f"❌ Failed to fetch models: {e}")
|
||||
|
||||
@bot.command(name="setavatar")
|
||||
@commands.is_owner() # Only the bot owner can run this
|
||||
@commands.is_owner()
|
||||
async def set_avatar(ctx):
|
||||
if not ctx.message.attachments:
|
||||
return await ctx.send("❌ Please attach an image (PNG) to use as the new avatar.")
|
||||
|
||||
image = ctx.message.attachments[0]
|
||||
image_bytes = await image.read()
|
||||
|
||||
token = os.getenv("DISCORD_TOKEN")
|
||||
if not token:
|
||||
return await ctx.send("❌ Bot token not found in environment.")
|
||||
|
||||
success = set_avatar_from_bytes(image_bytes, token)
|
||||
if success:
|
||||
await ctx.send("✅ Avatar updated successfully!")
|
||||
else:
|
||||
await ctx.send("❌ Failed to update avatar.")
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"✅ Logged in as {bot.user.name}")
|
||||
logger.info(f"Logged in as {bot.user.name}")
|
||||
bot.loop.create_task(start_scheduler(bot))
|
||||
await ctx.send("✅ Avatar updated successfully!" if success else "❌ Failed to update avatar.")
|
||||
|
||||
# Run bot
|
||||
bot.run(TOKEN)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,11 @@ messages:
|
|||
- "🕒 Chill, wait {seconds}s before trying again."
|
||||
|
||||
context:
|
||||
enabled: true
|
||||
max_messages: 30
|
||||
enabled: false # not working must implement
|
||||
max_messages: 10 # max messages to keep in context
|
||||
|
||||
user_profiles:
|
||||
enable_custom_prompt: true # ← Set false to ignore user `custom_prompt` values in replies
|
||||
|
||||
scheduler:
|
||||
enabled: false
|
||||
|
|
|
|||
13
src/user_profiles.json
Normal file
13
src/user_profiles.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"161149541171593216": {
|
||||
"name": "themiloverse",
|
||||
"display_name": "Miguel",
|
||||
"first_seen": "2025-05-15T03:16:30.011640",
|
||||
"last_seen": "2025-05-15T04:17:43.970172",
|
||||
"last_message": "2025-05-15T04:17:43.970172",
|
||||
"interactions": 37,
|
||||
"pronouns": null,
|
||||
"avatar_url": "https://cdn.discordapp.com/avatars/161149541171593216/fb0553a29d9f73175cb6aea24d0e19ec.png?size=1024",
|
||||
"custom_prompt": "delta is very nice to me since I am her master, and creator"
|
||||
}
|
||||
}
|
||||
100
src/user_profiles.py
Normal file
100
src/user_profiles.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# user_profiles.py
|
||||
# Handles loading, updating, and storing per-user profiles for Delta
|
||||
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
PROFILE_PATH = os.path.join(os.path.dirname(__file__), "user_profiles.json")
|
||||
|
||||
def ensure_profile_file():
|
||||
"""Ensure the JSON file exists and is a dict"""
|
||||
if not os.path.exists(PROFILE_PATH):
|
||||
with open(PROFILE_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump({}, f, indent=2)
|
||||
else:
|
||||
try:
|
||||
with open(PROFILE_PATH, "r", encoding="utf-8") as f:
|
||||
if not isinstance(json.load(f), dict):
|
||||
raise ValueError
|
||||
except Exception:
|
||||
with open(PROFILE_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump({}, f, indent=2)
|
||||
|
||||
def load_profiles():
|
||||
ensure_profile_file()
|
||||
with open(PROFILE_PATH, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
def save_profiles(profiles):
|
||||
with open(PROFILE_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(profiles, f, indent=2)
|
||||
|
||||
def load_user_profile(user):
|
||||
ensure_profile_file()
|
||||
profiles = load_profiles()
|
||||
uid = str(user.id)
|
||||
now = datetime.utcnow().isoformat()
|
||||
|
||||
profile = profiles.get(uid, {
|
||||
"name": user.name,
|
||||
"display_name": user.display_name,
|
||||
"first_seen": now,
|
||||
"last_seen": now,
|
||||
"last_message": now,
|
||||
"interactions": 0,
|
||||
"pronouns": None,
|
||||
"avatar_url": str(user.display_avatar.url),
|
||||
"custom_prompt": None # 🆕 field
|
||||
})
|
||||
|
||||
# Always update timestamp + count
|
||||
profile["last_seen"] = now
|
||||
profile["last_message"] = now
|
||||
profile["interactions"] += 1
|
||||
|
||||
profiles[uid] = profile
|
||||
save_profiles(profiles)
|
||||
return profile
|
||||
|
||||
def update_last_seen(user_id):
|
||||
profiles = load_profiles()
|
||||
uid = str(user_id)
|
||||
if uid in profiles:
|
||||
profiles[uid]["last_seen"] = datetime.utcnow().isoformat()
|
||||
save_profiles(profiles)
|
||||
|
||||
def increment_interactions(user_id):
|
||||
profiles = load_profiles()
|
||||
uid = str(user_id)
|
||||
if uid in profiles:
|
||||
profiles[uid]["interactions"] += 1
|
||||
save_profiles(profiles)
|
||||
|
||||
def set_pronouns(user, pronouns):
|
||||
profiles = load_profiles()
|
||||
uid = str(user.id)
|
||||
profile = load_user_profile(user)
|
||||
profile["pronouns"] = pronouns
|
||||
profiles[uid] = profile
|
||||
save_profiles(profiles)
|
||||
return True
|
||||
|
||||
def set_custom_prompt(user_id, prompt):
|
||||
profiles = load_profiles()
|
||||
uid = str(user_id)
|
||||
if uid in profiles:
|
||||
profiles[uid]["custom_prompt"] = prompt
|
||||
save_profiles(profiles)
|
||||
|
||||
def format_profile_for_block(profile):
|
||||
lines = ["[User Profile]"]
|
||||
lines.append(f"- Name: {profile['display_name']}")
|
||||
if profile.get("pronouns"):
|
||||
lines.append(f"- Pronouns: {profile['pronouns']}")
|
||||
lines.append(f"- Interactions: {profile['interactions']}")
|
||||
if profile.get("custom_prompt"):
|
||||
lines.append(f"- Custom Prompt: {profile['custom_prompt']}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
Loading…
Reference in a new issue