Fraser commited on
Commit
55b1a24
Β·
1 Parent(s): 6a18e94

monster-piclet

Browse files
src/lib/components/Pages/Scanner.svelte CHANGED
@@ -1,5 +1,5 @@
1
  <script lang="ts">
2
- import MonsterGenerator from '../MonsterGenerator/MonsterGenerator.svelte';
3
  import type { GradioClient } from '$lib/types';
4
 
5
  interface Props {
@@ -13,7 +13,7 @@
13
 
14
  <div class="scanner-page">
15
  {#if fluxClient && joyCaptionClient && zephyrClient}
16
- <MonsterGenerator
17
  {fluxClient}
18
  {joyCaptionClient}
19
  {zephyrClient}
 
1
  <script lang="ts">
2
+ import PicletGenerator from '../PicletGenerator/PicletGenerator.svelte';
3
  import type { GradioClient } from '$lib/types';
4
 
5
  interface Props {
 
13
 
14
  <div class="scanner-page">
15
  {#if fluxClient && joyCaptionClient && zephyrClient}
16
+ <PicletGenerator
17
  {fluxClient}
18
  {joyCaptionClient}
19
  {zephyrClient}
src/lib/components/{MonsterGenerator/MonsterGenerator.svelte β†’ PicletGenerator/PicletGenerator.svelte} RENAMED
@@ -1,27 +1,26 @@
1
  <script lang="ts">
2
- import type { MonsterGeneratorProps, MonsterWorkflowState, CaptionType, CaptionLength, MonsterStats } from '$lib/types';
3
  import type { PicletInstance } from '$lib/db/schema';
4
  import UploadStep from './UploadStep.svelte';
5
  import WorkflowProgress from './WorkflowProgress.svelte';
6
- import MonsterResult from './MonsterResult.svelte';
7
  import { removeBackground } from '$lib/utils/professionalImageProcessing';
8
- import { saveMonster } from '$lib/db/monsters';
9
  import { extractPicletMetadata } from '$lib/services/picletMetadata';
10
- import { savePicletInstance } from '$lib/db/piclets';
11
  import { PicletType, TYPE_DATA } from '$lib/types/picletTypes';
12
 
13
- interface Props extends MonsterGeneratorProps {}
14
 
15
  let { joyCaptionClient, zephyrClient, fluxClient }: Props = $props();
16
 
17
- let state: MonsterWorkflowState = $state({
18
  currentStep: 'upload',
19
  userImage: null,
20
  imageCaption: null,
21
- monsterConcept: null,
22
- monsterStats: null,
23
  imagePrompt: null,
24
- monsterImage: null,
25
  error: null,
26
  isProcessing: false
27
  });
@@ -66,9 +65,11 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
66
  const savedId = await savePicletInstance(picletData);
67
 
68
  // Create a success state similar to generation
69
- state.monsterImage = {
70
  imageUrl: picletData.imageUrl,
71
- imageData: picletData.imageData
 
 
72
  };
73
 
74
  // Show import success
@@ -118,8 +119,8 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
118
  // Step 3: Generate monster image
119
  await generateMonsterImage();
120
 
121
- // Step 4: Auto-save the monster
122
- await autoSaveMonster();
123
 
124
  state.currentStep = 'complete';
125
  } catch (err) {
@@ -187,7 +188,7 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
187
  const [prompt, caption] = output.data;
188
  // The caption now contains the full monster concept with lore, visual description, and rarity
189
  state.imageCaption = caption;
190
- state.monsterConcept = caption; // Store as concept since it's the full monster details
191
  console.log('Monster concept generated:', caption);
192
  } catch (error) {
193
  handleAPIError(error);
@@ -199,12 +200,12 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
199
  async function generateMonsterImage() {
200
  state.currentStep = 'generating';
201
 
202
- if (!fluxClient || !state.monsterConcept || !state.monsterStats) {
203
  throw new Error('Image generation service not available or no concept/stats');
204
  }
205
 
206
  // Extract the visual description from the joy-caption output
207
- const visualDescMatch = state.monsterConcept.match(/## Monster Visual Description\s*\n([\s\S]*?)(?=##|$)/);
208
 
209
  if (visualDescMatch && visualDescMatch[1]) {
210
  state.imagePrompt = visualDescMatch[1].trim();
@@ -213,7 +214,7 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
213
  // Fallback: use text generation to extract visual description
214
  console.log('Using text generation for visual description extraction');
215
 
216
- const promptGenerationPrompt = IMAGE_GENERATION_PROMPT(state.monsterConcept);
217
  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.";
218
 
219
  console.log('Using smart text generation for visual description extraction');
@@ -241,7 +242,7 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
241
  }
242
 
243
  // Get tier for image quality enhancement
244
- const tier = state.monsterStats.tier || 'medium';
245
  const tierDescriptions = {
246
  low: 'simple and basic design',
247
  medium: 'detailed and well-crafted design',
@@ -271,7 +272,7 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
271
  console.log('Processing image for background removal...');
272
  try {
273
  const transparentBase64 = await removeBackground(url);
274
- state.monsterImage = {
275
  imageUrl: url,
276
  imageData: transparentBase64,
277
  seed: usedSeed,
@@ -281,7 +282,7 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
281
  } catch (processError) {
282
  console.error('Failed to process image for background removal:', processError);
283
  // Fallback to original image
284
- state.monsterImage = {
285
  imageUrl: url,
286
  seed: usedSeed,
287
  prompt: state.imagePrompt
@@ -298,7 +299,7 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
298
  async function generateStats() {
299
  state.currentStep = 'statsGenerating';
300
 
301
- if (!state.monsterConcept) {
302
  throw new Error('No concept available for stats generation');
303
  }
304
 
@@ -306,11 +307,11 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
306
  let tier: 'low' | 'medium' | 'high' | 'legendary' = 'medium';
307
 
308
  // Extract object description from the Object Caption section
309
- const objectMatch = state.monsterConcept.match(/# Object Caption\s*\n([\s\S]*?)(?=^# )/m);
310
  const objectDescription = objectMatch ? objectMatch[1].trim() : '';
311
 
312
  // Extract monster name from the Monster Name section - similar to object extraction
313
- const monsterNameMatch = state.monsterConcept.match(/# Monster Name\s*\n([\s\S]*?)(?=^## |$)/m);
314
  const monsterName = monsterNameMatch ? monsterNameMatch[1].trim() : 'Unknown Monster';
315
 
316
  // Debug logging
@@ -319,7 +320,7 @@ Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit
319
 
320
  // Create stats prompt - only ask for battle-related stats
321
  const statsPrompt = `Based on this monster concept, generate a JSON object with battle stats and abilities:
322
- "${state.monsterConcept}"
323
 
324
  The original object is described as: "${objectDescription}"
325
 
@@ -483,8 +484,8 @@ Write your response within \`\`\`json\`\`\``;
483
  delete parsedStats.disparageActionDescription;
484
  }
485
 
486
- const stats: MonsterStats = parsedStats;
487
- state.monsterStats = stats;
488
  console.log('Monster stats generated:', stats);
489
  console.log('Monster stats JSON:', JSON.stringify(stats, null, 2));
490
  } catch (parseError) {
@@ -496,45 +497,48 @@ Write your response within \`\`\`json\`\`\``;
496
  }
497
  }
498
 
499
- async function autoSaveMonster() {
500
- if (!state.monsterImage || !state.imageCaption || !state.monsterConcept || !state.imagePrompt || !state.monsterStats) {
501
  console.error('Cannot auto-save: missing required data');
502
  return;
503
  }
504
 
505
  try {
506
  // Create a clean copy of stats to ensure it's serializable
507
- const cleanStats = JSON.parse(JSON.stringify(state.monsterStats));
508
 
509
- const monsterData = {
510
- name: state.monsterStats.name,
511
- imageUrl: state.monsterImage.imageUrl,
512
- imageData: state.monsterImage.imageData,
513
  imageCaption: state.imageCaption,
514
- concept: state.monsterConcept,
515
  imagePrompt: state.imagePrompt,
516
- stats: cleanStats
 
517
  };
518
 
519
  // Check for any non-serializable data
520
- console.log('Checking monster data for serializability:');
521
- console.log('- name type:', typeof monsterData.name);
522
- console.log('- imageUrl type:', typeof monsterData.imageUrl);
523
- console.log('- imageData type:', typeof monsterData.imageData, monsterData.imageData ? `length: ${monsterData.imageData.length}` : 'null/undefined');
524
- console.log('- imageCaption type:', typeof monsterData.imageCaption);
525
- console.log('- concept type:', typeof monsterData.concept);
526
- console.log('- imagePrompt type:', typeof monsterData.imagePrompt);
527
  console.log('- stats:', cleanStats);
528
 
529
- const id = await saveMonster(monsterData);
530
- console.log('Monster auto-saved with ID:', id);
 
 
531
  } catch (err) {
532
- console.error('Failed to auto-save monster:', err);
533
- console.error('Monster data that failed to save:', {
534
- name: state.monsterStats?.name,
535
- hasImageUrl: !!state.monsterImage?.imageUrl,
536
- hasImageData: !!state.monsterImage?.imageData,
537
- hasStats: !!state.monsterStats
538
  });
539
  // Don't throw - we don't want to interrupt the workflow
540
  }
@@ -545,17 +549,17 @@ Write your response within \`\`\`json\`\`\``;
545
  currentStep: 'upload',
546
  userImage: null,
547
  imageCaption: null,
548
- monsterConcept: null,
549
- monsterStats: null,
550
  imagePrompt: null,
551
- monsterImage: null,
552
  error: null,
553
  isProcessing: false
554
  };
555
  }
556
  </script>
557
 
558
- <div class="monster-generator">
559
 
560
  {#if state.currentStep !== 'upload'}
561
  <WorkflowProgress currentStep={state.currentStep} error={state.error} />
@@ -567,7 +571,7 @@ Write your response within \`\`\`json\`\`\``;
567
  isProcessing={state.isProcessing}
568
  />
569
  {:else if state.currentStep === 'complete'}
570
- <MonsterResult workflowState={state} onReset={reset} />
571
  {:else}
572
  <div class="processing-container">
573
  <div class="spinner"></div>
@@ -585,7 +589,7 @@ Write your response within \`\`\`json\`\`\``;
585
  </div>
586
 
587
  <style>
588
- .monster-generator {
589
  width: 100%;
590
  max-width: 1200px;
591
  margin: 0 auto;
 
1
  <script lang="ts">
2
+ import type { PicletGeneratorProps, PicletWorkflowState, CaptionType, CaptionLength, PicletStats } from '$lib/types';
3
  import type { PicletInstance } from '$lib/db/schema';
4
  import UploadStep from './UploadStep.svelte';
5
  import WorkflowProgress from './WorkflowProgress.svelte';
6
+ import PicletResult from './PicletResult.svelte';
7
  import { removeBackground } from '$lib/utils/professionalImageProcessing';
 
8
  import { extractPicletMetadata } from '$lib/services/picletMetadata';
9
+ import { savePicletInstance, monsterToPicletInstance } from '$lib/db/piclets';
10
  import { PicletType, TYPE_DATA } from '$lib/types/picletTypes';
11
 
12
+ interface Props extends PicletGeneratorProps {}
13
 
14
  let { joyCaptionClient, zephyrClient, fluxClient }: Props = $props();
15
 
16
+ let state: PicletWorkflowState = $state({
17
  currentStep: 'upload',
18
  userImage: null,
19
  imageCaption: null,
20
+ picletConcept: null,
21
+ picletStats: null,
22
  imagePrompt: null,
23
+ picletImage: null,
24
  error: null,
25
  isProcessing: false
26
  });
 
65
  const savedId = await savePicletInstance(picletData);
66
 
67
  // Create a success state similar to generation
68
+ state.picletImage = {
69
  imageUrl: picletData.imageUrl,
70
+ imageData: picletData.imageData,
71
+ seed: 0,
72
+ prompt: 'Imported piclet'
73
  };
74
 
75
  // Show import success
 
119
  // Step 3: Generate monster image
120
  await generateMonsterImage();
121
 
122
+ // Step 4: Auto-save the piclet
123
+ await autoSavePiclet();
124
 
125
  state.currentStep = 'complete';
126
  } catch (err) {
 
188
  const [prompt, caption] = output.data;
189
  // The caption now contains the full monster concept with lore, visual description, and rarity
190
  state.imageCaption = caption;
191
+ state.picletConcept = caption; // Store as concept since it's the full monster details
192
  console.log('Monster concept generated:', caption);
193
  } catch (error) {
194
  handleAPIError(error);
 
200
  async function generateMonsterImage() {
201
  state.currentStep = 'generating';
202
 
203
+ if (!fluxClient || !state.picletConcept || !state.picletStats) {
204
  throw new Error('Image generation service not available or no concept/stats');
205
  }
206
 
207
  // Extract the visual description from the joy-caption output
208
+ const visualDescMatch = state.picletConcept.match(/## Monster Visual Description\s*\n([\s\S]*?)(?=##|$)/);
209
 
210
  if (visualDescMatch && visualDescMatch[1]) {
211
  state.imagePrompt = visualDescMatch[1].trim();
 
214
  // Fallback: use text generation to extract visual description
215
  console.log('Using text generation for visual description extraction');
216
 
217
+ const promptGenerationPrompt = IMAGE_GENERATION_PROMPT(state.picletConcept);
218
  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.";
219
 
220
  console.log('Using smart text generation for visual description extraction');
 
242
  }
243
 
244
  // Get tier for image quality enhancement
245
+ const tier = state.picletStats.tier || 'medium';
246
  const tierDescriptions = {
247
  low: 'simple and basic design',
248
  medium: 'detailed and well-crafted design',
 
272
  console.log('Processing image for background removal...');
273
  try {
274
  const transparentBase64 = await removeBackground(url);
275
+ state.picletImage = {
276
  imageUrl: url,
277
  imageData: transparentBase64,
278
  seed: usedSeed,
 
282
  } catch (processError) {
283
  console.error('Failed to process image for background removal:', processError);
284
  // Fallback to original image
285
+ state.picletImage = {
286
  imageUrl: url,
287
  seed: usedSeed,
288
  prompt: state.imagePrompt
 
299
  async function generateStats() {
300
  state.currentStep = 'statsGenerating';
301
 
302
+ if (!state.picletConcept) {
303
  throw new Error('No concept available for stats generation');
304
  }
305
 
 
307
  let tier: 'low' | 'medium' | 'high' | 'legendary' = 'medium';
308
 
309
  // Extract object description from the Object Caption section
310
+ const objectMatch = state.picletConcept.match(/# Object Caption\s*\n([\s\S]*?)(?=^# )/m);
311
  const objectDescription = objectMatch ? objectMatch[1].trim() : '';
312
 
313
  // Extract monster name from the Monster Name section - similar to object extraction
314
+ const monsterNameMatch = state.picletConcept.match(/# Monster Name\s*\n([\s\S]*?)(?=^## |$)/m);
315
  const monsterName = monsterNameMatch ? monsterNameMatch[1].trim() : 'Unknown Monster';
316
 
317
  // Debug logging
 
320
 
321
  // Create stats prompt - only ask for battle-related stats
322
  const statsPrompt = `Based on this monster concept, generate a JSON object with battle stats and abilities:
323
+ "${state.picletConcept}"
324
 
325
  The original object is described as: "${objectDescription}"
326
 
 
484
  delete parsedStats.disparageActionDescription;
485
  }
486
 
487
+ const stats: PicletStats = parsedStats;
488
+ state.picletStats = stats;
489
  console.log('Monster stats generated:', stats);
490
  console.log('Monster stats JSON:', JSON.stringify(stats, null, 2));
491
  } catch (parseError) {
 
497
  }
498
  }
499
 
500
+ async function autoSavePiclet() {
501
+ if (!state.picletImage || !state.imageCaption || !state.picletConcept || !state.imagePrompt || !state.picletStats) {
502
  console.error('Cannot auto-save: missing required data');
503
  return;
504
  }
505
 
506
  try {
507
  // Create a clean copy of stats to ensure it's serializable
508
+ const cleanStats = JSON.parse(JSON.stringify(state.picletStats));
509
 
510
+ const picletData = {
511
+ name: state.picletStats.name,
512
+ imageUrl: state.picletImage.imageUrl,
513
+ imageData: state.picletImage.imageData,
514
  imageCaption: state.imageCaption,
515
+ concept: state.picletConcept,
516
  imagePrompt: state.imagePrompt,
517
+ stats: cleanStats,
518
+ createdAt: new Date()
519
  };
520
 
521
  // Check for any non-serializable data
522
+ console.log('Checking piclet data for serializability:');
523
+ console.log('- name type:', typeof picletData.name);
524
+ console.log('- imageUrl type:', typeof picletData.imageUrl);
525
+ console.log('- imageData type:', typeof picletData.imageData, picletData.imageData ? `length: ${picletData.imageData.length}` : 'null/undefined');
526
+ console.log('- imageCaption type:', typeof picletData.imageCaption);
527
+ console.log('- concept type:', typeof picletData.concept);
528
+ console.log('- imagePrompt type:', typeof picletData.imagePrompt);
529
  console.log('- stats:', cleanStats);
530
 
531
+ // Convert to PicletInstance format and save
532
+ const picletInstance = await monsterToPicletInstance(picletData);
533
+ const id = await savePicletInstance(picletInstance);
534
+ console.log('Piclet auto-saved with ID:', id);
535
  } catch (err) {
536
+ console.error('Failed to auto-save piclet:', err);
537
+ console.error('Piclet data that failed to save:', {
538
+ name: state.picletStats?.name,
539
+ hasImageUrl: !!state.picletImage?.imageUrl,
540
+ hasImageData: !!state.picletImage?.imageData,
541
+ hasStats: !!state.picletStats
542
  });
543
  // Don't throw - we don't want to interrupt the workflow
544
  }
 
549
  currentStep: 'upload',
550
  userImage: null,
551
  imageCaption: null,
552
+ picletConcept: null,
553
+ picletStats: null,
554
  imagePrompt: null,
555
+ picletImage: null,
556
  error: null,
557
  isProcessing: false
558
  };
559
  }
560
  </script>
561
 
562
+ <div class="piclet-generator">
563
 
564
  {#if state.currentStep !== 'upload'}
565
  <WorkflowProgress currentStep={state.currentStep} error={state.error} />
 
571
  isProcessing={state.isProcessing}
572
  />
573
  {:else if state.currentStep === 'complete'}
574
+ <PicletResult workflowState={state} onReset={reset} />
575
  {:else}
576
  <div class="processing-container">
577
  <div class="spinner"></div>
 
589
  </div>
590
 
591
  <style>
592
+ .piclet-generator {
593
  width: 100%;
594
  max-width: 1200px;
595
  margin: 0 auto;
src/lib/components/{MonsterGenerator/MonsterResult.svelte β†’ PicletGenerator/PicletResult.svelte} RENAMED
@@ -1,14 +1,13 @@
1
  <script lang="ts">
2
- import type { MonsterWorkflowState } from '$lib/types';
3
  import type { PicletInstance } from '$lib/db/schema';
4
- import { saveMonster } from '$lib/db/monsters';
5
  import { TYPE_DATA, PicletType } from '$lib/types/picletTypes';
6
  import { monsterToPicletInstance } from '$lib/db/piclets';
7
  import PicletCard from '../Piclets/PicletCard.svelte';
8
  import PicletDetail from '../Piclets/PicletDetail.svelte';
9
 
10
  interface Props {
11
- workflowState: MonsterWorkflowState;
12
  onReset: () => void;
13
  }
14
 
@@ -19,23 +18,31 @@
19
  let showDetailView = $state(false);
20
  let saveError: string | null = $state(null);
21
 
22
- // Create piclet instance from the monster data
23
  $effect(() => {
24
- if (workflowState.monsterImage && workflowState.monsterConcept && workflowState.monsterStats) {
25
  try {
26
- const monster = {
27
- name: workflowState.monsterStats.name || 'Unknown Monster',
28
- imageUrl: workflowState.monsterImage.imageUrl,
29
- imageData: workflowState.monsterImage.imageData,
30
  imageCaption: workflowState.imageCaption || '',
31
- concept: workflowState.monsterConcept,
32
  imagePrompt: workflowState.imagePrompt || '',
33
- stats: workflowState.monsterStats
 
34
  };
35
 
36
- picletInstance = monsterToPicletInstance(monster);
 
 
 
 
 
 
37
  } catch (error) {
38
  console.error('Failed to create piclet instance:', error);
 
39
  }
40
  }
41
  });
 
1
  <script lang="ts">
2
+ import type { PicletWorkflowState } from '$lib/types';
3
  import type { PicletInstance } from '$lib/db/schema';
 
4
  import { TYPE_DATA, PicletType } from '$lib/types/picletTypes';
5
  import { monsterToPicletInstance } from '$lib/db/piclets';
6
  import PicletCard from '../Piclets/PicletCard.svelte';
7
  import PicletDetail from '../Piclets/PicletDetail.svelte';
8
 
9
  interface Props {
10
+ workflowState: PicletWorkflowState;
11
  onReset: () => void;
12
  }
13
 
 
18
  let showDetailView = $state(false);
19
  let saveError: string | null = $state(null);
20
 
21
+ // Create piclet instance from the generated data
22
  $effect(() => {
23
+ if (workflowState.picletImage && workflowState.picletConcept && workflowState.picletStats) {
24
  try {
25
+ const picletData = {
26
+ name: workflowState.picletStats.name || 'Unknown Piclet',
27
+ imageUrl: workflowState.picletImage.imageUrl,
28
+ imageData: workflowState.picletImage.imageData,
29
  imageCaption: workflowState.imageCaption || '',
30
+ concept: workflowState.picletConcept,
31
  imagePrompt: workflowState.imagePrompt || '',
32
+ stats: workflowState.picletStats,
33
+ createdAt: new Date()
34
  };
35
 
36
+ // Create piclet instance asynchronously
37
+ monsterToPicletInstance(picletData).then(instance => {
38
+ picletInstance = { ...instance, id: 0 }; // Add temporary id for display
39
+ }).catch(error => {
40
+ console.error('Failed to create piclet instance:', error);
41
+ saveError = `Failed to create piclet: ${(error as Error).message || String(error)}`;
42
+ });
43
  } catch (error) {
44
  console.error('Failed to create piclet instance:', error);
45
+ saveError = `Failed to create piclet: ${(error as Error).message || String(error)}`;
46
  }
47
  }
48
  });
src/lib/components/{MonsterGenerator β†’ PicletGenerator}/UploadStep.svelte RENAMED
File without changes
src/lib/components/{MonsterGenerator β†’ PicletGenerator}/WorkflowProgress.svelte RENAMED
@@ -1,15 +1,15 @@
1
  <script lang="ts">
2
- import type { MonsterWorkflowStep } from '$lib/types';
3
 
4
  interface Props {
5
- currentStep: MonsterWorkflowStep;
6
  error?: string | null;
7
  }
8
 
9
  let { currentStep, error = null }: Props = $props();
10
 
11
  interface StepInfo {
12
- id: MonsterWorkflowStep;
13
  label: string;
14
  description: string;
15
  }
@@ -22,7 +22,7 @@
22
  },
23
  {
24
  id: 'captioning',
25
- label: 'Monster Design',
26
  description: 'Creating concept & lore'
27
  },
28
  {
@@ -33,16 +33,16 @@
33
  {
34
  id: 'generating',
35
  label: 'Image Generation',
36
- description: 'Creating monster art'
37
  },
38
  {
39
  id: 'complete',
40
  label: 'Complete',
41
- description: 'Your monster is ready!'
42
  }
43
  ];
44
 
45
- function getStepIndex(step: MonsterWorkflowStep): number {
46
  return steps.findIndex(s => s.id === step);
47
  }
48
 
 
1
  <script lang="ts">
2
+ import type { PicletWorkflowStep } from '$lib/types';
3
 
4
  interface Props {
5
+ currentStep: PicletWorkflowStep;
6
  error?: string | null;
7
  }
8
 
9
  let { currentStep, error = null }: Props = $props();
10
 
11
  interface StepInfo {
12
+ id: PicletWorkflowStep;
13
  label: string;
14
  description: string;
15
  }
 
22
  },
23
  {
24
  id: 'captioning',
25
+ label: 'Piclet Design',
26
  description: 'Creating concept & lore'
27
  },
28
  {
 
33
  {
34
  id: 'generating',
35
  label: 'Image Generation',
36
+ description: 'Creating piclet art'
37
  },
38
  {
39
  id: 'complete',
40
  label: 'Complete',
41
+ description: 'Your piclet is ready!'
42
  }
43
  ];
44
 
45
+ function getStepIndex(step: PicletWorkflowStep): number {
46
  return steps.findIndex(s => s.id === step);
47
  }
48
 
src/lib/components/{MonsterGenerator β†’ PicletGenerator}/test.json RENAMED
File without changes
src/lib/components/{MonsterGenerator β†’ PicletGenerator}/todo.txt RENAMED
File without changes
src/lib/db/piclets.ts CHANGED
@@ -29,9 +29,18 @@ export async function monsterToPicletInstance(monster: Monster, level: number =
29
  // Determine primary type - use picletType from stats if available, otherwise auto-detect
30
  let primaryType: PicletType;
31
  if (monster.stats && (monster.stats as any).picletType) {
32
- // Use the type determined during stat generation
33
  const statsType = (monster.stats as any).picletType as string;
34
- primaryType = statsType as PicletType;
 
 
 
 
 
 
 
 
 
35
  } else {
36
  // Fallback to concept-based detection
37
  primaryType = getTypeFromConcept(monster.concept, monster.imageCaption);
 
29
  // Determine primary type - use picletType from stats if available, otherwise auto-detect
30
  let primaryType: PicletType;
31
  if (monster.stats && (monster.stats as any).picletType) {
32
+ // Use the type determined during stat generation, but validate it
33
  const statsType = (monster.stats as any).picletType as string;
34
+ const normalizedType = statsType.toLowerCase();
35
+
36
+ // Validate that the type exists in our enum
37
+ const validType = Object.values(PicletType).find(type => type === normalizedType);
38
+ if (validType) {
39
+ primaryType = validType;
40
+ } else {
41
+ console.warn(`Invalid picletType "${statsType}" from stats, falling back to concept detection`);
42
+ primaryType = getTypeFromConcept(monster.concept, monster.imageCaption);
43
+ }
44
  } else {
45
  // Fallback to concept-based detection
46
  primaryType = getTypeFromConcept(monster.concept, monster.imageCaption);
src/lib/types/index.ts CHANGED
@@ -74,8 +74,8 @@ export interface GradioLibs {
74
  };
75
  }
76
 
77
- // Monster Generator Types
78
- export type MonsterWorkflowStep =
79
  | 'upload'
80
  | 'captioning'
81
  | 'conceptualizing'
@@ -84,26 +84,26 @@ export type MonsterWorkflowStep =
84
  | 'generating'
85
  | 'complete';
86
 
87
- export interface MonsterWorkflowState {
88
- currentStep: MonsterWorkflowStep;
89
  userImage: Blob | null;
90
  imageCaption: string | null;
91
- monsterConcept: string | null;
92
- monsterStats: MonsterStats | null;
93
  imagePrompt: string | null;
94
- monsterImage: FluxGenerationResult | null;
95
  error: string | null;
96
  isProcessing: boolean;
97
  }
98
 
99
- export interface MonsterGeneratorProps {
100
  joyCaptionClient: GradioClient | null;
101
  zephyrClient: GradioClient | null;
102
  fluxClient: GradioClient | null;
103
  }
104
 
105
- // Monster Stats Types
106
- export interface MonsterStats {
107
  name: string;
108
  description: string;
109
  tier: 'low' | 'medium' | 'high' | 'legendary';
 
74
  };
75
  }
76
 
77
+ // Piclet Generator Types
78
+ export type PicletWorkflowStep =
79
  | 'upload'
80
  | 'captioning'
81
  | 'conceptualizing'
 
84
  | 'generating'
85
  | 'complete';
86
 
87
+ export interface PicletWorkflowState {
88
+ currentStep: PicletWorkflowStep;
89
  userImage: Blob | null;
90
  imageCaption: string | null;
91
+ picletConcept: string | null;
92
+ picletStats: PicletStats | null;
93
  imagePrompt: string | null;
94
+ picletImage: FluxGenerationResult | null;
95
  error: string | null;
96
  isProcessing: boolean;
97
  }
98
 
99
+ export interface PicletGeneratorProps {
100
  joyCaptionClient: GradioClient | null;
101
  zephyrClient: GradioClient | null;
102
  fluxClient: GradioClient | null;
103
  }
104
 
105
+ // Piclet Stats Types
106
+ export interface PicletStats {
107
  name: string;
108
  description: string;
109
  tier: 'low' | 'medium' | 'high' | 'legendary';