M0RE
Browse files
src/lib/components/MonsterGenerator/MonsterGenerator.svelte
CHANGED
|
@@ -1,9 +1,10 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import type { MonsterGeneratorProps, MonsterWorkflowState, CaptionType, CaptionLength } from '$lib/types';
|
| 3 |
import UploadStep from './UploadStep.svelte';
|
| 4 |
import WorkflowProgress from './WorkflowProgress.svelte';
|
| 5 |
import MonsterResult from './MonsterResult.svelte';
|
| 6 |
import { makeWhiteTransparent } from '$lib/utils/imageProcessing';
|
|
|
|
| 7 |
|
| 8 |
interface Props extends MonsterGeneratorProps {}
|
| 9 |
|
|
@@ -42,7 +43,7 @@ Include:
|
|
| 42 |
- Personality traits
|
| 43 |
- Habitat or origin
|
| 44 |
|
| 45 |
-
Assistant:
|
| 46 |
|
| 47 |
const IMAGE_GENERATION_PROMPT = (concept: string) => `User: Convert this monster concept into a clear and succinct description of its appearance:
|
| 48 |
"${concept}"
|
|
@@ -64,20 +65,26 @@ Here is the output schema:
|
|
| 64 |
\`\`\`
|
| 65 |
{
|
| 66 |
"properties": {
|
| 67 |
-
"name": {"type": "string", "description": "The monster's name"},
|
| 68 |
-
"description": {"type": "string", "description": "A brief description of the monster"},
|
| 69 |
"rarity": {"type": "integer", "minimum": 0, "maximum": 100, "description": "How rare/unique the monster is (0=very common, 100=legendary)"},
|
| 70 |
-
"HP": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Health
|
| 71 |
-
"defence": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Defensive
|
| 72 |
-
"attack": {"type": "integer", "minimum": 0, "maximum": 100, "description": "
|
| 73 |
-
"speed": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Movement speed (0=immobile, 100=lightning fast)"},
|
| 74 |
-
"specialPassiveTraitDescription": {"type": "string", "description": "
|
| 75 |
-
"
|
| 76 |
-
"
|
| 77 |
-
"
|
| 78 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
},
|
| 80 |
-
"required": ["name", "description", "rarity", "HP", "defence", "attack", "speed", "specialPassiveTraitDescription",
|
|
|
|
|
|
|
| 81 |
}
|
| 82 |
\`\`\`
|
| 83 |
|
|
@@ -119,6 +126,9 @@ Assistant: \`\`\`json`;
|
|
| 119 |
// Step 5: Generate monster image
|
| 120 |
await generateMonsterImage();
|
| 121 |
|
|
|
|
|
|
|
|
|
|
| 122 |
state.currentStep = 'complete';
|
| 123 |
} catch (err) {
|
| 124 |
console.error('Workflow error:', err);
|
|
@@ -379,6 +389,36 @@ Assistant: \`\`\`json`;
|
|
| 379 |
}
|
| 380 |
}
|
| 381 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
function reset() {
|
| 383 |
state = {
|
| 384 |
currentStep: 'upload',
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import type { MonsterGeneratorProps, MonsterWorkflowState, CaptionType, CaptionLength, MonsterStats } from '$lib/types';
|
| 3 |
import UploadStep from './UploadStep.svelte';
|
| 4 |
import WorkflowProgress from './WorkflowProgress.svelte';
|
| 5 |
import MonsterResult from './MonsterResult.svelte';
|
| 6 |
import { makeWhiteTransparent } from '$lib/utils/imageProcessing';
|
| 7 |
+
import { saveMonster } from '$lib/db/monsters';
|
| 8 |
|
| 9 |
interface Props extends MonsterGeneratorProps {}
|
| 10 |
|
|
|
|
| 43 |
- Personality traits
|
| 44 |
- Habitat or origin
|
| 45 |
|
| 46 |
+
Assistant: **Monster Name:**`;
|
| 47 |
|
| 48 |
const IMAGE_GENERATION_PROMPT = (concept: string) => `User: Convert this monster concept into a clear and succinct description of its appearance:
|
| 49 |
"${concept}"
|
|
|
|
| 65 |
\`\`\`
|
| 66 |
{
|
| 67 |
"properties": {
|
| 68 |
+
"name": {"type": "string", "description": "The monster's unique name"},
|
| 69 |
+
"description": {"type": "string", "description": "A brief physical description of the monster's appearance"},
|
| 70 |
"rarity": {"type": "integer", "minimum": 0, "maximum": 100, "description": "How rare/unique the monster is (0=very common, 100=legendary)"},
|
| 71 |
+
"HP": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Health/vitality stat (0=fragile, 100=incredibly tanky)"},
|
| 72 |
+
"defence": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Defensive/armor stat (0=paper thin, 100=impenetrable fortress)"},
|
| 73 |
+
"attack": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Physical attack power (0=harmless, 100=devastating force)"},
|
| 74 |
+
"speed": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Movement and reaction speed (0=immobile, 100=lightning fast)"},
|
| 75 |
+
"specialPassiveTraitDescription": {"type": "string", "description": "Describe a passive ability that gives this monster a unique advantage in battle"},
|
| 76 |
+
"attackActionName": {"type": "string", "description": "Name of the monster's primary damage-dealing attack (e.g., 'Flame Burst', 'Toxic Bite')"},
|
| 77 |
+
"attackActionDescription": {"type": "string", "description": "Describe how this attack damages the opponent and any special effects"},
|
| 78 |
+
"boostActionName": {"type": "string", "description": "Name of the monster's self-buff ability (e.g., 'Iron Defense', 'Speed Boost')"},
|
| 79 |
+
"boostActionDescription": {"type": "string", "description": "Describe which stats are boosted and how this improves the monster's battle performance"},
|
| 80 |
+
"disparageActionName": {"type": "string", "description": "Name of the monster's enemy debuff ability (e.g., 'Intimidate', 'Slow Poison')"},
|
| 81 |
+
"disparageActionDescription": {"type": "string", "description": "Describe which enemy stats are lowered and how this weakens the opponent"},
|
| 82 |
+
"specialActionName": {"type": "string", "description": "Name of the monster's ultimate move (one use per battle)"},
|
| 83 |
+
"specialActionDescription": {"type": "string", "description": "Describe this powerful finishing move and its dramatic effects in battle"}
|
| 84 |
},
|
| 85 |
+
"required": ["name", "description", "rarity", "HP", "defence", "attack", "speed", "specialPassiveTraitDescription",
|
| 86 |
+
"attackActionName", "attackActionDescription", "boostActionName", "boostActionDescription",
|
| 87 |
+
"disparageActionName", "disparageActionDescription", "specialActionName", "specialActionDescription"]
|
| 88 |
}
|
| 89 |
\`\`\`
|
| 90 |
|
|
|
|
| 126 |
// Step 5: Generate monster image
|
| 127 |
await generateMonsterImage();
|
| 128 |
|
| 129 |
+
// Step 6: Auto-save the monster
|
| 130 |
+
await autoSaveMonster();
|
| 131 |
+
|
| 132 |
state.currentStep = 'complete';
|
| 133 |
} catch (err) {
|
| 134 |
console.error('Workflow error:', err);
|
|
|
|
| 389 |
}
|
| 390 |
}
|
| 391 |
|
| 392 |
+
async function autoSaveMonster() {
|
| 393 |
+
if (!state.monsterImage || !state.imageCaption || !state.monsterConcept || !state.imagePrompt || !state.monsterStats) {
|
| 394 |
+
console.error('Cannot auto-save: missing required data');
|
| 395 |
+
return;
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
try {
|
| 399 |
+
const monsterData = {
|
| 400 |
+
name: state.monsterStats.name,
|
| 401 |
+
imageUrl: state.monsterImage.imageUrl,
|
| 402 |
+
imageData: state.monsterImage.imageData || state.monsterImage.imageUrl, // Fallback to URL if no transparent image
|
| 403 |
+
imageCaption: state.imageCaption,
|
| 404 |
+
concept: state.monsterConcept,
|
| 405 |
+
imagePrompt: state.imagePrompt,
|
| 406 |
+
stats: state.monsterStats
|
| 407 |
+
};
|
| 408 |
+
|
| 409 |
+
console.log('Auto-saving monster:', {
|
| 410 |
+
...monsterData,
|
| 411 |
+
imageData: monsterData.imageData ? `${monsterData.imageData.substring(0, 50)}... (length: ${monsterData.imageData.length})` : 'null'
|
| 412 |
+
});
|
| 413 |
+
|
| 414 |
+
const id = await saveMonster(monsterData);
|
| 415 |
+
console.log('Monster auto-saved with ID:', id);
|
| 416 |
+
} catch (err) {
|
| 417 |
+
console.error('Failed to auto-save monster:', err);
|
| 418 |
+
// Don't throw - we don't want to interrupt the workflow
|
| 419 |
+
}
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
function reset() {
|
| 423 |
state = {
|
| 424 |
currentStep: 'upload',
|
src/lib/components/MonsterGenerator/MonsterResult.svelte
CHANGED
|
@@ -37,7 +37,7 @@
|
|
| 37 |
}
|
| 38 |
|
| 39 |
async function saveToCollection() {
|
| 40 |
-
if (!workflowState.monsterImage || !workflowState.imageCaption || !workflowState.monsterConcept || !workflowState.imagePrompt) {
|
| 41 |
saveError = 'Missing monster data';
|
| 42 |
return;
|
| 43 |
}
|
|
@@ -65,16 +65,23 @@
|
|
| 65 |
monsterName = workflowState.monsterStats.name;
|
| 66 |
}
|
| 67 |
|
| 68 |
-
|
| 69 |
name: monsterName,
|
| 70 |
imageUrl: workflowState.monsterImage.imageUrl,
|
| 71 |
imageData: workflowState.monsterImage.imageData,
|
| 72 |
imageCaption: workflowState.imageCaption,
|
| 73 |
concept: workflowState.monsterConcept,
|
| 74 |
imagePrompt: workflowState.imagePrompt,
|
| 75 |
-
stats: workflowState.monsterStats
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
});
|
| 77 |
|
|
|
|
|
|
|
| 78 |
isSaved = true;
|
| 79 |
} catch (err) {
|
| 80 |
console.error('Failed to save monster:', err);
|
|
@@ -87,6 +94,7 @@
|
|
| 87 |
|
| 88 |
<div class="result-container">
|
| 89 |
<h3>{workflowState.monsterStats?.name || 'Your Monster Has Been Created!'}</h3>
|
|
|
|
| 90 |
|
| 91 |
{#if workflowState.monsterImage}
|
| 92 |
<div class="monster-image-container">
|
|
@@ -98,8 +106,11 @@
|
|
| 98 |
</div>
|
| 99 |
{/if}
|
| 100 |
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
-
|
| 103 |
<div class="result-section">
|
| 104 |
<h4>Battle Stats</h4>
|
| 105 |
<div class="stats-grid">
|
|
@@ -135,19 +146,19 @@
|
|
| 135 |
<p>{workflowState.monsterStats.specialPassiveTrait}</p>
|
| 136 |
</div>
|
| 137 |
<div class="ability-item">
|
| 138 |
-
<h5>Attack</h5>
|
| 139 |
<p>{workflowState.monsterStats.attackActionDescription}</p>
|
| 140 |
</div>
|
| 141 |
<div class="ability-item">
|
| 142 |
-
<h5>Boost</h5>
|
| 143 |
<p>{workflowState.monsterStats.boostActionDescription}</p>
|
| 144 |
</div>
|
| 145 |
<div class="ability-item">
|
| 146 |
-
<h5>
|
| 147 |
<p>{workflowState.monsterStats.disparageActionDescription}</p>
|
| 148 |
</div>
|
| 149 |
<div class="ability-item">
|
| 150 |
-
<h5>
|
| 151 |
<p>{workflowState.monsterStats.specialActionDescription}</p>
|
| 152 |
</div>
|
| 153 |
</div>
|
|
@@ -161,26 +172,6 @@
|
|
| 161 |
</svg>
|
| 162 |
Download Monster
|
| 163 |
</button>
|
| 164 |
-
<button
|
| 165 |
-
class="action-button save"
|
| 166 |
-
onclick={saveToCollection}
|
| 167 |
-
disabled={isSaving || isSaved}
|
| 168 |
-
>
|
| 169 |
-
{#if isSaving}
|
| 170 |
-
<div class="spinner-small"></div>
|
| 171 |
-
Saving...
|
| 172 |
-
{:else if isSaved}
|
| 173 |
-
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
|
| 174 |
-
<path d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"/>
|
| 175 |
-
</svg>
|
| 176 |
-
Saved!
|
| 177 |
-
{:else}
|
| 178 |
-
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
|
| 179 |
-
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/>
|
| 180 |
-
</svg>
|
| 181 |
-
Save to Collection
|
| 182 |
-
{/if}
|
| 183 |
-
</button>
|
| 184 |
<button class="action-button reset" onclick={onReset}>
|
| 185 |
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
|
| 186 |
<path d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.058 7.293a1 1 0 01-1.414 1.414l-2.35-2.35A1 1 0 011 5.648V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.943 13H13a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"/>
|
|
@@ -404,6 +395,23 @@
|
|
| 404 |
line-height: 1.4;
|
| 405 |
}
|
| 406 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
@media (max-width: 768px) {
|
| 408 |
.result-container {
|
| 409 |
padding: 1rem;
|
|
|
|
| 37 |
}
|
| 38 |
|
| 39 |
async function saveToCollection() {
|
| 40 |
+
if (!workflowState.monsterImage || !workflowState.imageCaption || !workflowState.monsterConcept || !workflowState.imagePrompt || !workflowState.monsterStats) {
|
| 41 |
saveError = 'Missing monster data';
|
| 42 |
return;
|
| 43 |
}
|
|
|
|
| 65 |
monsterName = workflowState.monsterStats.name;
|
| 66 |
}
|
| 67 |
|
| 68 |
+
const monsterData = {
|
| 69 |
name: monsterName,
|
| 70 |
imageUrl: workflowState.monsterImage.imageUrl,
|
| 71 |
imageData: workflowState.monsterImage.imageData,
|
| 72 |
imageCaption: workflowState.imageCaption,
|
| 73 |
concept: workflowState.monsterConcept,
|
| 74 |
imagePrompt: workflowState.imagePrompt,
|
| 75 |
+
stats: workflowState.monsterStats
|
| 76 |
+
};
|
| 77 |
+
|
| 78 |
+
console.log('Saving monster with data:', {
|
| 79 |
+
...monsterData,
|
| 80 |
+
imageData: monsterData.imageData ? `${monsterData.imageData.substring(0, 50)}... (length: ${monsterData.imageData.length})` : 'null'
|
| 81 |
});
|
| 82 |
|
| 83 |
+
await saveMonster(monsterData);
|
| 84 |
+
|
| 85 |
isSaved = true;
|
| 86 |
} catch (err) {
|
| 87 |
console.error('Failed to save monster:', err);
|
|
|
|
| 94 |
|
| 95 |
<div class="result-container">
|
| 96 |
<h3>{workflowState.monsterStats?.name || 'Your Monster Has Been Created!'}</h3>
|
| 97 |
+
<p class="saved-message">✓ Automatically saved to your collection</p>
|
| 98 |
|
| 99 |
{#if workflowState.monsterImage}
|
| 100 |
<div class="monster-image-container">
|
|
|
|
| 106 |
</div>
|
| 107 |
{/if}
|
| 108 |
|
| 109 |
+
{#if workflowState.monsterStats?.description}
|
| 110 |
+
<p class="monster-description">{workflowState.monsterStats.description}</p>
|
| 111 |
+
{/if}
|
| 112 |
|
| 113 |
+
{#if workflowState.monsterStats}
|
| 114 |
<div class="result-section">
|
| 115 |
<h4>Battle Stats</h4>
|
| 116 |
<div class="stats-grid">
|
|
|
|
| 146 |
<p>{workflowState.monsterStats.specialPassiveTrait}</p>
|
| 147 |
</div>
|
| 148 |
<div class="ability-item">
|
| 149 |
+
<h5>Attack: {workflowState.monsterStats.attackActionName}</h5>
|
| 150 |
<p>{workflowState.monsterStats.attackActionDescription}</p>
|
| 151 |
</div>
|
| 152 |
<div class="ability-item">
|
| 153 |
+
<h5>Boost: {workflowState.monsterStats.boostActionName}</h5>
|
| 154 |
<p>{workflowState.monsterStats.boostActionDescription}</p>
|
| 155 |
</div>
|
| 156 |
<div class="ability-item">
|
| 157 |
+
<h5>Debuff: {workflowState.monsterStats.disparageActionName}</h5>
|
| 158 |
<p>{workflowState.monsterStats.disparageActionDescription}</p>
|
| 159 |
</div>
|
| 160 |
<div class="ability-item">
|
| 161 |
+
<h5>Ultimate: {workflowState.monsterStats.specialActionName}</h5>
|
| 162 |
<p>{workflowState.monsterStats.specialActionDescription}</p>
|
| 163 |
</div>
|
| 164 |
</div>
|
|
|
|
| 172 |
</svg>
|
| 173 |
Download Monster
|
| 174 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
<button class="action-button reset" onclick={onReset}>
|
| 176 |
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
|
| 177 |
<path d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.058 7.293a1 1 0 01-1.414 1.414l-2.35-2.35A1 1 0 011 5.648V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.943 13H13a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"/>
|
|
|
|
| 395 |
line-height: 1.4;
|
| 396 |
}
|
| 397 |
|
| 398 |
+
.saved-message {
|
| 399 |
+
color: #28a745;
|
| 400 |
+
font-size: 0.9rem;
|
| 401 |
+
margin: -0.5rem 0 1.5rem;
|
| 402 |
+
text-align: center;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
.monster-description {
|
| 406 |
+
text-align: center;
|
| 407 |
+
color: #555;
|
| 408 |
+
font-size: 1rem;
|
| 409 |
+
line-height: 1.5;
|
| 410 |
+
margin: 1rem auto 2rem;
|
| 411 |
+
max-width: 600px;
|
| 412 |
+
font-style: italic;
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
@media (max-width: 768px) {
|
| 416 |
.result-container {
|
| 417 |
padding: 1rem;
|
src/lib/types/index.ts
CHANGED
|
@@ -112,8 +112,12 @@ export interface MonsterStats {
|
|
| 112 |
attack: number; // 0-100
|
| 113 |
speed: number; // 0-100
|
| 114 |
specialPassiveTrait: string;
|
|
|
|
| 115 |
attackActionDescription: string;
|
|
|
|
| 116 |
boostActionDescription: string;
|
|
|
|
| 117 |
disparageActionDescription: string;
|
|
|
|
| 118 |
specialActionDescription: string;
|
| 119 |
}
|
|
|
|
| 112 |
attack: number; // 0-100
|
| 113 |
speed: number; // 0-100
|
| 114 |
specialPassiveTrait: string;
|
| 115 |
+
attackActionName: string;
|
| 116 |
attackActionDescription: string;
|
| 117 |
+
boostActionName: string;
|
| 118 |
boostActionDescription: string;
|
| 119 |
+
disparageActionName: string;
|
| 120 |
disparageActionDescription: string;
|
| 121 |
+
specialActionName: string;
|
| 122 |
specialActionDescription: string;
|
| 123 |
}
|