Fraser commited on
Commit
de7bb17
·
1 Parent(s): 5f5a552
src/lib/components/AutoTrainerScanner/AutoTrainerScanner.svelte CHANGED
@@ -1,7 +1,13 @@
1
  <script lang="ts">
2
  import type { GradioClient } from '$lib/types';
3
- import { TrainerScanService, type TrainerScanState } from '$lib/services/trainerScanService';
4
- import { resetFailedScans } from '$lib/db/trainerScanning';
 
 
 
 
 
 
5
 
6
  interface Props {
7
  joyCaptionClient: GradioClient;
@@ -11,45 +17,53 @@
11
 
12
  let { joyCaptionClient, zephyrClient, fluxClient }: Props = $props();
13
 
14
- // Scanner service and state
15
- let scanService: TrainerScanService | null = null;
16
- let scanState: TrainerScanState = $state({
17
  isScanning: false,
18
- currentImage: null,
19
- currentTrainer: null,
20
  progress: {
21
  total: 0,
22
  completed: 0,
23
  failed: 0,
24
  pending: 0
25
  },
26
- error: null
27
  });
28
 
29
  let showDetails = $state(false);
30
  let isInitializing = $state(false);
 
31
 
32
- // Initialize scan service when clients are available
 
 
 
33
  $effect(() => {
34
- if (joyCaptionClient && zephyrClient && fluxClient && !scanService) {
35
- scanService = new TrainerScanService(joyCaptionClient, zephyrClient, fluxClient);
36
-
37
- // Subscribe to state changes
38
- scanService.onStateChange((newState) => {
39
- scanState = newState;
40
- });
41
-
42
- // Load initial state
43
  loadInitialState();
44
  }
45
  });
46
 
47
  async function loadInitialState() {
48
- if (!scanService) return;
49
-
50
  try {
51
  isInitializing = true;
52
- await scanService.initializeFromFile();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  } catch (error) {
54
  console.error('Failed to initialize scanner:', error);
55
  scanState.error = error instanceof Error ? error.message : 'Failed to initialize';
@@ -58,37 +72,102 @@
58
  }
59
  }
60
 
 
 
 
 
 
 
 
 
 
 
61
  async function startScanning() {
62
- if (!scanService) return;
 
 
 
 
63
 
64
  try {
65
- await scanService.startScanning();
66
  } catch (error) {
67
- console.error('Failed to start scanning:', error);
68
- scanState.error = error instanceof Error ? error.message : 'Failed to start scanning';
 
 
 
 
69
  }
70
  }
71
 
72
  function stopScanning() {
73
- if (!scanService) return;
74
- scanService.stopScanning();
75
  }
76
 
77
- async function retryFailedScans() {
78
- try {
79
- const resetCount = await resetFailedScans();
80
- console.log(`Reset ${resetCount} failed scans to pending`);
81
 
82
- // Refresh state
83
- if (scanService) {
84
- await scanService.initializeFromFile();
85
  }
86
- } catch (error) {
87
- console.error('Failed to retry failed scans:', error);
88
- scanState.error = error instanceof Error ? error.message : 'Failed to retry failed scans';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
  }
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  function formatImageName(imagePath: string | null): string {
93
  if (!imagePath) return '';
94
  const parts = imagePath.split('/');
@@ -168,12 +247,6 @@
168
  >
169
  ▶️ Start Auto Scan
170
  </button>
171
-
172
- {#if scanState.progress.failed > 0}
173
- <button class="retry-button" onclick={retryFailedScans}>
174
- 🔄 Retry Failed ({scanState.progress.failed})
175
- </button>
176
- {/if}
177
  </div>
178
  {/if}
179
 
@@ -219,6 +292,20 @@
219
  {/if}
220
  </div>
221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  <style>
223
  .auto-trainer-scanner {
224
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@@ -338,7 +425,7 @@
338
  margin-bottom: 1rem;
339
  }
340
 
341
- .start-button, .stop-button, .retry-button {
342
  padding: 0.8rem 1.2rem;
343
  border: none;
344
  border-radius: 8px;
@@ -373,15 +460,6 @@
373
  box-shadow: 0 4px 8px rgba(255, 107, 107, 0.3);
374
  }
375
 
376
- .retry-button {
377
- background: linear-gradient(135deg, #ffa726 0%, #ff7043 100%);
378
- color: white;
379
- }
380
-
381
- .retry-button:hover {
382
- transform: translateY(-1px);
383
- box-shadow: 0 4px 8px rgba(255, 167, 38, 0.3);
384
- }
385
 
386
  .progress-details {
387
  margin-bottom: 1rem;
 
1
  <script lang="ts">
2
  import type { GradioClient } from '$lib/types';
3
+ import {
4
+ initializeTrainerScanProgress,
5
+ getNextPendingImage,
6
+ markImageProcessingCompleted,
7
+ markImageProcessingFailed,
8
+ getScanningStats
9
+ } from '$lib/db/trainerScanning';
10
+ import PicletGenerator from '$lib/components/PicletGenerator/PicletGenerator.svelte';
11
 
12
  interface Props {
13
  joyCaptionClient: GradioClient;
 
17
 
18
  let { joyCaptionClient, zephyrClient, fluxClient }: Props = $props();
19
 
20
+ // Scanner state
21
+ let scanState = $state({
 
22
  isScanning: false,
23
+ currentImage: null as string | null,
24
+ currentTrainer: null as string | null,
25
  progress: {
26
  total: 0,
27
  completed: 0,
28
  failed: 0,
29
  pending: 0
30
  },
31
+ error: null as string | null
32
  });
33
 
34
  let showDetails = $state(false);
35
  let isInitializing = $state(false);
36
+ let shouldStop = $state(false);
37
 
38
+ // Reference to PicletGenerator component
39
+ let picletGenerator: any;
40
+
41
+ // Initialize trainer paths on component mount
42
  $effect(() => {
43
+ if (joyCaptionClient && zephyrClient && fluxClient) {
 
 
 
 
 
 
 
 
44
  loadInitialState();
45
  }
46
  });
47
 
48
  async function loadInitialState() {
 
 
49
  try {
50
  isInitializing = true;
51
+
52
+ // Load trainer image paths and initialize database
53
+ const response = await fetch('/trainer_image_paths.txt');
54
+ if (!response.ok) {
55
+ throw new Error(`Failed to fetch trainer_image_paths.txt: ${response.statusText}`);
56
+ }
57
+
58
+ const content = await response.text();
59
+ const imagePaths = content.trim().split('\n')
60
+ .map(path => typeof path === 'string' ? path.trim() : '')
61
+ .filter(path => path.length > 0);
62
+
63
+ console.log(`Loaded ${imagePaths.length} trainer image paths`);
64
+
65
+ await initializeTrainerScanProgress(imagePaths);
66
+ await updateProgress();
67
  } catch (error) {
68
  console.error('Failed to initialize scanner:', error);
69
  scanState.error = error instanceof Error ? error.message : 'Failed to initialize';
 
72
  }
73
  }
74
 
75
+ async function updateProgress() {
76
+ const stats = await getScanningStats();
77
+ scanState.progress = {
78
+ total: stats.total,
79
+ completed: stats.completed,
80
+ failed: stats.failed,
81
+ pending: stats.pending
82
+ };
83
+ }
84
+
85
  async function startScanning() {
86
+ if (scanState.isScanning) return;
87
+
88
+ scanState.isScanning = true;
89
+ scanState.error = null;
90
+ shouldStop = false;
91
 
92
  try {
93
+ await processTrainerImages();
94
  } catch (error) {
95
+ console.error('Scanning error:', error);
96
+ scanState.error = error instanceof Error ? error.message : 'Unknown error';
97
+ } finally {
98
+ scanState.isScanning = false;
99
+ scanState.currentImage = null;
100
+ scanState.currentTrainer = null;
101
  }
102
  }
103
 
104
  function stopScanning() {
105
+ shouldStop = true;
 
106
  }
107
 
108
+ async function processTrainerImages() {
109
+ while (!shouldStop) {
110
+ const nextImage = await getNextPendingImage();
 
111
 
112
+ if (!nextImage) {
113
+ // No more pending images
114
+ break;
115
  }
116
+
117
+ scanState.currentImage = nextImage.imagePath;
118
+ scanState.currentTrainer = nextImage.trainerName;
119
+
120
+ try {
121
+ // Fetch remote image
122
+ const imageFile = await fetchRemoteImage(nextImage.remoteUrl, nextImage.imagePath);
123
+
124
+ // Queue the image in PicletGenerator
125
+ if (picletGenerator) {
126
+ picletGenerator.queueTrainerImage(imageFile, nextImage.imagePath);
127
+ }
128
+
129
+ // Wait for this image to be processed before continuing
130
+ // (The onTrainerImageCompleted callback will handle the database update)
131
+
132
+ } catch (error) {
133
+ console.error(`Failed to process ${nextImage.imagePath}:`, error);
134
+ await markImageProcessingFailed(
135
+ nextImage.imagePath,
136
+ error instanceof Error ? error.message : 'Unknown error'
137
+ );
138
+ }
139
+
140
+ await updateProgress();
141
+
142
+ // Small delay between images
143
+ await new Promise(resolve => setTimeout(resolve, 1000));
144
  }
145
  }
146
 
147
+ async function fetchRemoteImage(remoteUrl: string, originalPath: string): Promise<File> {
148
+ const response = await fetch(remoteUrl);
149
+ if (!response.ok) {
150
+ throw new Error(`Failed to fetch ${remoteUrl}: ${response.statusText}`);
151
+ }
152
+
153
+ const blob = await response.blob();
154
+ const fileName = originalPath.split('/').pop() || 'trainer_image.jpg';
155
+
156
+ return new File([blob], fileName, { type: blob.type });
157
+ }
158
+
159
+ async function onTrainerImageCompleted(imagePath: string, picletId: number) {
160
+ console.log(`✅ Trainer image completed: ${imagePath} -> Piclet ID: ${picletId}`);
161
+ await markImageProcessingCompleted(imagePath, picletId);
162
+ await updateProgress();
163
+ }
164
+
165
+ async function onTrainerImageFailed(imagePath: string, error: string) {
166
+ console.error(`❌ Trainer image failed: ${imagePath} -> ${error}`);
167
+ await markImageProcessingFailed(imagePath, error);
168
+ await updateProgress();
169
+ }
170
+
171
  function formatImageName(imagePath: string | null): string {
172
  if (!imagePath) return '';
173
  const parts = imagePath.split('/');
 
247
  >
248
  ▶️ Start Auto Scan
249
  </button>
 
 
 
 
 
 
250
  </div>
251
  {/if}
252
 
 
292
  {/if}
293
  </div>
294
 
295
+ <!-- Hidden PicletGenerator for trainer mode processing -->
296
+ <div style="display: none;">
297
+ <PicletGenerator
298
+ bind:this={picletGenerator}
299
+ {joyCaptionClient}
300
+ {zephyrClient}
301
+ {fluxClient}
302
+ qwenClient={zephyrClient}
303
+ isTrainerMode={true}
304
+ onTrainerImageCompleted={onTrainerImageCompleted}
305
+ onTrainerImageFailed={onTrainerImageFailed}
306
+ />
307
+ </div>
308
+
309
  <style>
310
  .auto-trainer-scanner {
311
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 
425
  margin-bottom: 1rem;
426
  }
427
 
428
+ .start-button, .stop-button {
429
  padding: 0.8rem 1.2rem;
430
  border: none;
431
  border-radius: 8px;
 
460
  box-shadow: 0 4px 8px rgba(255, 107, 107, 0.3);
461
  }
462
 
 
 
 
 
 
 
 
 
 
463
 
464
  .progress-details {
465
  margin-bottom: 1rem;
src/lib/components/PicletGenerator/PicletGenerator.svelte CHANGED
@@ -11,9 +11,22 @@
11
  import { PicletType, TYPE_DATA } from '$lib/types/picletTypes';
12
  import { EncounterService } from '$lib/db/encounterService';
13
 
14
- interface Props extends PicletGeneratorProps {}
 
 
 
 
 
15
 
16
- let { joyCaptionClient, zephyrClient, fluxClient, qwenClient }: Props = $props();
 
 
 
 
 
 
 
 
17
 
18
  let state: PicletWorkflowState = $state({
19
  currentStep: 'upload',
@@ -31,6 +44,9 @@
31
  let imageQueue: File[] = $state([]);
32
  let currentImageIndex: number = $state(0);
33
 
 
 
 
34
  const IMAGE_GENERATION_PROMPT = (concept: string) => `Extract ONLY the visual appearance from this monster concept and describe it in one concise sentence:
35
  "${concept}"
36
 
@@ -959,6 +975,11 @@ Write your response within \`\`\`json\`\`\``;
959
  const picletInstance = await generatedDataToPicletInstance(picletData);
960
  const picletId = await savePicletInstance(picletInstance);
961
  console.log('Piclet auto-saved as uncaught with ID:', picletId);
 
 
 
 
 
962
  } catch (err) {
963
  console.error('Failed to auto-save piclet:', err);
964
  console.error('Piclet data that failed to save:', {
@@ -967,6 +988,13 @@ Write your response within \`\`\`json\`\`\``;
967
  hasImageData: !!state.picletImage?.imageData,
968
  hasStats: !!state.picletStats
969
  });
 
 
 
 
 
 
 
970
  // Don't throw - we don't want to interrupt the workflow
971
  }
972
  }
@@ -984,6 +1012,17 @@ Write your response within \`\`\`json\`\`\``;
984
  isProcessing: false
985
  };
986
  }
 
 
 
 
 
 
 
 
 
 
 
987
  </script>
988
 
989
  <div class="piclet-generator">
 
11
  import { PicletType, TYPE_DATA } from '$lib/types/picletTypes';
12
  import { EncounterService } from '$lib/db/encounterService';
13
 
14
+ interface Props extends PicletGeneratorProps {
15
+ // Trainer mode props
16
+ isTrainerMode?: boolean;
17
+ onTrainerImageCompleted?: (imagePath: string, picletId: number) => void;
18
+ onTrainerImageFailed?: (imagePath: string, error: string) => void;
19
+ }
20
 
21
+ let {
22
+ joyCaptionClient,
23
+ zephyrClient,
24
+ fluxClient,
25
+ qwenClient,
26
+ isTrainerMode = false,
27
+ onTrainerImageCompleted,
28
+ onTrainerImageFailed
29
+ }: Props = $props();
30
 
31
  let state: PicletWorkflowState = $state({
32
  currentStep: 'upload',
 
44
  let imageQueue: File[] = $state([]);
45
  let currentImageIndex: number = $state(0);
46
 
47
+ // Track trainer image metadata when in trainer mode
48
+ let trainerImagePaths: string[] = $state([]);
49
+
50
  const IMAGE_GENERATION_PROMPT = (concept: string) => `Extract ONLY the visual appearance from this monster concept and describe it in one concise sentence:
51
  "${concept}"
52
 
 
975
  const picletInstance = await generatedDataToPicletInstance(picletData);
976
  const picletId = await savePicletInstance(picletInstance);
977
  console.log('Piclet auto-saved as uncaught with ID:', picletId);
978
+
979
+ // If in trainer mode, notify completion
980
+ if (isTrainerMode && onTrainerImageCompleted && trainerImagePaths[currentImageIndex]) {
981
+ onTrainerImageCompleted(trainerImagePaths[currentImageIndex], picletId);
982
+ }
983
  } catch (err) {
984
  console.error('Failed to auto-save piclet:', err);
985
  console.error('Piclet data that failed to save:', {
 
988
  hasImageData: !!state.picletImage?.imageData,
989
  hasStats: !!state.picletStats
990
  });
991
+
992
+ // If in trainer mode, notify failure
993
+ if (isTrainerMode && onTrainerImageFailed && trainerImagePaths[currentImageIndex]) {
994
+ const errorMessage = err instanceof Error ? err.message : 'Failed to save piclet';
995
+ onTrainerImageFailed(trainerImagePaths[currentImageIndex], errorMessage);
996
+ }
997
+
998
  // Don't throw - we don't want to interrupt the workflow
999
  }
1000
  }
 
1012
  isProcessing: false
1013
  };
1014
  }
1015
+
1016
+ // Public method for trainer scanner to queue trainer images
1017
+ export function queueTrainerImage(imageFile: File, imagePath: string) {
1018
+ imageQueue.push(imageFile);
1019
+ trainerImagePaths.push(imagePath);
1020
+
1021
+ // If this is the first image and we're not processing, start processing
1022
+ if (imageQueue.length === 1 && !state.isProcessing) {
1023
+ processCurrentImage();
1024
+ }
1025
+ }
1026
  </script>
1027
 
1028
  <div class="piclet-generator">
src/lib/services/trainerScanService.ts CHANGED
@@ -8,10 +8,6 @@ import {
8
  getScanningStats,
9
  getCurrentProcessingImage
10
  } from '$lib/db/trainerScanning';
11
- import { savePicletInstance, generatedDataToPicletInstance } from '$lib/db/piclets';
12
- import { extractPicletMetadata } from './picletMetadata';
13
- import { removeBackground } from '$lib/utils/professionalImageProcessing';
14
- import type { PicletStats } from '$lib/types';
15
 
16
  export interface TrainerScanState {
17
  isScanning: boolean;
@@ -211,63 +207,10 @@ export class TrainerScanService {
211
  }
212
  }
213
 
214
- // Process a single remote image
 
215
  private async processImage(imagePath: string, remoteUrl: string): Promise<void> {
216
- await markImageProcessingStarted(imagePath);
217
-
218
- try {
219
- console.log(`🔄 Processing ${imagePath}: Fetching remote image...`);
220
- // Fetch remote image
221
- const imageFile = await this.fetchRemoteImage(remoteUrl, imagePath);
222
-
223
- console.log(`🔄 Processing ${imagePath}: Captioning image...`);
224
- // Caption the image
225
- const imageCaption = await this.captionImage(imageFile);
226
-
227
- console.log(`🔄 Processing ${imagePath}: Generating concept...`);
228
- // Generate monster concept
229
- const picletConcept = await this.generatePicletConcept(imageCaption);
230
-
231
- console.log(`🔄 Processing ${imagePath}: Generating stats...`);
232
- // Generate stats
233
- const picletStats = await this.generatePicletStats(picletConcept);
234
-
235
- console.log(`🔄 Processing ${imagePath}: Generating image prompt...`);
236
- // Generate image prompt
237
- const imagePrompt = await this.generateImagePrompt(picletConcept);
238
-
239
- console.log(`🔄 Processing ${imagePath}: Generating monster image...`);
240
- // Generate monster image
241
- const picletImageUrl = await this.generateMonsterImage(imagePrompt);
242
-
243
- console.log(`🔄 Processing ${imagePath}: Processing generated image...`);
244
- // Process generated image (remove background)
245
- const imageData = await this.processGeneratedImage(picletImageUrl);
246
-
247
- console.log(`🔄 Processing ${imagePath}: Creating piclet instance...`);
248
- // Create piclet instance
249
- const generatedData = {
250
- name: this.extractNameFromConcept(picletConcept),
251
- imageUrl: picletImageUrl,
252
- imageData,
253
- imageCaption,
254
- concept: picletConcept,
255
- imagePrompt,
256
- stats: picletStats,
257
- createdAt: new Date()
258
- };
259
-
260
- const picletInstance = await generatedDataToPicletInstance(generatedData, 5);
261
- const savedId = await savePicletInstance(picletInstance);
262
-
263
- await markImageProcessingCompleted(imagePath, savedId);
264
-
265
- } catch (error) {
266
- // Add context about which step failed
267
- const enhancedError = new Error(`Failed during processing of ${imagePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
268
- enhancedError.cause = error;
269
- throw enhancedError;
270
- }
271
  }
272
 
273
  // Fetch remote image and convert to File
@@ -283,128 +226,4 @@ export class TrainerScanService {
283
  return new File([blob], fileName, { type: blob.type });
284
  }
285
 
286
- // Caption image using Joy Caption
287
- private async captionImage(imageFile: File): Promise<string> {
288
- const result = await this.joyCaptionClient.predict("/caption", [imageFile, "descriptive", "any", false]);
289
- const captionResult = result.data[0] as string;
290
-
291
- if (!captionResult || captionResult.trim() === '') {
292
- throw new Error('Failed to generate image caption');
293
- }
294
-
295
- return captionResult.trim();
296
- }
297
-
298
- // Generate piclet concept using Zephyr
299
- private async generatePicletConcept(imageCaption: string): Promise<string> {
300
- const prompt = `Based on this image description, create a unique creature concept for a Pokemon-style monster collection game called "Pictuary":
301
-
302
- "${imageCaption}"
303
-
304
- Create a creative, original monster concept that:
305
- 1. Is inspired by elements from the image but is clearly a fantastical creature
306
- 2. Has a unique name and personality
307
- 3. Includes special abilities related to its appearance
308
- 4. Is suitable for a family-friendly game
309
-
310
- Write a detailed monster concept (2-3 paragraphs).`;
311
-
312
- const result = await this.zephyrClient.predict("/chat", [
313
- [["user", prompt]],
314
- 512, // max tokens
315
- 0.7, // temperature
316
- 0.9, // top_p
317
- ]);
318
-
319
- const conceptResult = result.data[0][1][1] as string;
320
-
321
- if (!conceptResult || conceptResult.trim() === '') {
322
- throw new Error('Failed to generate piclet concept');
323
- }
324
-
325
- return conceptResult.trim();
326
- }
327
-
328
- // Generate piclet stats
329
- private async generatePicletStats(concept: string): Promise<PicletStats> {
330
- return await extractPicletMetadata(concept, this.zephyrClient);
331
- }
332
-
333
- // Generate image prompt for monster creation
334
- private async generateImagePrompt(concept: string): Promise<string> {
335
- const prompt = `Extract ONLY the visual appearance from this monster concept and describe it in one concise sentence:
336
- "${concept}"
337
-
338
- Focus on: colors, body shape, eyes, limbs, mouth, and key visual features. Omit backstory, abilities, and non-visual details.`;
339
-
340
- const result = await this.zephyrClient.predict("/chat", [
341
- [["user", prompt]],
342
- 256, // max tokens
343
- 0.5, // temperature
344
- 0.9, // top_p
345
- ]);
346
-
347
- const promptResult = result.data[0][1][1] as string;
348
-
349
- if (!promptResult || promptResult.trim() === '') {
350
- throw new Error('Failed to generate image prompt');
351
- }
352
-
353
- return promptResult.trim();
354
- }
355
-
356
- // Generate monster image using Flux
357
- private async generateMonsterImage(imagePrompt: string): Promise<string> {
358
- const fullPrompt = `${imagePrompt}, digital art, creature design, fantasy monster, clean background, professional illustration`;
359
-
360
- const result = await this.fluxClient.predict("/infer", [
361
- fullPrompt,
362
- "", // negative prompt
363
- 832, // width
364
- 1216, // height
365
- 1, // num inference steps
366
- 3.0, // guidance scale
367
- 0, // seed
368
- ]);
369
-
370
- const imageUrl = result.data[0] as string;
371
-
372
- if (!imageUrl) {
373
- throw new Error('Failed to generate monster image');
374
- }
375
-
376
- return imageUrl;
377
- }
378
-
379
- // Process generated image (remove background)
380
- private async processGeneratedImage(imageUrl: string): Promise<string> {
381
- try {
382
- return await removeBackground(imageUrl);
383
- } catch (error) {
384
- console.warn('Background removal failed, using original image:', error);
385
- return imageUrl;
386
- }
387
- }
388
-
389
- // Extract name from concept text
390
- private extractNameFromConcept(concept: string): string {
391
- // Try to find a name in common patterns
392
- const patterns = [
393
- /(?:called|named)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)/,
394
- /^([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)/,
395
- /this\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)/,
396
- ];
397
-
398
- for (const pattern of patterns) {
399
- const match = concept.match(pattern);
400
- if (match && match[1]) {
401
- return match[1].trim();
402
- }
403
- }
404
-
405
- // Fallback to generating a random trainer-inspired name
406
- const trainerNames = ['Snap', 'Blaze', 'Nimbus', 'Breaker', 'Trinket'];
407
- const randomName = trainerNames[Math.floor(Math.random() * trainerNames.length)];
408
- return `Trainer${randomName}`;
409
- }
410
  }
 
8
  getScanningStats,
9
  getCurrentProcessingImage
10
  } from '$lib/db/trainerScanning';
 
 
 
 
11
 
12
  export interface TrainerScanState {
13
  isScanning: boolean;
 
207
  }
208
  }
209
 
210
+ // DEPRECATED: This service is no longer used
211
+ // The AutoTrainerScanner now directly uses PicletGenerator component
212
  private async processImage(imagePath: string, remoteUrl: string): Promise<void> {
213
+ throw new Error('TrainerScanService is deprecated - use PicletGenerator directly');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  }
215
 
216
  // Fetch remote image and convert to File
 
226
  return new File([blob], fileName, { type: blob.type });
227
  }
228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  }