chore: 2.1

This commit is contained in:
Severian
2025-12-10 08:27:22 +08:00
parent f99985ad6c
commit 95f5a3e725
2 changed files with 185 additions and 27 deletions

View File

@@ -31,6 +31,7 @@ import {
Collapsible, Collapsible,
CollapsibleTrigger, CollapsibleTrigger,
} from "@/components/ui/collapsible"; } from "@/components/ui/collapsible";
import { CollapsibleInfobox } from "@/components/ui/collapsible-infobox";
import Script from "next/script"; import Script from "next/script";
interface CardDataV2 { interface CardDataV2 {
@@ -82,7 +83,9 @@ export default function Home() {
Record<string, number> Record<string, number>
>({}); >({});
const [proxyUrl, setProxyUrl] = useState("https://sucker.severian.dev/api/proxy"); const [proxyUrl, setProxyUrl] = useState(
"https://sucker.severian.dev/api/proxy"
);
const fetchCards = async () => { const fetchCards = async () => {
try { try {
@@ -321,7 +324,10 @@ export default function Home() {
return ( return (
<main className="min-h-screen bg-background text-foreground"> <main className="min-h-screen bg-background text-foreground">
<Script src="https://www.googletagmanager.com/gtag/js?id=G-YVD6QFSR71" strategy="afterInteractive" /> <Script
src="https://www.googletagmanager.com/gtag/js?id=G-YVD6QFSR71"
strategy="afterInteractive"
/>
<Script id="gtag-init" strategy="afterInteractive"> <Script id="gtag-init" strategy="afterInteractive">
{`window.dataLayer = window.dataLayer || []; {`window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag(){dataLayer.push(arguments);}
@@ -331,9 +337,9 @@ export default function Home() {
<div className="container mx-auto px-4 py-8"> <div className="container mx-auto px-4 py-8">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<div> <div>
<h1 className="text-3xl font-bold">Sucker v2.0</h1> <h1 className="text-3xl font-bold">Sucker v2.1</h1>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
A couple of updates, see below. Just some notes this time
</p> </p>
</div> </div>
<Button <Button
@@ -346,13 +352,34 @@ export default function Home() {
</div> </div>
<Separator className="my-4" /> <Separator className="my-4" />
<div className="mb-8"> {/* Collapsible infoboxes */}
<div className="bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 rounded-lg p-4"> {[
<div className="flex flex-col justify-between"> {
<span className="text-lg font-semibold text-blue-800 dark:text-blue-200"> title: "(Dec 2025) A note about fetching avatars",
V2 charcard format, multi-turn support for scripts/lorebooks, content: (
alternate greetings. <>
</span> <p className="text-sm text-muted-foreground">
The platform you suck from has implemented limited visibility
of metadata for certain content with a particular 'obscenity
rating'. This means that in some cases, the Fetch Avatar flow
here will show a 404 - character not found error at the end.
</p>
<p className="text-sm text-muted-foreground">
Sometimes (but not always), the avatar URL can still be
fetched after a day or two since the bot was published.
</p>
<p className="text-sm text-muted-foreground">
As of this moment, can't really find a fix for it, so you'll
have to download the image yourself and just add the image to
the card someplace else.
</p>
</>
),
},
{
title:
"(Oct 2025) V2 charcard format, multi-turn support for scripts/lorebooks, alternate greetings.",
content: (
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Sucker now tracks changes to character descriptions and Sucker now tracks changes to character descriptions and
scenarios across multiple messages. Cards with multiple versions scenarios across multiple messages. Cards with multiple versions
@@ -365,13 +392,79 @@ export default function Home() {
instead of the character name. instead of the character name.
<br /> <br />
Directions are updated below. Make sure you read 'em. Directions are updated below. Make sure you read 'em.
</p>
),
},
{
title: "List of mirrors",
content: (
<>
<p className="text-sm text-muted-foreground">
Sucker goes down sometimes on severian.dev because I use the
server for other stuff. Here's a full list of existing sucker
instances (thanks to those who signed up for it!):
</p>
<ul className="text-sm flex flex-col sm:flex-row list-none">
<li className="after:content-none sm:after:mx-2 sm:after:content-['•'] sm:last:after:content-none">
<a
className="text-yellow-600"
href="https://sucker.severian.dev"
>
severian.dev
</a>
</li>
<li className="after:content-none sm:after:mx-2 sm:after:content-['•'] sm:last:after:content-none">
<a
className="text-yellow-600"
href="https://sucker.trashpanda.land"
>
trashpanda.land
</a>
</li>
<li className="after:content-none sm:after:mx-2 sm:after:content-['•'] sm:last:after:content-none">
<a
className="text-yellow-600"
href="https://sucker.hitani.me"
>
hitani.me
</a>
</li>
<li className="after:content-none sm:after:mx-2 sm:after:content-['•'] sm:last:after:content-none">
<a
className="text-yellow-600"
href="https://succ.portalnexus.link"
>
portalnexus.link
</a>
</li>
<li className="after:content-none sm:after:mx-2 sm:after:content-['•'] sm:last:after:content-none">
<a
className="text-yellow-600"
href="https://sucker.lemuria.dev"
>
lemuria.dev
</a>
</li>
</ul>
<p className="text-sm text-muted-foreground">
<br /> <br />
If you're interested in hosting your own sucker instance, lmk If you're interested in hosting your own sucker instance, lmk
via Discord: @lyseverian, I've made the GH repo private for now. via Discord: @lyseverian, I've made the GH repo private for
now. Or send me a message if there's anything you think that
could be added here, open to suggestions.
</p> </p>
</div> </>
</div> ),
</div> },
].map((infobox, idx) => (
<CollapsibleInfobox
key={infobox.title}
title={infobox.title}
defaultOpen={false}
>
{infobox.content}
</CollapsibleInfobox>
))}
<Collapsible <Collapsible
open={isInstructionsOpen} open={isInstructionsOpen}
@@ -399,16 +492,18 @@ export default function Home() {
</p> </p>
<ol className="list-decimal list-inside"> <ol className="list-decimal list-inside">
<li className="mb-2"> <li className="mb-2">
Put <code style={{ color: "#fff0b9" }}>{proxyUrl}</code> in your Put <code style={{ color: "#fff0b9" }}>{proxyUrl}</code> in
API settings, any value for model and key. your API settings, any value for model and key.
</li> </li>
<li className="mb-2"> <li className="mb-2">
REQUIRED: Set your custom prompt to <code style={{ color: "#fff0b9" }}>&lt;.&gt;</code> REQUIRED: Set your custom prompt to{" "}
<code style={{ color: "#fff0b9" }}>&lt;.&gt;</code>
</li> </li>
<li className="mb-2"> <li className="mb-2">
REQUIRED: Set your persona (or create a new one) with the name{" "} REQUIRED: Set your persona (or create a new one) with the name{" "}
<code style={{ color: "#fff0b9" }}>&#123;user&#125;</code> and the description should only <code style={{ color: "#fff0b9" }}>&#123;user&#125;</code> and
have <code style={{ color: "#fff0b9" }}>.</code> in it. the description should only have{" "}
<code style={{ color: "#fff0b9" }}>.</code> in it.
</li> </li>
<li className="mb-2"> <li className="mb-2">
Save settings and refresh the page. Not this page. <i>That</i>{" "} Save settings and refresh the page. Not this page. <i>That</i>{" "}
@@ -416,7 +511,10 @@ export default function Home() {
</li> </li>
<li className="mb-2">Start a new chat with a character.</li> <li className="mb-2">Start a new chat with a character.</li>
<li className="mb-2"> <li className="mb-2">
Char name inference is implemented: if you send just a dot: <code style={{ color: "#fff0b9" }}>.</code>, sucker will use the inferred name from the persona tag, or you can send the character name yourself. Char name inference is implemented: if you send just a dot:{" "}
<code style={{ color: "#fff0b9" }}>.</code>, sucker will use
the inferred name from the persona tag, or you can send the
character name yourself.
</li> </li>
<li className="mb-2"> <li className="mb-2">
Hit the Refresh button here, and the cards should appear here. Hit the Refresh button here, and the cards should appear here.
@@ -425,12 +523,20 @@ export default function Home() {
If you're interested in capturing alternate greetings, start a If you're interested in capturing alternate greetings, start a
new chat and send the conversation ID as first message instead new chat and send the conversation ID as first message instead
of the character name. The format is{" "} of the character name. The format is{" "}
<code style={{ color: "#fff0b9" }}>[sucker:conv=conversationId]</code> which you'll be <code style={{ color: "#fff0b9" }}>
given when creating a new card. [sucker:conv=conversationId]
</code>{" "}
which you'll be given when creating a new card.
</li> </li>
<li className="mb-2"> <li className="mb-2">
You can also send more messages with possible keywords to trigger scripts/lorebooks. 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 You can also send more messages with possible keywords to
history with timestamps. Unfortunately, lorebook creation is out of scope at the moment, but you can use the changes detected to modify the character card yourself post-export. trigger scripts/lorebooks. 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.
Unfortunately, lorebook creation is out of scope at the
moment, but you can use the changes detected to modify the
character card yourself post-export.
</li> </li>
<li className="mb-2"> <li className="mb-2">
Download the JSON files or go through a little more effort to Download the JSON files or go through a little more effort to

View File

@@ -0,0 +1,52 @@
import { useState } from "react";
import { ChevronDown, ChevronUp } from "lucide-react";
import { Button } from "@/components/ui/button";
export interface CollapsibleInfoboxProps {
title: string;
children: React.ReactNode;
defaultOpen?: boolean;
className?: string;
}
export function CollapsibleInfobox({
title,
children,
defaultOpen = false,
className = "",
}: CollapsibleInfoboxProps) {
const [open, setOpen] = useState(defaultOpen);
return (
<div
className={`bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 rounded-lg p-4 mb-4 ${className}`}
>
<div
className="flex items-center justify-between cursor-pointer"
onClick={() => setOpen((v) => !v)}
>
<span className="text-lg font-semibold text-blue-800 dark:text-blue-200">
{title}
</span>
<Button
variant="ghost"
size="sm"
className="w-9 p-0"
tabIndex={-1}
type="button"
onClick={(e) => {
e.stopPropagation();
setOpen((v) => !v);
}}
>
{open ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
<span className="sr-only">Toggle {title}</span>
</Button>
</div>
{open && <div className="mt-2">{children}</div>}
</div>
);
}