formatting

This commit is contained in:
Severian
2025-10-03 23:28:34 +08:00
parent 936a8a7b62
commit 624f9f264b
2 changed files with 494 additions and 307 deletions

View File

@@ -91,28 +91,30 @@ export default function Home() {
const downloadJson = (card: Card) => {
// Use initial version for download, or current version if no initial version available
const downloadData = card.initialVersion ? {
name: card.initialVersion.name,
first_mes: card.initialVersion.first_mes,
description: card.initialVersion.description,
personality: card.initialVersion.personality,
mes_example: card.initialVersion.mes_example,
scenario: card.initialVersion.scenario,
} : {
name: card.name,
first_mes: card.first_mes,
description: card.description,
personality: card.personality,
mes_example: card.mes_example,
scenario: card.scenario,
};
const downloadData = card.initialVersion
? {
name: card.initialVersion.name,
first_mes: card.initialVersion.first_mes,
description: card.initialVersion.description,
personality: card.initialVersion.personality,
mes_example: card.initialVersion.mes_example,
scenario: card.initialVersion.scenario,
}
: {
name: card.name,
first_mes: card.first_mes,
description: card.description,
personality: card.personality,
mes_example: card.mes_example,
scenario: card.scenario,
};
const element = document.createElement("a");
const file = new Blob([JSON.stringify(downloadData, null, 2)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `${card.name.replace(/[^a-zA-Z0-9\-_]/g, '_')}.json`;
element.download = `${card.name.replace(/[^a-zA-Z0-9\-_]/g, "_")}.json`;
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
@@ -122,22 +124,27 @@ export default function Home() {
try {
const response = await fetch(`/api/proxy?changes=true&cardId=${card.id}`);
if (!response.ok) {
throw new Error('Failed to fetch changes');
throw new Error("Failed to fetch changes");
}
const changesData = await response.json();
const element = document.createElement("a");
const file = new Blob([JSON.stringify(changesData, null, 2)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `${card.name.replace(/[^a-zA-Z0-9\-_]/g, '_')}_changes.json`;
element.download = `${card.name.replace(
/[^a-zA-Z0-9\-_]/g,
"_"
)}_changes.json`;
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
} catch (error) {
console.error("Error downloading changes:", error);
alert("Failed to download changes. The card may not have version history.");
alert(
"Failed to download changes. The card may not have version history."
);
}
};
@@ -145,9 +152,9 @@ export default function Home() {
try {
const response = await fetch(`/api/proxy?changes=true&cardId=${card.id}`);
if (!response.ok) {
throw new Error('Failed to fetch changes');
throw new Error("Failed to fetch changes");
}
const changesData = await response.json();
setSelectedChanges(changesData);
setShowFullText(false); // Reset to diff view by default
@@ -187,27 +194,29 @@ export default function Home() {
const arrayBuffer = await pngBlob.arrayBuffer();
// Use initial version for PNG embedding, or current version if no initial version available
const pngData = card.initialVersion ? {
name: card.initialVersion.name,
first_mes: card.initialVersion.first_mes,
description: card.initialVersion.description,
personality: card.initialVersion.personality,
mes_example: card.initialVersion.mes_example,
scenario: card.initialVersion.scenario,
} : {
name: card.name,
first_mes: card.first_mes,
description: card.description,
personality: card.personality,
mes_example: card.mes_example,
scenario: card.scenario,
};
const pngData = card.initialVersion
? {
name: card.initialVersion.name,
first_mes: card.initialVersion.first_mes,
description: card.initialVersion.description,
personality: card.initialVersion.personality,
mes_example: card.initialVersion.mes_example,
scenario: card.initialVersion.scenario,
}
: {
name: card.name,
first_mes: card.first_mes,
description: card.description,
personality: card.personality,
mes_example: card.mes_example,
scenario: card.scenario,
};
const cardData = JSON.stringify(pngData);
const newImageData = Png.Generate(arrayBuffer, cardData);
const newFileName = `${
card.name.replace(/[^a-zA-Z0-9\-_]/g, '_') || "character"
card.name.replace(/[^a-zA-Z0-9\-_]/g, "_") || "character"
}.png`;
const newFile = new File([newImageData], newFileName, {
type: "image/png",
@@ -274,7 +283,8 @@ export default function Home() {
<div>
<h1 className="text-3xl font-bold">Sucker v2.0</h1>
<p className="text-sm text-muted-foreground">
Now with multimessage support! Tracks changes to character descriptions and scenarios across multiple extractions.
Now with multimessage support! Tracks changes to character
descriptions and scenarios across multiple extractions.
</p>
</div>
<Button
@@ -294,7 +304,10 @@ export default function Home() {
New: Multimessage Support
</span>
<p className="text-sm text-muted-foreground">
Sucker now tracks changes to character descriptions and scenarios across multiple messages. Cards with multiple versions show a version badge and offer a "Download Changes" button to get the change history.
Sucker now tracks changes to character descriptions and
scenarios across multiple messages. Cards with multiple versions
show a version badge and offer a "Download Changes" button to
get the change history.
</p>
</div>
</div>
@@ -333,7 +346,9 @@ export default function Home() {
REQUIRED: Set your custom prompt to <code>&lt;.&gt;</code>
</li>
<li className="mb-2">
REQUIRED: Set your persona (or create a new one) with the name <code>&#123;user&#125;</code> and the description should only have <code>.</code> in it.
REQUIRED: Set your persona (or create a new one) with the name{" "}
<code>&#123;user&#125;</code> and the description should only
have <code>.</code> in it.
</li>
<li className="mb-2">
Save settings and refresh the page. Not this page. <i>That</i>{" "}
@@ -343,7 +358,9 @@ export default function Home() {
Start a new chat with a character or multiple.
</li>
<li className="mb-2">
You can either send a dot to let sucker make a best guess about the char name, or send the char name yourself and it'll be used instead.
You can either send a dot to let sucker make a best guess
about the char name, or send the char name yourself and it'll
be used instead.
</li>
<li className="mb-2">
Hit the Refresh button here, and the cards should appear here.
@@ -359,10 +376,11 @@ export default function Home() {
I'm not storing shit.
</p>
<p className="mb-2">
<strong>New:</strong> If you send multiple messages with the same character name,
Sucker will track changes to the description and scenario fields. Cards with
multiple versions will show a version badge and offer a "Download Changes"
button to get a detailed change history with timestamps.
<strong>New:</strong> If you send multiple messages with the
same character name, Sucker will track changes to the
description and scenario fields. Cards with multiple versions
will show a version badge and offer a "Download Changes" button
to get a detailed change history with timestamps.
</p>
</div>
</CollapsibleContent>
@@ -400,19 +418,26 @@ export default function Home() {
</AccordionTrigger>
<AccordionContent>
<div id={`card-${index}`} className="space-y-4 mt-4">
{(card.initialVersion?.description || card.description) && (
{(card.initialVersion?.description ||
card.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.description}</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(card.initialVersion?.description || card.description);
<pre className="whitespace-pre-wrap font-sans text-sm">
{card.initialVersion?.description ||
card.description}
</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(
card.initialVersion?.description ||
card.description
);
}}
>
<Copy className="h-4 w-4" />
@@ -422,7 +447,8 @@ export default function Home() {
</AccordionItem>
</Accordion>
)}
{(card.initialVersion?.first_mes || card.first_mes) && (
{(card.initialVersion?.first_mes ||
card.first_mes) && (
<Accordion type="single" collapsible>
<AccordionItem value="first-message">
<AccordionTrigger>
@@ -430,13 +456,19 @@ export default function Home() {
</AccordionTrigger>
<AccordionContent>
<div className="flex justify-between">
<pre className="whitespace-pre-wrap font-sans text-sm">{card.initialVersion?.first_mes || card.first_mes}</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(card.initialVersion?.first_mes || card.first_mes);
<pre className="whitespace-pre-wrap font-sans text-sm">
{card.initialVersion?.first_mes ||
card.first_mes}
</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(
card.initialVersion?.first_mes ||
card.first_mes
);
}}
>
<Copy className="h-4 w-4" />
@@ -452,13 +484,19 @@ export default function Home() {
<AccordionTrigger>Scenario</AccordionTrigger>
<AccordionContent>
<div className="flex justify-between">
<pre className="whitespace-pre-wrap font-sans text-sm">{card.initialVersion?.scenario || card.scenario}</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(card.initialVersion?.scenario || card.scenario);
<pre className="whitespace-pre-wrap font-sans text-sm">
{card.initialVersion?.scenario ||
card.scenario}
</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(
card.initialVersion?.scenario ||
card.scenario
);
}}
>
<Copy className="h-4 w-4" />
@@ -468,7 +506,8 @@ export default function Home() {
</AccordionItem>
</Accordion>
)}
{(card.initialVersion?.mes_example || card.mes_example) && (
{(card.initialVersion?.mes_example ||
card.mes_example) && (
<Accordion type="single" collapsible>
<AccordionItem value="example-messages">
<AccordionTrigger>
@@ -476,13 +515,19 @@ export default function Home() {
</AccordionTrigger>
<AccordionContent>
<div className="flex justify-between">
<pre className="whitespace-pre-wrap font-sans text-sm">{card.initialVersion?.mes_example || card.mes_example}</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(card.initialVersion?.mes_example || card.mes_example);
<pre className="whitespace-pre-wrap font-sans text-sm">
{card.initialVersion?.mes_example ||
card.mes_example}
</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(
card.initialVersion?.mes_example ||
card.mes_example
);
}}
>
<Copy className="h-4 w-4" />
@@ -492,19 +537,26 @@ export default function Home() {
</AccordionItem>
</Accordion>
)}
{(card.initialVersion?.personality || card.personality) && (
{(card.initialVersion?.personality ||
card.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.personality}</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(card.initialVersion?.personality || card.personality);
<pre className="whitespace-pre-wrap font-sans text-sm">
{card.initialVersion?.personality ||
card.personality}
</pre>
<Button
variant="ghost"
size="icon"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(
card.initialVersion?.personality ||
card.personality
);
}}
>
<Copy className="h-4 w-4" />
@@ -620,13 +672,16 @@ export default function Home() {
Change History: {selectedChanges?.cardName}
</DialogTitle>
<DialogDescription className="flex items-center justify-between">
<span>Version history showing changes to description and scenario fields</span>
<span>
Version history showing changes to description and scenario
fields
</span>
<Button
variant="outline"
size="sm"
onClick={() => setShowFullText(!showFullText)}
>
{showFullText ? 'Show Changes Only' : 'Show Full Text'}
{showFullText ? "Show Changes Only" : "Show Full Text"}
</Button>
</DialogDescription>
</DialogHeader>
@@ -635,16 +690,20 @@ export default function Home() {
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<strong>Total Versions:</strong> {selectedChanges.totalVersions}
<strong>Total Versions:</strong>{" "}
{selectedChanges.totalVersions}
</div>
<div>
<strong>Current Version:</strong> {selectedChanges.currentVersion}
<strong>Current Version:</strong>{" "}
{selectedChanges.currentVersion}
</div>
<div>
<strong>Description Changes:</strong> {selectedChanges.summary.descriptionChanges}
<strong>Description Changes:</strong>{" "}
{selectedChanges.summary.descriptionChanges}
</div>
<div>
<strong>Scenario Changes:</strong> {selectedChanges.summary.scenarioChanges}
<strong>Scenario Changes:</strong>{" "}
{selectedChanges.summary.scenarioChanges}
</div>
</div>
@@ -660,16 +719,20 @@ export default function Home() {
</h4>
<div className="text-sm text-muted-foreground">
{new Date(version.timestamp).toLocaleString()}
{version.messageCount && ` • Message ${version.messageCount}`}
{version.messageCount &&
` • Message ${version.messageCount}`}
</div>
</div>
{version.changes.description && (
<div className="mb-3">
<h5 className="font-medium text-sm mb-1">Description Change:</h5>
{version.changeType === 'initial' ? (
<h5 className="font-medium text-sm mb-1">
Description Change:
</h5>
{version.changeType === "initial" ? (
<div className="bg-blue-50 dark:bg-blue-950 p-2 rounded text-sm">
<strong>Initial Content:</strong> {version.changes.description.new}
<strong>Initial Content:</strong>{" "}
{version.changes.description.new}
</div>
) : (
<div className="space-y-2">
@@ -677,15 +740,18 @@ export default function Home() {
<div className="bg-green-50 dark:bg-green-950 p-2 rounded text-sm">
<div className="flex justify-between items-start">
<div>
<strong>Added:</strong> {version.addedText.description}
<strong>Added:</strong>{" "}
{version.addedText.description}
</div>
<Button
variant="ghost"
size="icon"
<Button
variant="ghost"
size="icon"
className="ml-2 h-6 w-6"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(version.addedText.description);
onClick={(e) => {
e.stopPropagation();
copyToClipboard(
version.addedText.description
);
}}
>
<Copy className="h-3 w-3" />
@@ -695,16 +761,19 @@ export default function Home() {
)}
{version.removedText?.description && (
<div className="bg-red-50 dark:bg-red-950 p-2 rounded text-sm">
<strong>Removed:</strong> {version.removedText.description}
<strong>Removed:</strong>{" "}
{version.removedText.description}
</div>
)}
{showFullText && (
<div className="space-y-1 mt-2 pt-2 border-t">
<div className="bg-gray-50 dark:bg-gray-950 p-2 rounded text-xs">
<strong>Full Old:</strong> {version.changes.description.old}
<strong>Full Old:</strong>{" "}
{version.changes.description.old}
</div>
<div className="bg-gray-50 dark:bg-gray-950 p-2 rounded text-xs">
<strong>Full New:</strong> {version.changes.description.new}
<strong>Full New:</strong>{" "}
{version.changes.description.new}
</div>
</div>
)}
@@ -712,13 +781,16 @@ export default function Home() {
)}
</div>
)}
{version.changes.scenario && (
<div>
<h5 className="font-medium text-sm mb-1">Scenario Change:</h5>
{version.changeType === 'initial' ? (
<h5 className="font-medium text-sm mb-1">
Scenario Change:
</h5>
{version.changeType === "initial" ? (
<div className="bg-blue-50 dark:bg-blue-950 p-2 rounded text-sm">
<strong>Initial Content:</strong> {version.changes.scenario.new}
<strong>Initial Content:</strong>{" "}
{version.changes.scenario.new}
</div>
) : (
<div className="space-y-2">
@@ -726,15 +798,18 @@ export default function Home() {
<div className="bg-green-50 dark:bg-green-950 p-2 rounded text-sm">
<div className="flex justify-between items-start">
<div>
<strong>Added:</strong> {version.addedText.scenario}
<strong>Added:</strong>{" "}
{version.addedText.scenario}
</div>
<Button
variant="ghost"
size="icon"
<Button
variant="ghost"
size="icon"
className="ml-2 h-6 w-6"
onClick={(e) => {
e.stopPropagation();
copyToClipboard(version.addedText.scenario);
onClick={(e) => {
e.stopPropagation();
copyToClipboard(
version.addedText.scenario
);
}}
>
<Copy className="h-3 w-3" />
@@ -744,16 +819,19 @@ export default function Home() {
)}
{version.removedText?.scenario && (
<div className="bg-red-50 dark:bg-red-950 p-2 rounded text-sm">
<strong>Removed:</strong> {version.removedText.scenario}
<strong>Removed:</strong>{" "}
{version.removedText.scenario}
</div>
)}
{showFullText && (
<div className="space-y-1 mt-2 pt-2 border-t">
<div className="bg-gray-50 dark:bg-gray-950 p-2 rounded text-xs">
<strong>Full Old:</strong> {version.changes.scenario.old}
<strong>Full Old:</strong>{" "}
{version.changes.scenario.old}
</div>
<div className="bg-gray-50 dark:bg-gray-950 p-2 rounded text-xs">
<strong>Full New:</strong> {version.changes.scenario.new}
<strong>Full New:</strong>{" "}
{version.changes.scenario.new}
</div>
</div>
)}