Added the option to directly input webp file name or character image link when creating PNG for character cards

This commit is contained in:
2025-12-21 09:44:53 +13:00
parent 06d8b2e36c
commit bdde78475e
3 changed files with 195 additions and 159 deletions

2
next-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

9
package-lock.json generated
View File

@@ -1296,6 +1296,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@@ -1305,6 +1306,7 @@
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.2.0" "@types/react": "^19.2.0"
} }
@@ -1464,6 +1466,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -2075,6 +2078,7 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
@@ -2372,6 +2376,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.11",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@@ -2540,6 +2545,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
"integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -2549,6 +2555,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
"integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
@@ -2844,6 +2851,7 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@@ -2953,6 +2961,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },

View File

@@ -258,8 +258,7 @@ export default function Home() {
const cardData = JSON.stringify(pngData); const cardData = JSON.stringify(pngData);
const newImageData = Png.Generate(arrayBuffer, cardData); const newImageData = Png.Generate(arrayBuffer, cardData);
const newFileName = `${ const newFileName = `${(card.initialVersion?.name || card.data.name).replace(
(card.initialVersion?.name || card.data.name).replace(
/[^a-zA-Z0-9\-_]/g, /[^a-zA-Z0-9\-_]/g,
"_" "_"
) || "character" ) || "character"
@@ -290,6 +289,11 @@ export default function Home() {
}; };
const handleOpenMetadata = () => { const handleOpenMetadata = () => {
// Check if the input is a character metadata URL (janitorai.com/characters/...)
const isCharacterUrl = /janitorai\.com\/characters\//.test(characterUrl);
if (isCharacterUrl) {
// Extract character ID and open metadata page, then show second input
const match = characterUrl.match(/characters\/([\w-]+)/); const match = characterUrl.match(/characters\/([\w-]+)/);
if (match && match[1]) { if (match && match[1]) {
const characterId = match[1].split("_")[0]; const characterId = match[1].split("_")[0];
@@ -299,6 +303,31 @@ export default function Home() {
); );
setIsMetadataOpen(true); setIsMetadataOpen(true);
} }
return;
}
// Check if the input is a direct image link (webp filename or full image URL)
const isImagePath = /\.(webp|png|jpg|jpeg|gif)(\?.*)?$/i.test(characterUrl);
const isFullImageUrl = (characterUrl.startsWith("http://") || characterUrl.startsWith("https://")) && isImagePath;
const isWebpFilename = /^[\w-]+\.(webp|png|jpg|jpeg|gif)$/i.test(characterUrl);
if (isFullImageUrl || isWebpFilename) {
// Directly set the avatar URL without opening metadata
if (selectedCardIndex === null) return;
const avatarUrl = isFullImageUrl
? characterUrl
: `https://ella.janitorai.com/bot-avatars/${characterUrl}`;
const updatedCards = [...cards];
updatedCards[selectedCardIndex] = {
...updatedCards[selectedCardIndex],
avatarUrl,
};
setCards(updatedCards);
setDialogOpen(false);
return;
}
}; };
const handleFetchAvatar = async () => { const handleFetchAvatar = async () => {
@@ -650,8 +679,7 @@ export default function Home() {
card.alternate_greetings.length > 0 && ( card.alternate_greetings.length > 0 && (
<div className="mt-4"> <div className="mt-4">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<h4 className="font-medium">{`Alternate Greetings (${ <h4 className="font-medium">{`Alternate Greetings (${card.alternate_greetings?.length || 0
card.alternate_greetings?.length || 0
})`}</h4> })`}</h4>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
@@ -863,12 +891,12 @@ export default function Home() {
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{isMetadataOpen ? "Enter Avatar Path" : "Enter Character URL"} {isMetadataOpen ? "Enter Avatar Path" : "Fetch Avatar"}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
{isMetadataOpen {isMetadataOpen
? "Look for the avatar field in the opened tab and paste the value here." ? "Look for the avatar field in the opened tab and paste the value here."
: "Enter the Janitor character URL (https://janitorai.com/characters/...)."} : "Enter a character URL (janitorai.com/characters/...) to open metadata, or paste an image filename (id.webp) or full image URL directly."}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -888,19 +916,18 @@ export default function Home() {
) : ( ) : (
<div className="space-y-4"> <div className="space-y-4">
<Input <Input
placeholder="https://janitorai.com/characters/..." placeholder="URL or id.webp"
value={characterUrl} value={characterUrl}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setCharacterUrl(e.target.value) setCharacterUrl(e.target.value)
} }
/> />
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Upon clicking this button, a new tab will open with the For character URLs, a new tab will open with metadata. For image
character's metadata. Look for the avatar field and copy the filenames or full image URLs, the avatar will be set directly.
value before returning to this page.
</p> </p>
<Button onClick={handleOpenMetadata} className="w-full"> <Button onClick={handleOpenMetadata} className="w-full">
Open Metadata Fetch Avatar
</Button> </Button>
</div> </div>
)} )}