monster-piclet
Browse files- src/lib/components/Pages/Scanner.svelte +2 -2
- src/lib/components/{MonsterGenerator/MonsterGenerator.svelte β PicletGenerator/PicletGenerator.svelte} +60 -56
- src/lib/components/{MonsterGenerator/MonsterResult.svelte β PicletGenerator/PicletResult.svelte} +19 -12
- src/lib/components/{MonsterGenerator β PicletGenerator}/UploadStep.svelte +0 -0
- src/lib/components/{MonsterGenerator β PicletGenerator}/WorkflowProgress.svelte +7 -7
- src/lib/components/{MonsterGenerator β PicletGenerator}/test.json +0 -0
- src/lib/components/{MonsterGenerator β PicletGenerator}/todo.txt +0 -0
- src/lib/db/piclets.ts +11 -2
- src/lib/types/index.ts +10 -10
src/lib/components/Pages/Scanner.svelte
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import
|
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 |
-
<
|
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 {
|
3 |
import type { PicletInstance } from '$lib/db/schema';
|
4 |
import UploadStep from './UploadStep.svelte';
|
5 |
import WorkflowProgress from './WorkflowProgress.svelte';
|
6 |
-
import
|
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
|
14 |
|
15 |
let { joyCaptionClient, zephyrClient, fluxClient }: Props = $props();
|
16 |
|
17 |
-
let state:
|
18 |
currentStep: 'upload',
|
19 |
userImage: null,
|
20 |
imageCaption: null,
|
21 |
-
|
22 |
-
|
23 |
imagePrompt: null,
|
24 |
-
|
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.
|
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
|
122 |
-
await
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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:
|
487 |
-
state.
|
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
|
500 |
-
if (!state.
|
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.
|
508 |
|
509 |
-
const
|
510 |
-
name: state.
|
511 |
-
imageUrl: state.
|
512 |
-
imageData: state.
|
513 |
imageCaption: state.imageCaption,
|
514 |
-
concept: state.
|
515 |
imagePrompt: state.imagePrompt,
|
516 |
-
stats: cleanStats
|
|
|
517 |
};
|
518 |
|
519 |
// Check for any non-serializable data
|
520 |
-
console.log('Checking
|
521 |
-
console.log('- name type:', typeof
|
522 |
-
console.log('- imageUrl type:', typeof
|
523 |
-
console.log('- imageData type:', typeof
|
524 |
-
console.log('- imageCaption type:', typeof
|
525 |
-
console.log('- concept type:', typeof
|
526 |
-
console.log('- imagePrompt type:', typeof
|
527 |
console.log('- stats:', cleanStats);
|
528 |
|
529 |
-
|
530 |
-
|
|
|
|
|
531 |
} catch (err) {
|
532 |
-
console.error('Failed to auto-save
|
533 |
-
console.error('
|
534 |
-
name: state.
|
535 |
-
hasImageUrl: !!state.
|
536 |
-
hasImageData: !!state.
|
537 |
-
hasStats: !!state.
|
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 |
-
|
549 |
-
|
550 |
imagePrompt: null,
|
551 |
-
|
552 |
error: null,
|
553 |
isProcessing: false
|
554 |
};
|
555 |
}
|
556 |
</script>
|
557 |
|
558 |
-
<div class="
|
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 |
-
<
|
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 |
-
.
|
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 {
|
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:
|
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
|
23 |
$effect(() => {
|
24 |
-
if (workflowState.
|
25 |
try {
|
26 |
-
const
|
27 |
-
name: workflowState.
|
28 |
-
imageUrl: workflowState.
|
29 |
-
imageData: workflowState.
|
30 |
imageCaption: workflowState.imageCaption || '',
|
31 |
-
concept: workflowState.
|
32 |
imagePrompt: workflowState.imagePrompt || '',
|
33 |
-
stats: workflowState.
|
|
|
34 |
};
|
35 |
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 {
|
3 |
|
4 |
interface Props {
|
5 |
-
currentStep:
|
6 |
error?: string | null;
|
7 |
}
|
8 |
|
9 |
let { currentStep, error = null }: Props = $props();
|
10 |
|
11 |
interface StepInfo {
|
12 |
-
id:
|
13 |
label: string;
|
14 |
description: string;
|
15 |
}
|
@@ -22,7 +22,7 @@
|
|
22 |
},
|
23 |
{
|
24 |
id: 'captioning',
|
25 |
-
label: '
|
26 |
description: 'Creating concept & lore'
|
27 |
},
|
28 |
{
|
@@ -33,16 +33,16 @@
|
|
33 |
{
|
34 |
id: 'generating',
|
35 |
label: 'Image Generation',
|
36 |
-
description: 'Creating
|
37 |
},
|
38 |
{
|
39 |
id: 'complete',
|
40 |
label: 'Complete',
|
41 |
-
description: 'Your
|
42 |
}
|
43 |
];
|
44 |
|
45 |
-
function getStepIndex(step:
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
//
|
78 |
-
export type
|
79 |
| 'upload'
|
80 |
| 'captioning'
|
81 |
| 'conceptualizing'
|
@@ -84,26 +84,26 @@ export type MonsterWorkflowStep =
|
|
84 |
| 'generating'
|
85 |
| 'complete';
|
86 |
|
87 |
-
export interface
|
88 |
-
currentStep:
|
89 |
userImage: Blob | null;
|
90 |
imageCaption: string | null;
|
91 |
-
|
92 |
-
|
93 |
imagePrompt: string | null;
|
94 |
-
|
95 |
error: string | null;
|
96 |
isProcessing: boolean;
|
97 |
}
|
98 |
|
99 |
-
export interface
|
100 |
joyCaptionClient: GradioClient | null;
|
101 |
zephyrClient: GradioClient | null;
|
102 |
fluxClient: GradioClient | null;
|
103 |
}
|
104 |
|
105 |
-
//
|
106 |
-
export interface
|
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';
|