Fraser commited on
Commit
600cde8
·
1 Parent(s): 0b4d8ba
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 points (0=fragile, 100=tank)"},
71
- "defence": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Defensive capability (0=paper thin, 100=impenetrable)"},
72
- "attack": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Attack power (0=harmless, 100=devastating)"},
73
- "speed": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Movement speed (0=immobile, 100=lightning fast)"},
74
- "specialPassiveTraitDescription": {"type": "string", "description": "Description of a passive trait that gives the monster a unique advantage in battle"},
75
- "attackActionDescription": {"type": "string", "description": "Description of a primary attack that deals damage"},
76
- "boostActionDescription": {"type": "string", "description": "Description of an action that buffs the monster's own stats/status"},
77
- "disparageActionDescription": {"type": "string", "description": "Description of an action that lowers enemy stats/status"},
78
- "specialActionDescription": {"type": "string", "description": "Description of a powerful action with single use per battle"}
 
 
 
 
79
  },
80
- "required": ["name", "description", "rarity", "HP", "defence", "attack", "speed", "specialPassiveTraitDescription", "attackActionDescription", "boostActionDescription", "disparageActionDescription", "specialActionDescription"]
 
 
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
- await saveMonster({
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 || undefined
 
 
 
 
 
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
- {#if workflowState.monsterStats}
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>Disparage</h5>
147
  <p>{workflowState.monsterStats.disparageActionDescription}</p>
148
  </div>
149
  <div class="ability-item">
150
- <h5>Special</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
  }