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 |
}
|