opencrush 0.3.10 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +145 -96
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42073,10 +42073,19 @@ async function createFromPreset(apiKey, provider) {
|
|
|
42073
42073
|
personality,
|
|
42074
42074
|
backstory
|
|
42075
42075
|
};
|
|
42076
|
-
const spinner = ora("
|
|
42077
|
-
|
|
42076
|
+
const spinner = ora("Building character files...").start();
|
|
42077
|
+
let blueprint = buildBlueprintFromPreset(config);
|
|
42078
|
+
if (apiKey && provider) {
|
|
42079
|
+
try {
|
|
42080
|
+
blueprint = await enrichBlueprintWithLLM(blueprint, config, apiKey, provider);
|
|
42081
|
+
spinner.succeed(source_default.green(`${displayName} created!`));
|
|
42082
|
+
} catch {
|
|
42083
|
+
spinner.succeed(source_default.green(`${displayName} created! (basic \u2014 add API key for richer files)`));
|
|
42084
|
+
}
|
|
42085
|
+
} else {
|
|
42086
|
+
spinner.succeed(source_default.green(`${displayName} created!`));
|
|
42087
|
+
}
|
|
42078
42088
|
writeCharacterFiles(folderName, blueprint);
|
|
42079
|
-
spinner.succeed(source_default.green(`${displayName} created!`));
|
|
42080
42089
|
const { hasPortrait, falKey } = await craftPortrait(config, folderName, apiKey, provider);
|
|
42081
42090
|
printCreationSuccess(folderName, displayName);
|
|
42082
42091
|
return { folderName, displayName, gender: preset.gender, hasPortrait, falKey };
|
|
@@ -42301,20 +42310,24 @@ function buildPortraitPrompt(config) {
|
|
|
42301
42310
|
`wearing ${appearance.fashionStyle} style`,
|
|
42302
42311
|
archetype.portraitBase,
|
|
42303
42312
|
vibeHints,
|
|
42304
|
-
"cinematic photography, ultra-detailed, 8k resolution,
|
|
42313
|
+
"cinematic photography, ultra-detailed, 8k resolution, beautiful, editorial fashion, sharp focus, atmospheric background matching character aesthetic, environmental storytelling, moody scene lighting"
|
|
42305
42314
|
];
|
|
42306
42315
|
return parts.filter(Boolean).join(", ");
|
|
42307
42316
|
}
|
|
42308
42317
|
async function optimizePortraitPrompt(basePrompt, config, apiKey, provider) {
|
|
42309
|
-
const system = `You are an expert at writing image generation prompts for
|
|
42318
|
+
const system = `You are an expert at writing image generation prompts for character portraits.
|
|
42310
42319
|
Given a character description and base prompt, write an optimized FLUX/Stable Diffusion prompt.
|
|
42311
|
-
Rules:
|
|
42320
|
+
Rules:
|
|
42321
|
+
- Under 200 words, focus on visual details, include quality tags
|
|
42322
|
+
- ALWAYS include a scene-appropriate background that matches the character's vibe and lifestyle (e.g. neon cityscape for cyberpunk, cozy caf\xE9 for warm characters, art studio for artists, rain-soaked street for moody characters)
|
|
42323
|
+
- NEVER use plain white, studio, or blank backgrounds
|
|
42324
|
+
- The background should tell a story about who this character is
|
|
42312
42325
|
Return ONLY the prompt text \u2014 no explanation, no quotes.`;
|
|
42313
42326
|
const userMsg = `Character: ${config.name}, ${config.gender}
|
|
42314
42327
|
Archetype: ${config.archetype.label}
|
|
42315
42328
|
Base prompt: ${basePrompt}
|
|
42316
42329
|
|
|
42317
|
-
Optimize this into a vivid
|
|
42330
|
+
Optimize this into a vivid portrait prompt with an atmospheric background that fits the character's world. Preserve all physical details.`;
|
|
42318
42331
|
try {
|
|
42319
42332
|
const result = await callLLMDirect(provider, apiKey, system, [{ role: "user", content: userMsg }], 300);
|
|
42320
42333
|
return result.trim() || basePrompt;
|
|
@@ -42358,6 +42371,38 @@ function openImage(filePath) {
|
|
|
42358
42371
|
(0, import_child_process2.exec)(cmd, () => {
|
|
42359
42372
|
});
|
|
42360
42373
|
}
|
|
42374
|
+
async function enrichBlueprintWithLLM(blueprint, config, apiKey, provider) {
|
|
42375
|
+
const system = `You are a character writer creating rich companion profiles. Write in second person removed \u2014 describe the character as they are, not to them. Be specific, vivid, contradictory. Real people have texture.
|
|
42376
|
+
|
|
42377
|
+
RULES:
|
|
42378
|
+
- Expand appearance into 2-3 paragraphs of vivid prose (not bullet points)
|
|
42379
|
+
- Add a ## Background section (2-3 paragraphs about their history, how they got here)
|
|
42380
|
+
- Flesh out the SOUL with specific examples, not generic traits
|
|
42381
|
+
- Keep the markdown structure and frontmatter exactly as given
|
|
42382
|
+
- Write in English only
|
|
42383
|
+
- Do NOT add any sections that aren't in the original
|
|
42384
|
+
- Return the FULL expanded file content, not just the additions`;
|
|
42385
|
+
const identityPrompt = `Expand this IDENTITY.md into a rich, detailed character file. The appearance section should be vivid prose paragraphs (like a novel character introduction), not a list. Add a ## Background section at the end with 2-3 paragraphs of backstory.
|
|
42386
|
+
|
|
42387
|
+
Current file:
|
|
42388
|
+
${blueprint.identity}
|
|
42389
|
+
|
|
42390
|
+
Character context: ${config.archetype.label} archetype, personality traits: ${config.personality.join(", ")}${config.backstory ? ", backstory moment: " + config.backstory : ""}`;
|
|
42391
|
+
const soulPrompt = `Expand this SOUL.md into a richer version. Add specific examples to each section. The "Voice & Vibe" section should be 2+ paragraphs. "Loves" and "Dislikes" should have 5-7 items each with colorful detail. Add a "## Things She Does" section with 6-8 specific behavioral habits. Keep the same structure.
|
|
42392
|
+
|
|
42393
|
+
Current file:
|
|
42394
|
+
${blueprint.soul}`;
|
|
42395
|
+
const [enrichedIdentity, enrichedSoul] = await Promise.all([
|
|
42396
|
+
callLLMDirect(provider, apiKey, system, [{ role: "user", content: identityPrompt }], 2e3).catch(() => blueprint.identity),
|
|
42397
|
+
callLLMDirect(provider, apiKey, system, [{ role: "user", content: soulPrompt }], 2e3).catch(() => blueprint.soul)
|
|
42398
|
+
]);
|
|
42399
|
+
return {
|
|
42400
|
+
identity: enrichedIdentity || blueprint.identity,
|
|
42401
|
+
soul: enrichedSoul || blueprint.soul,
|
|
42402
|
+
user: blueprint.user,
|
|
42403
|
+
memory: blueprint.memory
|
|
42404
|
+
};
|
|
42405
|
+
}
|
|
42361
42406
|
function buildBlueprintFromPreset(config) {
|
|
42362
42407
|
const { archetype, name, appearance, personality, backstory } = config;
|
|
42363
42408
|
const renameIn = (text) => text.replace(new RegExp(`# ${escapeRegex(archetype.id.charAt(0).toUpperCase() + archetype.id.slice(1))}`, "g"), `# ${name}`).replace(new RegExp(`\\b${escapeRegex(archetype.id)}\\b`, "gi"), name).replace(new RegExp(`\\b(Yuna|Valentina|Hana|Nyx|Hu Lan|Riot|Kai|Eli)\\b`, "g"), name);
|
|
@@ -245110,7 +245155,68 @@ async function runSetupWizard() {
|
|
|
245110
245155
|
const TOTAL = 4;
|
|
245111
245156
|
const step = (n2, label) => console.log(source_default.cyan(`
|
|
245112
245157
|
[${n2}/${TOTAL}] ${label}`));
|
|
245113
|
-
step(1, "
|
|
245158
|
+
step(1, "Choose your AI brain");
|
|
245159
|
+
console.log(source_default.gray(" Pick whichever you already have a key for.\n"));
|
|
245160
|
+
const envValues = {};
|
|
245161
|
+
const providerChoices = [
|
|
245162
|
+
PROVIDER_INFO.find((p2) => p2.id === "anthropic"),
|
|
245163
|
+
PROVIDER_INFO.find((p2) => p2.id === "openai"),
|
|
245164
|
+
PROVIDER_INFO.find((p2) => p2.id === "deepseek"),
|
|
245165
|
+
PROVIDER_INFO.find((p2) => p2.id === "qwen"),
|
|
245166
|
+
PROVIDER_INFO.find((p2) => p2.id === "kimi"),
|
|
245167
|
+
PROVIDER_INFO.find((p2) => p2.id === "zhipu"),
|
|
245168
|
+
PROVIDER_INFO.find((p2) => p2.id === "minimax"),
|
|
245169
|
+
PROVIDER_INFO.find((p2) => p2.isLocal)
|
|
245170
|
+
].filter(Boolean);
|
|
245171
|
+
const { llmProvider } = await esm_default12.prompt([{
|
|
245172
|
+
type: "list",
|
|
245173
|
+
name: "llmProvider",
|
|
245174
|
+
message: "Which AI provider?",
|
|
245175
|
+
choices: providerChoices.map((p2) => ({
|
|
245176
|
+
name: `${p2.emoji} ${p2.name}
|
|
245177
|
+
${source_default.gray(p2.tagline)}`,
|
|
245178
|
+
value: p2.id,
|
|
245179
|
+
short: p2.name
|
|
245180
|
+
}))
|
|
245181
|
+
}]);
|
|
245182
|
+
envValues.LLM_PROVIDER = llmProvider;
|
|
245183
|
+
const providerInfo = getProviderInfo(llmProvider);
|
|
245184
|
+
let collectedApiKey;
|
|
245185
|
+
let collectedProvider;
|
|
245186
|
+
if (llmProvider !== "ollama") {
|
|
245187
|
+
console.log(source_default.yellow(`
|
|
245188
|
+
Get your API key: ${providerInfo.keyUrl}`));
|
|
245189
|
+
await openBrowser(providerInfo.keyUrl);
|
|
245190
|
+
if (llmProvider === "anthropic") {
|
|
245191
|
+
console.log(source_default.gray(' Sign up \u2192 API Keys \u2192 Create Key \u2192 copy the key (starts with "sk-ant-")\n'));
|
|
245192
|
+
} else if (llmProvider === "openai") {
|
|
245193
|
+
console.log(source_default.gray(" Sign up \u2192 API Keys \u2192 Create new secret key\n"));
|
|
245194
|
+
} else if (llmProvider === "deepseek") {
|
|
245195
|
+
console.log(source_default.gray(" Sign up \u2192 API Keys \u2192 Create key (new users get free credits)\n"));
|
|
245196
|
+
}
|
|
245197
|
+
const { apiKey } = await esm_default12.prompt([{
|
|
245198
|
+
type: "password",
|
|
245199
|
+
name: "apiKey",
|
|
245200
|
+
message: `Paste your ${providerInfo.name} API key:`,
|
|
245201
|
+
mask: "*",
|
|
245202
|
+
validate: (v2) => {
|
|
245203
|
+
if (!v2.trim()) return "API key required";
|
|
245204
|
+
if (providerInfo.keyPrefix && !v2.startsWith(providerInfo.keyPrefix)) {
|
|
245205
|
+
return `Should start with "${providerInfo.keyPrefix}"`;
|
|
245206
|
+
}
|
|
245207
|
+
return true;
|
|
245208
|
+
}
|
|
245209
|
+
}]);
|
|
245210
|
+
envValues[providerInfo.envKey] = apiKey;
|
|
245211
|
+
collectedApiKey = apiKey;
|
|
245212
|
+
collectedProvider = llmProvider;
|
|
245213
|
+
} else {
|
|
245214
|
+
console.log(source_default.yellow("\n Make sure Ollama is running: https://ollama.ai"));
|
|
245215
|
+
console.log(source_default.gray(" Run: ollama pull qwen2.5:7b\n"));
|
|
245216
|
+
envValues.OLLAMA_BASE_URL = "http://localhost:11434";
|
|
245217
|
+
envValues.OLLAMA_MODEL = "qwen2.5:7b";
|
|
245218
|
+
}
|
|
245219
|
+
step(2, "Pick your companion");
|
|
245114
245220
|
const characters = getExistingCharacters();
|
|
245115
245221
|
let characterName;
|
|
245116
245222
|
let characterCreatedNew = false;
|
|
@@ -245137,7 +245243,7 @@ async function runSetupWizard() {
|
|
|
245137
245243
|
choices
|
|
245138
245244
|
}]);
|
|
245139
245245
|
if (pick === "__new__") {
|
|
245140
|
-
const created = await createCharacterFlow();
|
|
245246
|
+
const created = await createCharacterFlow(collectedApiKey, collectedProvider);
|
|
245141
245247
|
characterName = created.folderName;
|
|
245142
245248
|
characterCreatedNew = true;
|
|
245143
245249
|
gender = created.gender;
|
|
@@ -245148,14 +245254,17 @@ async function runSetupWizard() {
|
|
|
245148
245254
|
}
|
|
245149
245255
|
} else {
|
|
245150
245256
|
console.log(source_default.gray(" No companions found yet \u2014 let's create one!\n"));
|
|
245151
|
-
const created = await createCharacterFlow();
|
|
245257
|
+
const created = await createCharacterFlow(collectedApiKey, collectedProvider);
|
|
245152
245258
|
characterName = created.folderName;
|
|
245153
245259
|
characterCreatedNew = true;
|
|
245154
245260
|
gender = created.gender;
|
|
245155
245261
|
}
|
|
245262
|
+
envValues.CHARACTER_NAME = characterName;
|
|
245156
245263
|
const pro = pronouns(gender);
|
|
245157
|
-
|
|
245158
|
-
|
|
245264
|
+
if (characterCreatedNew && collectedApiKey) {
|
|
245265
|
+
await runTestChat(characterName, collectedApiKey, collectedProvider ?? "anthropic");
|
|
245266
|
+
}
|
|
245267
|
+
step(3, `Where do you want to chat with ${characterName}?`);
|
|
245159
245268
|
const { platforms } = await esm_default12.prompt([{
|
|
245160
245269
|
type: "checkbox",
|
|
245161
245270
|
name: "platforms",
|
|
@@ -245221,61 +245330,6 @@ async function runSetupWizard() {
|
|
|
245221
245330
|
WhatsApp needs no setup! A QR code will appear when ${characterName} starts.`));
|
|
245222
245331
|
console.log(source_default.gray(" Scan it with WhatsApp \u2192 Linked Devices on your phone."));
|
|
245223
245332
|
}
|
|
245224
|
-
step(3, `Give ${characterName} a brain`);
|
|
245225
|
-
console.log(source_default.gray(" Pick whichever you already have a key for.\n"));
|
|
245226
|
-
const providerChoices = [
|
|
245227
|
-
PROVIDER_INFO.find((p2) => p2.id === "anthropic"),
|
|
245228
|
-
PROVIDER_INFO.find((p2) => p2.id === "openai"),
|
|
245229
|
-
PROVIDER_INFO.find((p2) => p2.id === "deepseek"),
|
|
245230
|
-
PROVIDER_INFO.find((p2) => p2.isLocal)
|
|
245231
|
-
].filter(Boolean);
|
|
245232
|
-
const { llmProvider } = await esm_default12.prompt([{
|
|
245233
|
-
type: "list",
|
|
245234
|
-
name: "llmProvider",
|
|
245235
|
-
message: "Which AI provider?",
|
|
245236
|
-
choices: providerChoices.map((p2) => ({
|
|
245237
|
-
name: `${p2.emoji} ${p2.name}
|
|
245238
|
-
${source_default.gray(p2.tagline)}`,
|
|
245239
|
-
value: p2.id,
|
|
245240
|
-
short: p2.name
|
|
245241
|
-
}))
|
|
245242
|
-
}]);
|
|
245243
|
-
envValues.LLM_PROVIDER = llmProvider;
|
|
245244
|
-
const providerInfo = getProviderInfo(llmProvider);
|
|
245245
|
-
if (llmProvider !== "ollama") {
|
|
245246
|
-
console.log(source_default.yellow(`
|
|
245247
|
-
Get your API key: ${providerInfo.keyUrl}`));
|
|
245248
|
-
await openBrowser(providerInfo.keyUrl);
|
|
245249
|
-
if (llmProvider === "anthropic") {
|
|
245250
|
-
console.log(source_default.gray(' Sign up \u2192 API Keys \u2192 Create Key \u2192 copy the key (starts with "sk-ant-")\n'));
|
|
245251
|
-
} else if (llmProvider === "openai") {
|
|
245252
|
-
console.log(source_default.gray(" Sign up \u2192 API Keys \u2192 Create new secret key\n"));
|
|
245253
|
-
} else if (llmProvider === "deepseek") {
|
|
245254
|
-
console.log(source_default.gray(" Sign up \u2192 API Keys \u2192 Create key (new users get free credits)\n"));
|
|
245255
|
-
}
|
|
245256
|
-
const { apiKey } = await esm_default12.prompt([{
|
|
245257
|
-
type: "password",
|
|
245258
|
-
name: "apiKey",
|
|
245259
|
-
message: `Paste your ${providerInfo.name} API key:`,
|
|
245260
|
-
mask: "*",
|
|
245261
|
-
validate: (v2) => {
|
|
245262
|
-
if (!v2.trim()) return "API key required";
|
|
245263
|
-
if (providerInfo.keyPrefix && !v2.startsWith(providerInfo.keyPrefix)) {
|
|
245264
|
-
return `Should start with "${providerInfo.keyPrefix}"`;
|
|
245265
|
-
}
|
|
245266
|
-
return true;
|
|
245267
|
-
}
|
|
245268
|
-
}]);
|
|
245269
|
-
envValues[providerInfo.envKey] = apiKey;
|
|
245270
|
-
if (characterCreatedNew) {
|
|
245271
|
-
await runTestChat(characterName, apiKey, llmProvider);
|
|
245272
|
-
}
|
|
245273
|
-
} else {
|
|
245274
|
-
console.log(source_default.yellow("\n Make sure Ollama is running: https://ollama.ai"));
|
|
245275
|
-
console.log(source_default.gray(" Run: ollama pull qwen2.5:7b\n"));
|
|
245276
|
-
envValues.OLLAMA_BASE_URL = "http://localhost:11434";
|
|
245277
|
-
envValues.OLLAMA_MODEL = "qwen2.5:7b";
|
|
245278
|
-
}
|
|
245279
245333
|
step(4, "Extras");
|
|
245280
245334
|
console.log(source_default.gray(" All optional \u2014 skip everything by pressing Enter.\n"));
|
|
245281
245335
|
const quickChoices = [
|
|
@@ -245520,7 +245574,7 @@ function parseIdentity(identityPath) {
|
|
|
245520
245574
|
const age = (ageMatch == null ? void 0 : ageMatch[1]) ?? "??";
|
|
245521
245575
|
const fromMatch = content.match(/\*\*From:\*\*\s*(.+)/i);
|
|
245522
245576
|
const locationRaw = ((_b2 = fromMatch == null ? void 0 : fromMatch[1]) == null ? void 0 : _b2.trim()) ?? "Unknown";
|
|
245523
|
-
const location = locationRaw.replace(/\s*\(.*\)
|
|
245577
|
+
const location = locationRaw.replace(/\s*\(.*\)/, "").split(" \u2014 ")[0].trim();
|
|
245524
245578
|
const hobbiesMatch = content.match(/\*\*Hobbies:\*\*\s*(.+)/i);
|
|
245525
245579
|
const hobbiesRaw = (hobbiesMatch == null ? void 0 : hobbiesMatch[1]) ?? "";
|
|
245526
245580
|
const tags = hobbiesRaw.split(",").map((t2) => t2.trim()).filter(Boolean).slice(0, 5);
|
|
@@ -245549,18 +245603,20 @@ function pickGradient(name) {
|
|
|
245549
245603
|
return gradients[hash % gradients.length];
|
|
245550
245604
|
}
|
|
245551
245605
|
function createSvgOverlay(data, gradient) {
|
|
245552
|
-
const textX = PORTRAIT_X + PORTRAIT_SIZE +
|
|
245553
|
-
const
|
|
245554
|
-
const
|
|
245606
|
+
const textX = PORTRAIT_X + PORTRAIT_SIZE + 50;
|
|
245607
|
+
const maxTagX = WIDTH - 30;
|
|
245608
|
+
const nameY = 210;
|
|
245609
|
+
const metaY = nameY + 45;
|
|
245610
|
+
const tagStartY = metaY + 50;
|
|
245555
245611
|
let inlineTags = "";
|
|
245556
245612
|
let offsetX = textX;
|
|
245557
|
-
let rowY =
|
|
245558
|
-
const rowHeight =
|
|
245613
|
+
let rowY = tagStartY;
|
|
245614
|
+
const rowHeight = 30;
|
|
245559
245615
|
const maxRows = 2;
|
|
245560
245616
|
let currentRow = 1;
|
|
245561
245617
|
for (const tag of data.tags) {
|
|
245562
|
-
const label = tag.length >
|
|
245563
|
-
const w2 = label.length *
|
|
245618
|
+
const label = tag.length > 18 ? tag.slice(0, 17) + "\u2026" : tag;
|
|
245619
|
+
const w2 = label.length * 7.5 + 16;
|
|
245564
245620
|
if (offsetX + w2 > maxTagX) {
|
|
245565
245621
|
if (currentRow >= maxRows) break;
|
|
245566
245622
|
currentRow++;
|
|
@@ -245568,12 +245624,12 @@ function createSvgOverlay(data, gradient) {
|
|
|
245568
245624
|
rowY += rowHeight;
|
|
245569
245625
|
}
|
|
245570
245626
|
inlineTags += `
|
|
245571
|
-
<rect x="${offsetX
|
|
245572
|
-
<text x="${offsetX +
|
|
245627
|
+
<rect x="${offsetX}" y="${rowY - 14}" width="${w2}" height="22" rx="11" fill="rgba(255,255,255,0.10)" />
|
|
245628
|
+
<text x="${offsetX + 8}" y="${rowY + 1}" font-family="system-ui, -apple-system, sans-serif" font-size="12" fill="#c0c0c0">${escapeXml(label)}</text>
|
|
245573
245629
|
`;
|
|
245574
|
-
offsetX += w2 +
|
|
245630
|
+
offsetX += w2 + 6;
|
|
245575
245631
|
}
|
|
245576
|
-
const descriptionY =
|
|
245632
|
+
const descriptionY = tagStartY + currentRow * rowHeight + 14;
|
|
245577
245633
|
return `<svg width="${WIDTH}" height="${HEIGHT}" xmlns="http://www.w3.org/2000/svg">
|
|
245578
245634
|
<defs>
|
|
245579
245635
|
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
@@ -245585,28 +245641,21 @@ function createSvgOverlay(data, gradient) {
|
|
|
245585
245641
|
</clipPath>
|
|
245586
245642
|
</defs>
|
|
245587
245643
|
|
|
245588
|
-
<!-- Background -->
|
|
245589
245644
|
<rect width="${WIDTH}" height="${HEIGHT}" fill="url(#bg)" />
|
|
245590
245645
|
|
|
245591
|
-
|
|
245592
|
-
<circle cx="${PORTRAIT_X + PORTRAIT_SIZE / 2}" cy="${PORTRAIT_Y + PORTRAIT_SIZE / 2}" r="${PORTRAIT_SIZE / 2 + 3}" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="2" />
|
|
245646
|
+
<circle cx="${PORTRAIT_X + PORTRAIT_SIZE / 2}" cy="${PORTRAIT_Y + PORTRAIT_SIZE / 2}" r="${PORTRAIT_SIZE / 2 + 2}" fill="none" stroke="rgba(255,255,255,0.12)" stroke-width="2" />
|
|
245593
245647
|
|
|
245594
|
-
|
|
245595
|
-
<text x="${textX}" y="170" font-family="system-ui, -apple-system, sans-serif" font-size="48" font-weight="bold" fill="white">${escapeXml(data.name)}</text>
|
|
245648
|
+
<text x="${textX}" y="${nameY}" font-family="system-ui, -apple-system, sans-serif" font-size="44" font-weight="bold" fill="white">${escapeXml(data.name)}</text>
|
|
245596
245649
|
|
|
245597
|
-
|
|
245598
|
-
<text x="${textX}" y="215" font-family="system-ui, -apple-system, sans-serif" font-size="18" fill="#a0a0a0">${escapeXml(data.age)} \xB7 ${escapeXml(truncate(data.location, 60))}</text>
|
|
245650
|
+
<text x="${textX}" y="${metaY}" font-family="system-ui, -apple-system, sans-serif" font-size="16" fill="#a0a0a0">${escapeXml(data.age)} \xB7 ${escapeXml(truncate(data.location, 35))}</text>
|
|
245599
245651
|
|
|
245600
|
-
<!-- Personality Tags (inline) -->
|
|
245601
245652
|
${inlineTags}
|
|
245602
245653
|
|
|
245603
|
-
|
|
245604
|
-
<text x="${textX}" y="${descriptionY}" font-family="system-ui, -apple-system, sans-serif" font-size="15" fill="#b0b0b0" font-style="italic">${escapeXml(truncate(data.description, 70))}</text>
|
|
245654
|
+
<text x="${textX}" y="${descriptionY}" font-family="system-ui, -apple-system, sans-serif" font-size="14" fill="#b0b0b0" font-style="italic">${escapeXml(truncate(data.description, 60))}</text>
|
|
245605
245655
|
|
|
245606
|
-
|
|
245607
|
-
<
|
|
245608
|
-
<text x="
|
|
245609
|
-
<text x="${WIDTH - 40}" y="${HEIGHT - 20}" font-family="system-ui, -apple-system, sans-serif" font-size="13" fill="#888" text-anchor="end">github.com/Hollandchirs/Opencrush</text>
|
|
245656
|
+
<rect x="0" y="${HEIGHT - 44}" width="${WIDTH}" height="44" fill="rgba(0,0,0,0.3)" />
|
|
245657
|
+
<text x="36" y="${HEIGHT - 17}" font-family="system-ui, -apple-system, sans-serif" font-size="14" font-weight="bold" fill="#ff69b4">Opencrush</text>
|
|
245658
|
+
<text x="${WIDTH - 36}" y="${HEIGHT - 17}" font-family="system-ui, -apple-system, sans-serif" font-size="12" fill="#888" text-anchor="end">github.com/Hollandchirs/Opencrush</text>
|
|
245610
245659
|
</svg>`;
|
|
245611
245660
|
}
|
|
245612
245661
|
function escapeXml(str2) {
|
|
@@ -245639,7 +245688,7 @@ async function generateCard(characterName) {
|
|
|
245639
245688
|
}
|
|
245640
245689
|
const svgOverlay = createSvgOverlay(data, gradient);
|
|
245641
245690
|
const svgBuffer = Buffer.from(svgOverlay);
|
|
245642
|
-
let card = sharp(svgBuffer, { density:
|
|
245691
|
+
let card = sharp(svgBuffer, { density: 300 }).resize(WIDTH, HEIGHT);
|
|
245643
245692
|
if (refImagePath) {
|
|
245644
245693
|
const circleMask = Buffer.from(
|
|
245645
245694
|
`<svg width="${PORTRAIT_SIZE}" height="${PORTRAIT_SIZE}">
|
|
@@ -245672,9 +245721,9 @@ var init_card = __esm({
|
|
|
245672
245721
|
init_source();
|
|
245673
245722
|
WIDTH = 1200;
|
|
245674
245723
|
HEIGHT = 630;
|
|
245675
|
-
PORTRAIT_SIZE =
|
|
245724
|
+
PORTRAIT_SIZE = 260;
|
|
245676
245725
|
PORTRAIT_X = 80;
|
|
245677
|
-
PORTRAIT_Y =
|
|
245726
|
+
PORTRAIT_Y = (HEIGHT - PORTRAIT_SIZE) / 2 - 10;
|
|
245678
245727
|
}
|
|
245679
245728
|
});
|
|
245680
245729
|
|