Change Avatar Custom Code

This is a code to change the Avatar of the Character, just type /change-avatar

Version 4

With /roll-avatar to re-generate the image with the same description.

// This is the code to change the Avatar of the Character
// `/change-avatar` and it will prompt the character to describe themselves, then use that as a prompt for the avatar.
// `/roll-avatar` to re-generate the image with the same description.

window.generateImage = async function (mode, description) {
  if (mode == 'new') {
    let prevDataUrl = oc.character.avatar.url;
    let { dataUrl } = await oc.textToImage({
      prompt: `${oc.character.imagePromptPrefix == '' ? '' : oc.character.imagePromptPrefix + ', '}${description}${oc.character.imagePromptSuffix == '' ? '' : ', ' + oc.character.imagePromptSuffix}`,
      negativePrompt: ``,
    });
    oc.character.avatar.url = await resizeDataURLWidth(dataUrl, 300);
    oc.thread.messages.pop()
    oc.thread.messages.pop()
    oc.thread.messages.pop()
    oc.thread.messages.push({
      author: "system",
      name: "System",
      content: `<p>New Avatar:</p><img src="${dataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p>Old Avatar:</p><img src="${prevDataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p><button onclick="oc.character.avatar.url = '${prevDataUrl}'">Revert to Old Avatar?</button><br><button onclick="oc.character.avatar.url = '${dataUrl}'">Back to Generated Avatar?</button></p>`,
      hiddenFrom: ['ai'],
      expectsReply: false,
      avatar: { size: 0 },
    })
  } else {
    let prevDataUrl = oc.character.avatar.url;
    let { dataUrl } = await oc.textToImage({
      prompt: `${oc.character.imagePromptPrefix == '' ? '' : oc.character.imagePromptPrefix + ', '}${description}${oc.character.imagePromptSuffix == '' ? '' : ', ' + oc.character.imagePromptSuffix}`,
      negativePrompt: ``,
    });
    oc.character.avatar.url = await resizeDataURLWidth(dataUrl, 300);
    oc.thread.messages.pop()
    oc.thread.messages.push({
      author: "system",
      name: "System",
      content: `<p>New Avatar:</p><img src="${dataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p>Old Avatar:</p><img src="${prevDataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p><button onclick="oc.character.avatar.url = '${prevDataUrl}'">Revert to Old Avatar?</button><br><button onclick="oc.character.avatar.url = '${dataUrl}'">Back to Generated Avatar?</button></p>`,
      hiddenFrom: ['ai'],
      expectsReply: false,
      avatar: { size: 0 },
    })
  }
}

window.alreadyGenerating = false;
oc.thread.on("MessageAdded", async function () {
  let lastMessage = oc.thread.messages.at(-1);
  oc.character.customData.description = oc.character.customData.description
      ? oc.character.customData.description
    : "";
  // console.log(lastMessage);
  if (lastMessage.author !== "ai") {
    if (/^\/change-avatar/.test(lastMessage.content)) {
      oc.thread.messages.pop()
      oc.thread.messages.push({
        author: "user",
        content: `${oc.character.name}, describe your appearance and personality in detail. Reply with a comma-separated list of keywords and keyphrases which *visually* capture your apprearance only. Imagine you're giving a list of keywords to an artist who will use them to paint or draw a portrait of you. Describe the your appearance with keywords/keyphrases, including your race, sex/gender, class, age, etc. Respond with only the comma-separated keyphrases - nothing more, nothing less.

Start with "Here is my current appearance:"`,
      })
    }
    if (/^\/roll-avatar/.test(lastMessage.content)) {
      oc.thread.messages.pop()
      oc.thread.messages.push({
        author: "system",
        name: "System",
        content: `The AI is Changing their Avatar, please wait ... <br><progress style="width:80px"></progress>`,
        hiddenFrom: ['ai'],
        expectsReply: false,
        avatar: { size: 0 },
      })
      generateImage('reroll', oc.character.customData.description);
    }
  };

  if (alreadyGenerating) return;
alreadyGenerating = true;

if (lastMessage.author === "ai") {
  if (/^Here is my current appearance\:/.test(lastMessage.content)) {
    let description = lastMessage.content.replace("Here is my current appearance:", '');
    console.log("DESCRIPTION: ", description)
    oc.character.customData.description = description
    oc.thread.messages.push({
      author: "system",
      name: "System",
      content: `The AI is Changing their Avatar, please wait ... <br><progress style="width:80px"></progress>`,
      hiddenFrom: ['ai'],
      expectsReply: false,
      avatar: { size: 0 },
    })

    console.log("PUSHED MESSAGE");
    generateImage('new', description);
  }
};
alreadyGenerating = false;
})

async function resizeDataURLWidth(dataURL, newWidth) {
  const blob = await fetch(dataURL).then(res => res.blob());
  const bitmap = await createImageBitmap(blob);
  const canvas = Object.assign(document.createElement('canvas'), { width: newWidth, height: bitmap.height / bitmap.width * newWidth });
  const ctx = canvas.getContext('2d');
  ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
  return canvas.toDataURL('image/jpeg');
}

Version 3

Uses the new imagePromptPrefix and imagePromptSuffix, also removed the negativePrompt since we can add them on the imagePromptSuffix!

// This is the code to change the Avatar of the Character
// Just type `/change-avatar` and it will start to generate a new avatar based on the description on 'roleInstruction'.
//
oc.thread.on("MessageAdded", async function() {
  let lastMessage = oc.thread.messages.at(-1);
  // console.log(lastMessage);
  if(lastMessage.author !== "ai") {
    if (/^\/change-avatar/.test(lastMessage.content)) {
      oc.thread.messages.pop()
      oc.thread.messages.push({
        author: "user",
        content: `${oc.character.name}, describe your appearance and personality in detail. Reply with a comma-separated list of keywords and keyphrases which *visually* capture your apprearance only. Imagine you're giving a list of keywords to an artist who will use them to paint or draw a portrait of you. Describe the your appearance with keywords/keyphrases, including your race, sex/gender, class, age, etc. Respond with only the comma-separated keyphrases - nothing more, nothing less.

Start with "Here is my current appearance:"`,
      })
    }
  };
})


window.alreadyGenerating = false;
oc.thread.on("MessageAdded", async function() {
  let lastMessage = oc.thread.messages.at(-1);
  if(alreadyGenerating) return;
  alreadyGenerating = true;

  if(lastMessage.author === "ai") {
    if (/^Here is my current appearance\:/.test(lastMessage.content)) {
      let description = lastMessage.content.replace("Here is my current appearance:", '');
      console.log("DESCRIPTION: ", description)
      oc.thread.messages.push({
        author: "system",
        name: "System",
        content: `The AI is Changing their Avatar, please wait ... <br><progress style="width:80px"></progress>`,
        hiddenFrom: ['ai'],
        expectsReply: false,
        avatar: {size:0},
      })

      console.log("PUSHED MESSAGE");
      (async function() {
        let prevDataUrl = oc.character.avatar.url;
      let { dataUrl } = await oc.textToImage({
        prompt: `${oc.character.imagePromptPrefix == '' ? '' : oc.character.imagePromptPrefix + ', '}${description}${oc.character.imagePromptSuffix == '' ? '' : ', '+ oc.character.imagePromptSuffix}`,
        negativePrompt: ``,
        });
        oc.character.avatar.url = await resizeDataURLWidth(dataUrl, 300);
        oc.thread.messages.pop()
        oc.thread.messages.pop()
        oc.thread.messages.pop()
        oc.thread.messages.push({
          author: "system",
          name: "System",
          content: `<p>New Avatar:</p><img src="${dataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p>Old Avatar:</p><img src="${prevDataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p><button onclick="oc.character.avatar.url = '${prevDataUrl}'">Revert to Old Avatar?</button><br><button onclick="oc.character.avatar.url = '${dataUrl}'">Back to Generated Avatar?</button></p>`,
          hiddenFrom: ['ai'],
          expectsReply: false,
          avatar: {size:0},
        })
      })();
    }
  };
  alreadyGenerating = false;
})

async function resizeDataURLWidth(dataURL, newWidth) {
  const blob = await fetch(dataURL).then(res => res.blob());
  const bitmap = await createImageBitmap(blob);
  const canvas = Object.assign(document.createElement('canvas'), { width: newWidth, height: bitmap.height / bitmap.width * newWidth });
  const ctx = canvas.getContext('2d');
  ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
  return canvas.toDataURL('image/jpeg');
}

Version 2

Allows the AI to reply and change their appearance based on what they replied.

// This is the code to change the Avatar of the Character
// Just type `/change-avatar` and it will start to generate a new avatar based on the description on 'roleInstruction'.
//
oc.thread.on("MessageAdded", async function() {
  let lastMessage = oc.thread.messages.at(-1);
  // console.log(lastMessage);
  if(lastMessage.author !== "ai") {
    if (/^\/change-avatar/.test(lastMessage.content)) {
      oc.thread.messages.pop()
      oc.thread.messages.push({
        author: "user",
        content: `${oc.character.name}, describe your appearance in detail. Reply with a comma-separated list of keywords and keyphrases which *visually* capture your apprearance only. Imagine you're giving a list of keywords to an artist who will use them to draw you. Describe the your appearance with keywords/keyphrases, including your race, class, age, etc. Respond with only the comma-separated keyphrases - nothing more, nothing less.

Start with "Here is my current appearance:"`,
      })
    }
  };
})


window.alreadyGenerating = false;
oc.thread.on("MessageAdded", async function() {
  let lastMessage = oc.thread.messages.at(-1);
  if(alreadyGenerating) return;
  alreadyGenerating = true;

  if(lastMessage.author === "ai") {
    if (/^Here is my current appearance\:/.test(lastMessage.content)) {
      let description = lastMessage.content.replace("Here is my current appearance:", '');
      console.log("DESCRIPTION: ", description)
      oc.thread.messages.push({
        author: "system",
        name: "System",
        content: `The AI is Changing their Avatar, please wait ... <br><progress style="width:80px"></progress>`,
        hiddenFrom: ['ai'],
        expectsReply: false,
        avatar: {size:0},
      })

      console.log("PUSHED MESSAGE");
      (async function() {
        let prevDataUrl = oc.character.avatar.url;
      let { dataUrl } = await oc.textToImage({
        prompt: `${description}, profile picture, digital art, detailed, concept avatar art, uhd, 8k`,
        negativePrompt: `(semi-realistic, hands, worst quality, blurry, low resolution, low quality:1.2)`,
        });
        oc.character.avatar.url = await resizeDataURLWidth(dataUrl, 300);
        oc.thread.messages.pop()
        oc.thread.messages.pop()
        oc.thread.messages.pop()
        oc.thread.messages.push({
          author: "system",
          name: "System",
          content: `<p>New Avatar:</p><img src="${dataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p>Old Avatar:</p><img src="${prevDataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p><button onclick="oc.character.avatar.url = '${prevDataUrl}'">Revert to Old Avatar?</button><br><button onclick="oc.character.avatar.url = '${dataUrl}'">Back to Generated Avatar?</button></p>`,
          hiddenFrom: ['ai'],
          expectsReply: false,
          avatar: {size:0},
        })
      })();
    }
  };
  alreadyGenerating = false;
})

async function resizeDataURLWidth(dataURL, newWidth) {
  const blob = await fetch(dataURL).then(res => res.blob());
  const bitmap = await createImageBitmap(blob);
  const canvas = Object.assign(document.createElement('canvas'), { width: newWidth, height: bitmap.height / bitmap.width * newWidth });
  const ctx = canvas.getContext('2d');
  ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
  return canvas.toDataURL('image/jpeg');
}

Version 1

It will generate a new avatar based on the description on 'roleInstruction' which is the Description/Instruction on the Input Box on the Edit Character after the Name Input Box:

oc.thread.on("MessageAdded", async function() {
  let lastMessage = oc.thread.messages.at(-1);
  // console.log(lastMessage);
  if(lastMessage.author !== "ai") {
    if (/^\/change-avatar/.test(lastMessage.content)) {
      oc.thread.messages.pop()
      oc.thread.messages.push({
        author: "system",
        name: "System",
        content: `The AI is Changing their Avatar, please wait ... <br><progress style="width:80px"></progress>`,
        hiddenFrom: ['ai'],
        expectsReply: false,
        avatar: {size:0},
      })

      let response = await oc.getInstructCompletion({
        instruction: `Below is a description of a character. Please turn it into a comma-separated 20-item list of descriptive keywords/keyphrases which visually capture this character.
---
${oc.character.roleInstruction}
---
Reply with a comma-separated 20-item list of keywords and keyphrases which *visually* capture the above character. Imagine you're giving a list of keywords to an artist who will use them to draw the character. Describe the character's appearance with keywords/keyphrases, including their race, class, age, etc. Respond with only the comma-separated 20-item keyphrases - nothing more, nothing less.`,
        stopSequences: ["\n\n"], // this is optional - tells the AI to stop generating when it generates a newline
      });
      if(response.stopReason === "error") throw new Error(`response.stopReason === "error"`);
      // Generate a Profile Picture
      (async function() {
        let prevDataUrl = oc.character.avatar.url;
      let { dataUrl } = await oc.textToImage({
        prompt: `${response.text}, profile picture, digital art, detailed, concept avatar art`,
        negativePrompt: `(semi-realistic, hands, worst quality, blurry, low resolution, low quality:1.2)`,
        });
        oc.character.avatar.url = await resizeDataURLWidth(dataUrl, 300);
        oc.thread.messages.pop()
        oc.thread.messages.push({
          author: "system",
          name: "System",
          content: `<p>New Avatar:</p><img src="${dataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p>Old Avatar:</p><img src="${prevDataUrl}" style="max-width: 200px; width: 100%; max-height: 200px; aspect-ratio: 1 / 1; object-fit: contain;">
<p><button onclick="oc.character.avatar.url = '${prevDataUrl}'">Revert to Old Avatar?</button><br><button onclick="oc.character.avatar.url = '${dataUrl}'">Back to Generated Avatar?</button></p>`,
          hiddenFrom: ['ai'],
          expectsReply: false,
          avatar: {size:0},
        })
      })();
    }
  };
})

async function resizeDataURLWidth(dataURL, newWidth) {
  const blob = await fetch(dataURL).then(res => res.blob());
  const bitmap = await createImageBitmap(blob);
  const canvas = Object.assign(document.createElement('canvas'), { width: newWidth, height: bitmap.height / bitmap.width * newWidth });
  const ctx = canvas.getContext('2d');
  ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
  return canvas.toDataURL('image/jpeg');
}
Edit

Pub: 30 Dec 2023 13:40 UTC

Edit: 31 Jan 2025 05:28 UTC

Views: 1088