mirror of
https://github.com/severian-dev/sucker.severian.dev.git
synced 2026-02-25 10:22:03 +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:
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