Fraser commited on
Commit
08f60f4
·
1 Parent(s): b16a79f

faster monster gen

Browse files
src/lib/components/MonsterGenerator/MonsterGenerator.svelte CHANGED
@@ -18,16 +18,15 @@
18
  userImage: null,
19
  imageCaption: null,
20
  monsterConcept: null,
 
21
  imagePrompt: null,
22
  monsterImage: null,
23
  error: null,
24
  isProcessing: false
25
  });
26
 
27
- // Prompt templates
28
- const MONSTER_CONCEPT_PROMPT = (caption: string) => `Based on this image caption: "${caption}"
29
-
30
- Create a Pokémon-style monster that transforms the object into an imaginative creature. The monster should clearly be inspired by the object's appearance but reimagined as a living monster.
31
 
32
  Guidelines:
33
  - Take the object's key visual elements (colors, shapes, materials) incorporating all of them into a single creature design
@@ -42,45 +41,23 @@ Include:
42
  - A creative name that hints at the original object
43
  - Physical description showing how the object becomes a creature
44
  - Special abilities derived from the object's function
45
- - Personality traits based on the object's purpose`;
 
 
 
 
 
 
 
 
 
 
46
 
47
  const IMAGE_GENERATION_PROMPT = (concept: string) => `Extract ONLY the visual appearance from this monster concept and describe it in one concise sentence:
48
  "${concept}"
49
 
50
  Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit backstory, abilities, and non-visual details.`;
51
 
52
- const MONSTER_STATS_PROMPT = (concept: string) => `Convert the following monster concept into a JSON object with stats:
53
- "${concept}"
54
-
55
- The output should be formatted as a JSON instance that conforms to the JSON schema below.
56
-
57
- \`\`\`json
58
- {
59
- "properties": {
60
- "name": {"type": "string", "description": "The monster's unique name"},
61
- "description": {"type": "string", "description": "A brief physical description of the monster's appearance"},
62
- "rarity": {"type": "integer", "minimum": 0, "maximum": 100, "description": "How rare/unique the monster is (0=very common, 100=legendary)"},
63
- "HP": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Health/vitality stat (0=fragile, 100=incredibly tanky)"},
64
- "defence": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Defensive/armor stat (0=paper thin, 100=impenetrable fortress)"},
65
- "attack": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Physical attack power (0=harmless, 100=devastating force)"},
66
- "speed": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Movement and reaction speed (0=immobile, 100=lightning fast)"},
67
- "specialPassiveTraitDescription": {"type": "string", "description": "Describe a passive ability that gives this monster a unique advantage in battle"},
68
- "attackActionName": {"type": "string", "description": "Name of the monster's primary damage-dealing attack (e.g., 'Flame Burst', 'Toxic Bite')"},
69
- "attackActionDescription": {"type": "string", "description": "Describe how this attack damages the opponent and any special effects"},
70
- "buffActionName": {"type": "string", "description": "Name of the monster's self-enhancement ability (e.g., 'Iron Defense', 'Speed Boost')"},
71
- "buffActionDescription": {"type": "string", "description": "Describe which stats are boosted and how this improves the monster's battle performance"},
72
- "debuffActionName": {"type": "string", "description": "Name of the monster's enemy-weakening ability (e.g., 'Intimidate', 'Slow Poison')"},
73
- "debuffActionDescription": {"type": "string", "description": "Describe which enemy stats are lowered and how this weakens the opponent"},
74
- "specialActionName": {"type": "string", "description": "Name of the monster's ultimate move (one use per battle)"},
75
- "specialActionDescription": {"type": "string", "description": "Describe this powerful finishing move and its dramatic effects in battle"}
76
- },
77
- "required": ["name", "description", "rarity", "HP", "defence", "attack", "speed", "specialPassiveTraitDescription", "attackActionName", "attackActionDescription", "buffActionName", "buffActionDescription", "debuffActionName", "debuffActionDescription", "specialActionName", "specialActionDescription"]
78
- }
79
- \`\`\`
80
-
81
- Remember to base the stats on how unique/powerful the original object was. Common objects should have lower stats, unique objects should have higher stats.
82
-
83
- Write your response within \`\`\`json\`\`\``;
84
 
85
  async function importPiclet(picletData: PicletInstance) {
86
  state.isProcessing = true;
@@ -132,26 +109,18 @@ Write your response within \`\`\`json\`\`\``;
132
  state.isProcessing = true;
133
 
134
  try {
135
- // Step 1: Caption the image
136
  await captionImage();
137
  await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for state update
138
 
139
- // Step 2: Generate monster concept
140
- await generateMonsterConcept();
141
- await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for state update
142
-
143
- // Step 3: Generate monster stats
144
  await generateStats();
145
  await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for state update
146
 
147
- // Step 4: Generate image prompt
148
- await generateImagePrompt();
149
- await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for state update
150
-
151
- // Step 5: Generate monster image
152
  await generateMonsterImage();
153
 
154
- // Step 6: Auto-save the monster
155
  await autoSaveMonster();
156
 
157
  state.currentStep = 'complete';
@@ -210,102 +179,69 @@ Write your response within \`\`\`json\`\`\``;
210
  try {
211
  const output = await joyCaptionClient.predict("/stream_chat", [
212
  state.userImage,
213
- "Descriptive" as CaptionType,
214
  "very long" as CaptionLength,
215
  [], // extra_options
216
  "", // name_input
217
- "" // custom_prompt
218
  ]);
219
 
220
  const [prompt, caption] = output.data;
 
221
  state.imageCaption = caption;
222
- console.log('Caption generated:', caption);
 
223
  } catch (error) {
224
  handleAPIError(error);
225
  }
226
  }
227
 
228
- async function generateMonsterConcept() {
229
- state.currentStep = 'conceptualizing';
230
-
231
- if (!rwkvClient || !state.imageCaption) {
232
- throw new Error('Text generation service not available or no caption');
233
- }
234
-
235
- const conceptPrompt = MONSTER_CONCEPT_PROMPT(state.imageCaption);
236
- const systemPrompt = "You are a creative Pokémon designer. Transform everyday objects into imaginative monsters by blending their characteristics with creature features. Think like the designers who turned a Pokéball into Voltorb, a keyring into Klefki, or an ice cream cone into Vanillite. Be creative and whimsical while keeping the object recognizable.";
237
-
238
- console.log('Generating monster concept with prompt:', conceptPrompt);
239
-
240
- try {
241
- const output = await rwkvClient.predict("/chat", [
242
- conceptPrompt, // message
243
- [], // chat_history
244
- systemPrompt, // system_prompt
245
- 2048, // max_new_tokens
246
- 0.7, // temperature
247
- 0.95, // top_p
248
- 50, // top_k
249
- 1.0 // repetition_penalty
250
- ]);
251
-
252
- console.log('Zephyr output:', output.data[0]);
253
- let conceptText = output.data[0];
254
-
255
- state.monsterConcept = conceptText;
256
- console.log('Monster concept generated:', state.monsterConcept);
257
-
258
- if (!state.monsterConcept || state.monsterConcept.trim() === '') {
259
- throw new Error('Failed to generate monster concept - received empty response');
260
- }
261
- } catch (error) {
262
- handleAPIError(error);
263
- }
264
- }
265
 
266
- async function generateImagePrompt() {
267
- state.currentStep = 'promptCrafting';
 
268
 
269
- if (!rwkvClient || !state.monsterConcept) {
270
- throw new Error('Text generation service not available or no concept');
271
  }
272
 
273
- const promptGenerationPrompt = IMAGE_GENERATION_PROMPT(state.monsterConcept);
274
- const systemPrompt = "You are an expert at creating concise visual descriptions for image generation. Extract ONLY visual appearance details and describe them in ONE sentence (max 50 words). Focus on colors, shape, eyes, limbs, and distinctive features. Omit all non-visual information like abilities, personality, or backstory.";
275
 
276
- console.log('Generating image prompt from concept');
277
-
278
- try {
279
- const output = await rwkvClient.predict("/chat", [
280
- promptGenerationPrompt, // message
281
- [], // chat_history
282
- systemPrompt, // system_prompt
283
- 1024, // max_new_tokens
284
- 0.7, // temperature
285
- 0.95, // top_p
286
- 50, // top_k
287
- 1.0 // repetition_penalty
288
- ]);
289
 
290
- console.log('Image prompt output:', output);
291
- let promptText = output.data[0];
292
 
293
- state.imagePrompt = promptText;
294
- console.log('Image prompt generated:', state.imagePrompt);
295
 
296
- if (!state.imagePrompt || state.imagePrompt.trim() === '') {
297
- throw new Error('Failed to generate image prompt - received empty response');
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  }
299
- } catch (error) {
300
- handleAPIError(error);
301
  }
302
- }
303
-
304
- async function generateMonsterImage() {
305
- state.currentStep = 'generating';
306
 
307
- if (!fluxClient || !state.imagePrompt) {
308
- throw new Error('Image generation service not available or no prompt');
309
  }
310
 
311
  try {
@@ -361,7 +297,61 @@ Write your response within \`\`\`json\`\`\``;
361
  throw new Error('Text generation service not available or no concept');
362
  }
363
 
364
- const statsPrompt = MONSTER_STATS_PROMPT(state.monsterConcept);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  const systemPrompt = "You are a game designer specializing in monster stats and abilities. You must ONLY output valid JSON that matches the provided schema exactly. Do not include any text before or after the JSON. Do not include null values in your JSON response. Your entire response should be wrapped in a ```json``` code block.";
366
 
367
  console.log('Generating monster stats from concept');
@@ -542,13 +532,9 @@ Write your response within \`\`\`json\`\`\``;
542
  <div class="spinner"></div>
543
  <p class="processing-text">
544
  {#if state.currentStep === 'captioning'}
545
- Analyzing your image...
546
- {:else if state.currentStep === 'conceptualizing'}
547
- Creating monster concept...
548
  {:else if state.currentStep === 'statsGenerating'}
549
  Generating battle stats...
550
- {:else if state.currentStep === 'promptCrafting'}
551
- Crafting generation prompt...
552
  {:else if state.currentStep === 'generating'}
553
  Generating your monster...
554
  {/if}
 
18
  userImage: null,
19
  imageCaption: null,
20
  monsterConcept: null,
21
+ monsterStats: null,
22
  imagePrompt: null,
23
  monsterImage: null,
24
  error: null,
25
  isProcessing: false
26
  });
27
 
28
+ // Custom prompt for joy-caption-alpha-two to generate everything in one step
29
+ const MONSTER_GENERATION_PROMPT = `Based on this image create a Pokémon-style monster that transforms the object into an imaginative creature. The monster should clearly be inspired by the object's appearance but reimagined as a living monster.
 
 
30
 
31
  Guidelines:
32
  - Take the object's key visual elements (colors, shapes, materials) incorporating all of them into a single creature design
 
41
  - A creative name that hints at the original object
42
  - Physical description showing how the object becomes a creature
43
  - Special abilities derived from the object's function
44
+ - Personality traits based on the object's purpose
45
+
46
+ Format your response as:
47
+ \`\`\`
48
+ # {monster name}
49
+ Object Rarity: {common, somewhat rare, very rare, extremely rare, legendary}
50
+ ## Monster Visual Description
51
+ ...
52
+ ## Monster Lore
53
+ ...
54
+ \`\`\``;
55
 
56
  const IMAGE_GENERATION_PROMPT = (concept: string) => `Extract ONLY the visual appearance from this monster concept and describe it in one concise sentence:
57
  "${concept}"
58
 
59
  Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit backstory, abilities, and non-visual details.`;
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  async function importPiclet(picletData: PicletInstance) {
63
  state.isProcessing = true;
 
109
  state.isProcessing = true;
110
 
111
  try {
112
+ // Step 1: Generate monster concept with joy-caption (includes lore, visual description, and rarity)
113
  await captionImage();
114
  await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for state update
115
 
116
+ // Step 2: Generate monster stats based on the concept
 
 
 
 
117
  await generateStats();
118
  await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for state update
119
 
120
+ // Step 3: Generate monster image
 
 
 
 
121
  await generateMonsterImage();
122
 
123
+ // Step 4: Auto-save the monster
124
  await autoSaveMonster();
125
 
126
  state.currentStep = 'complete';
 
179
  try {
180
  const output = await joyCaptionClient.predict("/stream_chat", [
181
  state.userImage,
182
+ "Creative" as CaptionType,
183
  "very long" as CaptionLength,
184
  [], // extra_options
185
  "", // name_input
186
+ MONSTER_GENERATION_PROMPT // custom_prompt
187
  ]);
188
 
189
  const [prompt, caption] = output.data;
190
+ // The caption now contains the full monster concept with lore, visual description, and rarity
191
  state.imageCaption = caption;
192
+ state.monsterConcept = caption; // Store as concept since it's the full monster details
193
+ console.log('Monster concept generated:', caption);
194
  } catch (error) {
195
  handleAPIError(error);
196
  }
197
  }
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
+
201
+ async function generateMonsterImage() {
202
+ state.currentStep = 'generating';
203
 
204
+ if (!fluxClient || !state.monsterConcept) {
205
+ throw new Error('Image generation service not available or no concept');
206
  }
207
 
208
+ // Extract the visual description from the joy-caption output
209
+ const visualDescMatch = state.monsterConcept.match(/## Monster Visual Description\s*\n([\s\S]*?)(?=##|$)/);
210
 
211
+ if (visualDescMatch && visualDescMatch[1]) {
212
+ state.imagePrompt = visualDescMatch[1].trim();
213
+ console.log('Extracted visual description for image generation:', state.imagePrompt);
214
+ } else {
215
+ // Fallback: use zephyr to extract visual description
216
+ if (!rwkvClient) {
217
+ throw new Error('Text generation service not available for fallback');
218
+ }
 
 
 
 
 
219
 
220
+ const promptGenerationPrompt = IMAGE_GENERATION_PROMPT(state.monsterConcept);
221
+ const systemPrompt = "You are an expert at creating concise visual descriptions for image generation. Extract ONLY visual appearance details and describe them in ONE sentence (max 50 words). Focus on colors, shape, eyes, limbs, and distinctive features. Omit all non-visual information like abilities, personality, or backstory.";
222
 
223
+ console.log('Falling back to zephyr for visual description extraction');
 
224
 
225
+ try {
226
+ const output = await rwkvClient.predict("/chat", [
227
+ promptGenerationPrompt, // message
228
+ [], // chat_history
229
+ systemPrompt, // system_prompt
230
+ 1024, // max_new_tokens
231
+ 0.7, // temperature
232
+ 0.95, // top_p
233
+ 50, // top_k
234
+ 1.0 // repetition_penalty
235
+ ]);
236
+
237
+ state.imagePrompt = output.data[0];
238
+ } catch (error) {
239
+ handleAPIError(error);
240
  }
 
 
241
  }
 
 
 
 
242
 
243
+ if (!state.imagePrompt || state.imagePrompt.trim() === '') {
244
+ throw new Error('Failed to extract visual description');
245
  }
246
 
247
  try {
 
297
  throw new Error('Text generation service not available or no concept');
298
  }
299
 
300
+ // Extract rarity from the joy-caption output
301
+ let rarityScore = 50; // default
302
+ const rarityMatch = state.monsterConcept.match(/Object Rarity:\s*(common|somewhat rare|very rare|extremely rare|legendary)/i);
303
+ if (rarityMatch) {
304
+ const rarityMap: { [key: string]: number } = {
305
+ 'common': 20,
306
+ 'somewhat rare': 40,
307
+ 'very rare': 60,
308
+ 'extremely rare': 80,
309
+ 'legendary': 100
310
+ };
311
+ rarityScore = rarityMap[rarityMatch[1].toLowerCase()] || 50;
312
+ }
313
+
314
+ // Extract monster name from the concept
315
+ const nameMatch = state.monsterConcept.match(/^#\s+(.+)$/m);
316
+ const monsterName = nameMatch ? nameMatch[1] : 'Unknown Monster';
317
+
318
+ // Update stats prompt to include the rarity score
319
+ const statsPrompt = `Convert the following monster concept into a JSON object with stats:
320
+ "${state.monsterConcept}"
321
+
322
+ The monster's rarity has been determined as: ${rarityScore}/100
323
+ The monster's name is: ${monsterName}
324
+
325
+ The output should be formatted as a JSON instance that conforms to the JSON schema below.
326
+
327
+ \`\`\`json
328
+ {
329
+ "properties": {
330
+ "name": {"type": "string", "description": "The monster's unique name"},
331
+ "description": {"type": "string", "description": "A brief physical description of the monster's appearance"},
332
+ "rarity": {"type": "integer", "minimum": 0, "maximum": 100, "description": "How rare/unique the monster is (0=very common, 100=legendary)"},
333
+ "HP": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Health/vitality stat (0=fragile, 100=incredibly tanky)"},
334
+ "defence": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Defensive/armor stat (0=paper thin, 100=impenetrable fortress)"},
335
+ "attack": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Physical attack power (0=harmless, 100=devastating force)"},
336
+ "speed": {"type": "integer", "minimum": 0, "maximum": 100, "description": "Movement and reaction speed (0=immobile, 100=lightning fast)"},
337
+ "specialPassiveTraitDescription": {"type": "string", "description": "Describe a passive ability that gives this monster a unique advantage in battle"},
338
+ "attackActionName": {"type": "string", "description": "Name of the monster's primary damage-dealing attack (e.g., 'Flame Burst', 'Toxic Bite')"},
339
+ "attackActionDescription": {"type": "string", "description": "Describe how this attack damages the opponent and any special effects"},
340
+ "buffActionName": {"type": "string", "description": "Name of the monster's self-enhancement ability (e.g., 'Iron Defense', 'Speed Boost')"},
341
+ "buffActionDescription": {"type": "string", "description": "Describe which stats are boosted and how this improves the monster's battle performance"},
342
+ "debuffActionName": {"type": "string", "description": "Name of the monster's enemy-weakening ability (e.g., 'Intimidate', 'Slow Poison')"},
343
+ "debuffActionDescription": {"type": "string", "description": "Describe which enemy stats are lowered and how this weakens the opponent"},
344
+ "specialActionName": {"type": "string", "description": "Name of the monster's ultimate move (one use per battle)"},
345
+ "specialActionDescription": {"type": "string", "description": "Describe this powerful finishing move and its dramatic effects in battle"}
346
+ },
347
+ "required": ["name", "description", "rarity", "HP", "defence", "attack", "speed", "specialPassiveTraitDescription", "attackActionName", "attackActionDescription", "buffActionName", "buffActionDescription", "debuffActionName", "debuffActionDescription", "specialActionName", "specialActionDescription"]
348
+ }
349
+ \`\`\`
350
+
351
+ Remember to set the rarity to ${rarityScore} and the name to "${monsterName}". Base the other stats on this rarity level.
352
+
353
+ Write your response within \`\`\`json\`\`\``;
354
+
355
  const systemPrompt = "You are a game designer specializing in monster stats and abilities. You must ONLY output valid JSON that matches the provided schema exactly. Do not include any text before or after the JSON. Do not include null values in your JSON response. Your entire response should be wrapped in a ```json``` code block.";
356
 
357
  console.log('Generating monster stats from concept');
 
532
  <div class="spinner"></div>
533
  <p class="processing-text">
534
  {#if state.currentStep === 'captioning'}
535
+ Creating monster concept from your image...
 
 
536
  {:else if state.currentStep === 'statsGenerating'}
537
  Generating battle stats...
 
 
538
  {:else if state.currentStep === 'generating'}
539
  Generating your monster...
540
  {/if}
src/lib/components/MonsterGenerator/WorkflowProgress.svelte CHANGED
@@ -22,28 +22,18 @@
22
  },
23
  {
24
  id: 'captioning',
25
- label: 'Analyzing',
26
- description: 'Creating detailed description'
27
- },
28
- {
29
- id: 'conceptualizing',
30
- label: 'Conceptualizing',
31
- description: 'Designing your monster'
32
  },
33
  {
34
  id: 'statsGenerating',
35
- label: 'Generating Stats',
36
- description: 'Creating battle attributes'
37
- },
38
- {
39
- id: 'promptCrafting',
40
- label: 'Crafting Prompt',
41
- description: 'Preparing image generation'
42
  },
43
  {
44
  id: 'generating',
45
- label: 'Generating',
46
- description: 'Creating monster image'
47
  },
48
  {
49
  id: 'complete',
 
22
  },
23
  {
24
  id: 'captioning',
25
+ label: 'Monster Design',
26
+ description: 'Creating concept & lore'
 
 
 
 
 
27
  },
28
  {
29
  id: 'statsGenerating',
30
+ label: 'Battle Stats',
31
+ description: 'Generating abilities'
 
 
 
 
 
32
  },
33
  {
34
  id: 'generating',
35
+ label: 'Image Generation',
36
+ description: 'Creating monster art'
37
  },
38
  {
39
  id: 'complete',