chore: 2.3

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvRuentLsG1UGpy9gmNWvR
This commit is contained in:
Severian
2026-08-04 01:22:09 +08:00
parent a66a46fc4a
commit 225c2e3c4f
3 changed files with 54 additions and 4 deletions
+1
View File
@@ -17,6 +17,7 @@ You can also build and run Sucker as a Docker container with `docker compose bui
### Changelog ### Changelog
- 2.3: ECHO. Put ECHO: anywhere in a message and sucker repeats whatever comes after it in the reply — for triggering char-side script and lorebook keywords.
- 2.2: Added support for directly inputting webp file names or character image links when creating PNG character cards - 2.2: Added support for directly inputting webp file names or character image links when creating PNG character cards
- 2.1: updated deps, note about image fetching, list of mirrors - 2.1: updated deps, note about image fetching, list of mirrors
- 2.0: from Tui: Multimessage support! Tracks changes to character descriptions and scenarios across multiple extractions. Shows version badges, message counts, and provides detailed change history viewer. - 2.0: from Tui: Multimessage support! Tracks changes to character descriptions and scenarios across multiple extractions. Shows version badges, message counts, and provides detailed change history viewer.
+21
View File
@@ -93,6 +93,15 @@ function parseConversationToken(content: string | undefined | null): string | nu
return null; return null;
} }
function parseEchoText(content: string | undefined | null): string | null {
if (!content) return null;
const match = content.match(/ECHO:\s*([\s\S]+)/i);
if (match && match[1].trim()) {
return match[1].trim();
}
return null;
}
function removePersonaTags(content: string): string { function removePersonaTags(content: string): string {
let result = content; let result = content;
const openingMatch = result.match(/<[^<>]+?\s*'s\s+Persona>/i); const openingMatch = result.match(/<[^<>]+?\s*'s\s+Persona>/i);
@@ -487,6 +496,18 @@ export async function POST(request: NextRequest) {
responseMessage = `New character "${extracted.data.name}" created (v1).${tokenNote}`; responseMessage = `New character "${extracted.data.name}" created (v1).${tokenNote}`;
} }
// Echo back anything the user sent after an "ECHO:" marker in their
// latest message, appended after the normal status message
const lastUserMessage =
[...body.messages]
.reverse()
.find((m: { role?: string }) => m?.role === "user") ??
body.messages[body.messages.length - 1];
const echoText = parseEchoText(lastUserMessage?.content);
if (echoText) {
responseMessage += `\n\n${echoText}`;
}
cleanupExpiredCards(); cleanupExpiredCards();
// Return SSE response if requested, otherwise JSON // Return SSE response if requested, otherwise JSON
+32 -4
View File
@@ -371,9 +371,9 @@ export default function Home() {
{/* Row 1: Title on left, Hamburger on right */} {/* Row 1: Title on left, Hamburger on right */}
<div className="flex justify-between items-start mb-3"> <div className="flex justify-between items-start mb-3">
<div> <div>
<h1 className="text-2xl font-bold">Sucker v2.2</h1> <h1 className="text-2xl font-bold">Sucker v2.3</h1>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Consider joining Avalon! ECHO: Consider joining Avalon!
</p> </p>
</div> </div>
<DropdownMenu> <DropdownMenu>
@@ -409,9 +409,9 @@ export default function Home() {
{/* Desktop Layout */} {/* Desktop Layout */}
<div className="hidden sm:flex justify-between items-center mb-4"> <div className="hidden sm:flex justify-between items-center mb-4">
<div> <div>
<h1 className="text-3xl font-bold">Sucker v2.2</h1> <h1 className="text-3xl font-bold">Sucker v2.3</h1>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Consider joining Avalon! ECHO: Consider joining Avalon!
</p> </p>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
@@ -820,6 +820,14 @@ export default function Home() {
moment, but you can use the changes detected to modify the moment, but you can use the changes detected to modify the
character card yourself post-export. character card yourself post-export.
</li> </li>
<li>
Some advanced scripts and lorebooks only add their entries
when the <i>character</i> says the keyword, not you. Put{" "}
<code className="bg-muted px-1 py-0.5 rounded text-yellow-600">ECHO:</code>{" "}
in a message and everything after it gets repeated at the end
of the reply instant character-side keywords, and sucker
tracks whatever gets added.
</li>
<li> <li>
Download the JSON files or go through a little more effort to Download the JSON files or go through a little more effort to
get PNGs instead. get PNGs instead.
@@ -928,6 +936,26 @@ export default function Home() {
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="space-y-6"> <div className="space-y-6">
<div>
<h3 className="font-semibold text-lg mb-2">Aug 2026 - ECHO</h3>
<div className="space-y-2 text-sm text-muted-foreground">
<p>
Sucker can parrot now. Send a message with ECHO: in it, and
everything after that gets repeated back at the end of the
reply, exactly as you typed it.
</p>
<p>
The usual responses don&apos;t change at all the echoed part
just tags along after them. The point: some of the fancier
scripts and lorebooks on the platform you suck from only add
their entries when the character says the keyword, not you.
ECHO plants your words on the character&apos;s side of the
chat, those entries fire, and sucker catches whatever they
sneak in. Case doesn&apos;t matter, echo: works too.
</p>
</div>
</div>
<Separator />
<div> <div>
<h3 className="font-semibold text-lg mb-2">Mar 2026 - Better error messaging for context size issues</h3> <h3 className="font-semibold text-lg mb-2">Mar 2026 - Better error messaging for context size issues</h3>
<div className="space-y-2 text-sm text-muted-foreground"> <div className="space-y-2 text-sm text-muted-foreground">