mirror of
https://github.com/severian-dev/sucker.severian.dev.git
synced 2026-01-03 14:02:24 +00:00
Added the option to directly input webp file name or character image link when creating PNG for character cards
This commit is contained in:
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <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
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -1296,6 +1296,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1305,6 +1306,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
@@ -1464,6 +1466,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -2075,6 +2078,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
@@ -2372,6 +2376,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -2540,6 +2545,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz",
|
||||
"integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -2549,6 +2555,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz",
|
||||
"integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -2844,6 +2851,7 @@
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
|
||||
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
@@ -2953,6 +2961,7 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
|
||||
343
src/app/page.tsx
343
src/app/page.tsx
@@ -258,12 +258,11 @@ export default function Home() {
|
||||
const cardData = JSON.stringify(pngData);
|
||||
|
||||
const newImageData = Png.Generate(arrayBuffer, cardData);
|
||||
const newFileName = `${
|
||||
(card.initialVersion?.name || card.data.name).replace(
|
||||
/[^a-zA-Z0-9\-_]/g,
|
||||
"_"
|
||||
) || "character"
|
||||
}.png`;
|
||||
const newFileName = `${(card.initialVersion?.name || card.data.name).replace(
|
||||
/[^a-zA-Z0-9\-_]/g,
|
||||
"_"
|
||||
) || "character"
|
||||
}.png`;
|
||||
const newFile = new File([new Uint8Array(newImageData)], newFileName, {
|
||||
type: "image/png",
|
||||
});
|
||||
@@ -290,14 +289,44 @@ export default function Home() {
|
||||
};
|
||||
|
||||
const handleOpenMetadata = () => {
|
||||
const match = characterUrl.match(/characters\/([\w-]+)/);
|
||||
if (match && match[1]) {
|
||||
const characterId = match[1].split("_")[0];
|
||||
window.open(
|
||||
`https://janitorai.com/hampter/characters/${characterId}`,
|
||||
"_blank"
|
||||
);
|
||||
setIsMetadataOpen(true);
|
||||
// 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-]+)/);
|
||||
if (match && match[1]) {
|
||||
const characterId = match[1].split("_")[0];
|
||||
window.open(
|
||||
`https://janitorai.com/hampter/characters/${characterId}`,
|
||||
"_blank"
|
||||
);
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -588,71 +617,70 @@ export default function Home() {
|
||||
<div id={`card-${index}`} className="space-y-4 mt-4">
|
||||
{(card.initialVersion?.description ||
|
||||
card.data?.description) && (
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="description">
|
||||
<AccordionTrigger>Description</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.description ||
|
||||
card.data.description}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.description ||
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="description">
|
||||
<AccordionTrigger>Description</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.description ||
|
||||
card.data.description}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.description ||
|
||||
card.data.description
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
{(card.initialVersion?.first_mes ||
|
||||
card.data?.first_mes) && (
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="first-message">
|
||||
<AccordionTrigger>
|
||||
First Message
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.first_mes ||
|
||||
card.data.first_mes}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.first_mes ||
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="first-message">
|
||||
<AccordionTrigger>
|
||||
First Message
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.first_mes ||
|
||||
card.data.first_mes}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.first_mes ||
|
||||
card.data.first_mes
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
{card.alternate_greetings &&
|
||||
card.alternate_greetings.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="font-medium">{`Alternate Greetings (${
|
||||
card.alternate_greetings?.length || 0
|
||||
})`}</h4>
|
||||
<h4 className="font-medium">{`Alternate Greetings (${card.alternate_greetings?.length || 0
|
||||
})`}</h4>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -721,93 +749,93 @@ export default function Home() {
|
||||
)}
|
||||
{(card.initialVersion?.scenario ||
|
||||
card.data?.scenario) && (
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="scenario">
|
||||
<AccordionTrigger>Scenario</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.scenario ||
|
||||
card.data.scenario}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.scenario ||
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="scenario">
|
||||
<AccordionTrigger>Scenario</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.scenario ||
|
||||
card.data.scenario}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.scenario ||
|
||||
card.data.scenario
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
{(card.initialVersion?.mes_example ||
|
||||
card.data?.mes_example) && (
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="example-messages">
|
||||
<AccordionTrigger>
|
||||
Example Messages
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.mes_example ||
|
||||
card.data.mes_example}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.mes_example ||
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="example-messages">
|
||||
<AccordionTrigger>
|
||||
Example Messages
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.mes_example ||
|
||||
card.data.mes_example}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.mes_example ||
|
||||
card.data.mes_example
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
{(card.initialVersion?.personality ||
|
||||
card.data?.personality) && (
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="personality">
|
||||
<AccordionTrigger>Personality</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.personality ||
|
||||
card.data.personality}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.personality ||
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="personality">
|
||||
<AccordionTrigger>Personality</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="flex justify-between">
|
||||
<pre className="whitespace-pre-wrap font-sans text-sm">
|
||||
{card.initialVersion?.personality ||
|
||||
card.data.personality}
|
||||
</pre>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copyToClipboard(
|
||||
card.initialVersion?.personality ||
|
||||
card.data.personality
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
@@ -863,12 +891,12 @@ export default function Home() {
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isMetadataOpen ? "Enter Avatar Path" : "Enter Character URL"}
|
||||
{isMetadataOpen ? "Enter Avatar Path" : "Fetch Avatar"}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{isMetadataOpen
|
||||
? "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>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -888,19 +916,18 @@ export default function Home() {
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<Input
|
||||
placeholder="https://janitorai.com/characters/..."
|
||||
placeholder="URL or id.webp"
|
||||
value={characterUrl}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setCharacterUrl(e.target.value)
|
||||
}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Upon clicking this button, a new tab will open with the
|
||||
character's metadata. Look for the avatar field and copy the
|
||||
value before returning to this page.
|
||||
For character URLs, a new tab will open with metadata. For image
|
||||
filenames or full image URLs, the avatar will be set directly.
|
||||
</p>
|
||||
<Button onClick={handleOpenMetadata} className="w-full">
|
||||
Open Metadata
|
||||
Fetch Avatar
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user