@zodic/shared 0.0.181 → 0.0.183
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DurableObjectState } from '@cloudflare/workers-types';
|
|
2
|
+
import { ConceptProgress } from '../../types';
|
|
2
3
|
|
|
3
4
|
export class ConceptNameDO {
|
|
4
5
|
state: DurableObjectState;
|
|
@@ -6,6 +7,7 @@ export class ConceptNameDO {
|
|
|
6
7
|
names: { 'en-us': string[]; 'pt-br': string[] };
|
|
7
8
|
count: number;
|
|
8
9
|
timestamps: number[];
|
|
10
|
+
lastIndex: number; // ✅ Stores last processed index for batch processing
|
|
9
11
|
|
|
10
12
|
static TOTAL_NAMES = 1728; // Maximum total names to be generated
|
|
11
13
|
|
|
@@ -15,6 +17,7 @@ export class ConceptNameDO {
|
|
|
15
17
|
this.names = { 'en-us': [], 'pt-br': [] };
|
|
16
18
|
this.count = 0;
|
|
17
19
|
this.timestamps = [];
|
|
20
|
+
this.lastIndex = 0; // ✅ Initialize lastIndex
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
async fetch(request: Request): Promise<Response> {
|
|
@@ -33,6 +36,12 @@ export class ConceptNameDO {
|
|
|
33
36
|
});
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
if (method === 'GET' && url.pathname === '/last-index') {
|
|
40
|
+
return new Response(JSON.stringify(await this.getLastIndex()), {
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
if (method === 'POST' && url.pathname === '/add-name') {
|
|
37
46
|
const { language, name } = (await request.json()) as {
|
|
38
47
|
language: 'en-us' | 'pt-br';
|
|
@@ -47,6 +56,12 @@ export class ConceptNameDO {
|
|
|
47
56
|
return new Response('OK', { status: 200 });
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
if (method === 'POST' && url.pathname === '/set-progress') {
|
|
60
|
+
const { lastIndex } = (await request.json()) as { lastIndex: number };
|
|
61
|
+
await this.setLastIndex(lastIndex);
|
|
62
|
+
return new Response('OK', { status: 200 });
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
return new Response('Not Found', { status: 404 });
|
|
51
66
|
}
|
|
52
67
|
|
|
@@ -60,28 +75,14 @@ export class ConceptNameDO {
|
|
|
60
75
|
return this.names;
|
|
61
76
|
}
|
|
62
77
|
|
|
63
|
-
async getProgress(): Promise<{
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
progress: string;
|
|
67
|
-
ratePerMinute: number;
|
|
68
|
-
estimatedTimeMinutes: number | 'N/A';
|
|
69
|
-
}> {
|
|
70
|
-
if (this.count === 0) {
|
|
71
|
-
this.count = (await this.state.storage.get<number>('count')) || 0;
|
|
72
|
-
}
|
|
73
|
-
if (!Array.isArray(this.timestamps) || this.timestamps.length === 0) {
|
|
74
|
-
this.timestamps =
|
|
75
|
-
(await this.state.storage.get<number[]>('timestamps')) || [];
|
|
76
|
-
}
|
|
78
|
+
async getProgress(): Promise<ConceptProgress> {
|
|
79
|
+
await this.loadCount();
|
|
80
|
+
await this.loadTimestamps();
|
|
77
81
|
|
|
78
82
|
const now = Date.now();
|
|
79
83
|
const oneMinuteAgo = now - 60 * 1000;
|
|
80
84
|
|
|
81
|
-
const recentTimestamps =
|
|
82
|
-
? this.timestamps.filter((ts) => ts >= oneMinuteAgo)
|
|
83
|
-
: []; // ✅ Ensure it's an array before calling filter
|
|
84
|
-
|
|
85
|
+
const recentTimestamps = this.timestamps.filter((ts) => ts >= oneMinuteAgo);
|
|
85
86
|
const ratePerMinute = recentTimestamps.length;
|
|
86
87
|
|
|
87
88
|
const remainingNames = ConceptNameDO.TOTAL_NAMES - this.count;
|
|
@@ -91,6 +92,7 @@ export class ConceptNameDO {
|
|
|
91
92
|
return {
|
|
92
93
|
count: this.count,
|
|
93
94
|
total: ConceptNameDO.TOTAL_NAMES,
|
|
95
|
+
lastIndex: this.lastIndex,
|
|
94
96
|
progress:
|
|
95
97
|
((this.count / ConceptNameDO.TOTAL_NAMES) * 100).toFixed(2) + '%',
|
|
96
98
|
ratePerMinute,
|
|
@@ -98,20 +100,25 @@ export class ConceptNameDO {
|
|
|
98
100
|
};
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
async getLastIndex(): Promise<number> {
|
|
104
|
+
if (this.lastIndex === 0) {
|
|
105
|
+
this.lastIndex = (await this.state.storage.get<number>('lastIndex')) || 0;
|
|
106
|
+
}
|
|
107
|
+
return this.lastIndex;
|
|
108
|
+
}
|
|
109
|
+
|
|
101
110
|
async addName(language: 'en-us' | 'pt-br', name: string): Promise<void> {
|
|
102
|
-
await this.getNames();
|
|
103
|
-
await this.loadCount();
|
|
111
|
+
await this.getNames();
|
|
112
|
+
await this.loadCount();
|
|
104
113
|
|
|
105
114
|
if (!this.names[language].includes(name)) {
|
|
106
115
|
this.names[language].push(name);
|
|
107
|
-
this.count += 1;
|
|
108
|
-
|
|
116
|
+
this.count += 1;
|
|
109
117
|
this.recordTimestamp();
|
|
110
118
|
|
|
111
|
-
// ✅ **Store all updated values in a single put() call**
|
|
112
119
|
await this.state.storage.put({
|
|
113
120
|
names: this.names,
|
|
114
|
-
count: this.count,
|
|
121
|
+
count: this.count,
|
|
115
122
|
timestamps: this.timestamps,
|
|
116
123
|
});
|
|
117
124
|
|
|
@@ -121,27 +128,23 @@ export class ConceptNameDO {
|
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
async setLastIndex(lastIndex: number): Promise<void> {
|
|
132
|
+
this.lastIndex = lastIndex;
|
|
133
|
+
await this.state.storage.put('lastIndex', this.lastIndex);
|
|
134
|
+
}
|
|
135
|
+
|
|
124
136
|
async loadCount(): Promise<void> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
137
|
+
this.count = (await this.state.storage.get<number>('count')) || 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async loadTimestamps(): Promise<void> {
|
|
141
|
+
this.timestamps =
|
|
142
|
+
(await this.state.storage.get<number[]>('timestamps')) || [];
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
recordTimestamp(): void {
|
|
134
146
|
const now = Date.now();
|
|
135
|
-
|
|
136
|
-
// ✅ Ensure timestamps is always an array before modifying
|
|
137
|
-
if (!Array.isArray(this.timestamps)) {
|
|
138
|
-
this.timestamps = [];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ✅ Keep only the last 60 timestamps
|
|
142
|
-
if (this.timestamps.length >= 60) {
|
|
143
|
-
this.timestamps.splice(0, this.timestamps.length - 59);
|
|
144
|
-
}
|
|
147
|
+
this.timestamps = this.timestamps.filter((ts) => now - ts < 60 * 60 * 1000);
|
|
145
148
|
this.timestamps.push(now);
|
|
146
149
|
}
|
|
147
150
|
}
|
|
@@ -28,51 +28,51 @@ export class ConceptService {
|
|
|
28
28
|
console.log(
|
|
29
29
|
`🚀 Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}, override: ${override}`
|
|
30
30
|
);
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
const kvStore = this.context.kvConceptsStore();
|
|
33
33
|
const kvFailuresStore = this.context.kvConceptFailuresStore();
|
|
34
34
|
const kvKeyEN = buildConceptKVKey('en-us', conceptSlug, combinationString);
|
|
35
35
|
const kvKeyPT = buildConceptKVKey('pt-br', conceptSlug, combinationString);
|
|
36
36
|
const failureKey = `failures:basic-info:${conceptSlug}:${combinationString}`;
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
// ✅ Use Durable Object stub
|
|
39
39
|
const id = this.context.env.CONCEPT_NAMES_DO.idFromName(conceptSlug);
|
|
40
40
|
const stub = this.context.env.CONCEPT_NAMES_DO.get(id);
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
let attempts = 0;
|
|
43
43
|
const maxAttempts = 3;
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
while (attempts < maxAttempts) {
|
|
46
46
|
let phase = 'generation';
|
|
47
47
|
try {
|
|
48
48
|
attempts++;
|
|
49
|
-
console.log(`🔄 Attempt ${attempts} to generate basic info...`);
|
|
50
|
-
|
|
49
|
+
console.log(`🔄 Attempt ${attempts} to generate basic info for ${conceptSlug}...`);
|
|
50
|
+
|
|
51
51
|
// ✅ Fetch the latest name list from Durable Object
|
|
52
|
+
console.log(`📡 Fetching names from Durable Object for ${conceptSlug}...`);
|
|
52
53
|
let allNamesEN: string[] = [];
|
|
53
54
|
let allNamesPT: string[] = [];
|
|
54
|
-
const response = await stub.fetch(
|
|
55
|
-
|
|
56
|
-
);
|
|
55
|
+
const response = await stub.fetch(`https://internal/names`);
|
|
56
|
+
|
|
57
57
|
if (response.ok) {
|
|
58
|
-
const data = (await response.json()) as {
|
|
59
|
-
'en-us': string[];
|
|
60
|
-
'pt-br': string[];
|
|
61
|
-
};
|
|
58
|
+
const data = (await response.json()) as { 'en-us': string[]; 'pt-br': string[] };
|
|
62
59
|
allNamesEN = data['en-us'] || [];
|
|
63
60
|
allNamesPT = data['pt-br'] || [];
|
|
61
|
+
console.log(`✅ Retrieved ${allNamesEN.length} EN names and ${allNamesPT.length} PT names.`);
|
|
64
62
|
} else {
|
|
65
|
-
console.log(
|
|
66
|
-
console.log('!-- Response:', await response.text());
|
|
63
|
+
console.log(`❌ Error fetching names from DO for ${conceptSlug}: ${await response.text()}`);
|
|
67
64
|
}
|
|
68
|
-
|
|
65
|
+
|
|
69
66
|
// ✅ Fetch existing KV data to backfill Durable Object if necessary
|
|
67
|
+
console.log(`📡 Fetching existing KV data for ${conceptSlug}...`);
|
|
70
68
|
const existingEN = await this.getKVConcept(kvKeyEN);
|
|
71
69
|
const existingPT = await this.getKVConcept(kvKeyPT);
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
|
|
71
|
+
console.log(`📝 Existing EN name: ${existingEN.name || 'None'}, PT name: ${existingPT.name || 'None'}`);
|
|
72
|
+
|
|
73
|
+
// 🔥 If names exist in KV but not in DO, add them
|
|
74
74
|
if (existingEN.name && !allNamesEN.includes(existingEN.name)) {
|
|
75
|
-
console.log(`⚡ Backfilling existing name to DO: ${existingEN.name}`);
|
|
75
|
+
console.log(`⚡ Backfilling existing EN name to DO: ${existingEN.name}`);
|
|
76
76
|
await stub.fetch(`https://internal/add-name`, {
|
|
77
77
|
method: 'POST',
|
|
78
78
|
body: JSON.stringify({ language: 'en-us', name: existingEN.name }),
|
|
@@ -80,23 +80,22 @@ export class ConceptService {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
if (existingPT.name && !allNamesPT.includes(existingPT.name)) {
|
|
83
|
-
console.log(`⚡ Backfilling existing name to DO: ${existingPT.name}`);
|
|
83
|
+
console.log(`⚡ Backfilling existing PT name to DO: ${existingPT.name}`);
|
|
84
84
|
await stub.fetch(`https://internal/add-name`, {
|
|
85
85
|
method: 'POST',
|
|
86
86
|
body: JSON.stringify({ language: 'pt-br', name: existingPT.name }),
|
|
87
87
|
headers: { 'Content-Type': 'application/json' },
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
// ✅ Skip generation if basic info already exists and override is false
|
|
92
92
|
if (!override && existingEN.name && existingPT.name) {
|
|
93
|
-
console.log(
|
|
94
|
-
`⚡ Basic info already exists for ${conceptSlug}, skipping.`
|
|
95
|
-
);
|
|
93
|
+
console.log(`⚡ Basic info already exists for ${conceptSlug}, skipping.`);
|
|
96
94
|
return;
|
|
97
95
|
}
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
|
|
97
|
+
console.log(`✏️ Generating new name...`);
|
|
98
|
+
const recentNamesEN = allNamesEN.slice(-60);
|
|
100
99
|
const messages = this.context
|
|
101
100
|
.buildLLMMessages()
|
|
102
101
|
.generateConceptBasicInfo({
|
|
@@ -104,43 +103,44 @@ export class ConceptService {
|
|
|
104
103
|
conceptSlug,
|
|
105
104
|
existingNames: recentNamesEN,
|
|
106
105
|
});
|
|
107
|
-
|
|
108
|
-
let aiResponse = await this.context
|
|
109
|
-
.api()
|
|
110
|
-
.callTogether.single(messages, {});
|
|
106
|
+
|
|
107
|
+
let aiResponse = await this.context.api().callTogether.single(messages, {});
|
|
111
108
|
if (!aiResponse) throw new Error(`❌ AI returned an empty response`);
|
|
112
|
-
|
|
109
|
+
|
|
113
110
|
phase = 'cleaning';
|
|
111
|
+
console.log(`🧼 Cleaning AI response...`);
|
|
114
112
|
aiResponse = this.cleanAIResponse(aiResponse);
|
|
115
|
-
|
|
113
|
+
|
|
116
114
|
phase = 'parsing';
|
|
115
|
+
console.log(`📜 Parsing AI response...`);
|
|
117
116
|
let { nameEN, descriptionEN, poemEN, namePT, descriptionPT, poemPT } =
|
|
118
117
|
this.parseBasicInfoResponse(aiResponse, conceptSlug);
|
|
119
|
-
|
|
118
|
+
|
|
119
|
+
console.log(`🎭 Generated names: EN - "${nameEN}", PT - "${namePT}"`);
|
|
120
|
+
|
|
120
121
|
// ✅ Check uniqueness before storing
|
|
121
122
|
if (allNamesEN.includes(nameEN) || allNamesPT.includes(namePT)) {
|
|
122
|
-
console.warn(
|
|
123
|
-
`⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`
|
|
124
|
-
);
|
|
123
|
+
console.warn(`⚠️ Duplicate Name Detected: "${nameEN}" or "${namePT}"`);
|
|
125
124
|
if (attempts >= maxAttempts)
|
|
126
125
|
throw new Error(`🚨 Could not generate a unique name`);
|
|
127
126
|
console.log('🔁 Retrying due to duplicate name...');
|
|
128
127
|
continue;
|
|
129
128
|
}
|
|
130
|
-
|
|
129
|
+
|
|
130
|
+
console.log(`📝 Storing names in Durable Object...`);
|
|
131
131
|
// ✅ **Immediately update Durable Object with the new name**
|
|
132
132
|
await stub.fetch(`https://internal/add-name`, {
|
|
133
133
|
method: 'POST',
|
|
134
134
|
body: JSON.stringify({ language: 'en-us', name: nameEN }),
|
|
135
135
|
headers: { 'Content-Type': 'application/json' },
|
|
136
136
|
});
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
await stub.fetch(`https://internal/add-name`, {
|
|
139
139
|
method: 'POST',
|
|
140
140
|
body: JSON.stringify({ language: 'pt-br', name: namePT }),
|
|
141
141
|
headers: { 'Content-Type': 'application/json' },
|
|
142
142
|
});
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
// ✅ Store the generated basic info in KV
|
|
145
145
|
Object.assign(existingEN, {
|
|
146
146
|
name: nameEN,
|
|
@@ -149,7 +149,7 @@ export class ConceptService {
|
|
|
149
149
|
status: 'idle',
|
|
150
150
|
});
|
|
151
151
|
await kvStore.put(kvKeyEN, JSON.stringify(existingEN));
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
Object.assign(existingPT, {
|
|
154
154
|
name: namePT,
|
|
155
155
|
description: descriptionPT,
|
|
@@ -157,17 +157,12 @@ export class ConceptService {
|
|
|
157
157
|
status: 'idle',
|
|
158
158
|
});
|
|
159
159
|
await kvStore.put(kvKeyPT, JSON.stringify(existingPT));
|
|
160
|
-
|
|
161
|
-
console.log(
|
|
162
|
-
`✅ Stored basic info for ${conceptSlug}, combination: ${combinationString}.`
|
|
163
|
-
);
|
|
160
|
+
|
|
161
|
+
console.log(`✅ Stored basic info for ${conceptSlug}, combination: ${combinationString}.`);
|
|
164
162
|
return;
|
|
165
163
|
} catch (error) {
|
|
166
|
-
console.error(
|
|
167
|
-
|
|
168
|
-
(error as Error).message
|
|
169
|
-
);
|
|
170
|
-
|
|
164
|
+
console.error(`❌ Attempt ${attempts} failed at phase: ${phase}`, (error as Error).message);
|
|
165
|
+
|
|
171
166
|
// ✅ Store failure details in KV for debugging
|
|
172
167
|
await kvFailuresStore.put(
|
|
173
168
|
failureKey,
|
|
@@ -180,12 +175,10 @@ export class ConceptService {
|
|
|
180
175
|
timestamp: new Date().toISOString(),
|
|
181
176
|
})
|
|
182
177
|
);
|
|
183
|
-
|
|
178
|
+
|
|
184
179
|
if (attempts >= maxAttempts) {
|
|
185
180
|
console.error(`🚨 All ${maxAttempts} attempts failed.`);
|
|
186
|
-
throw new Error(
|
|
187
|
-
`Failed to generate basic info after ${maxAttempts} attempts.`
|
|
188
|
-
);
|
|
181
|
+
throw new Error(`Failed to generate basic info after ${maxAttempts} attempts.`);
|
|
189
182
|
}
|
|
190
183
|
console.log('🔁 Retrying...');
|
|
191
184
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { inject, injectable } from 'inversify';
|
|
2
2
|
import pMap from 'p-map';
|
|
3
3
|
import { zodiacSignCombinations } from '../../data/zodiacSignCombinations';
|
|
4
|
-
import { Concept, ConceptPhase } from '../../types';
|
|
4
|
+
import { Concept, ConceptPhase, ConceptProgress } from '../../types';
|
|
5
|
+
import { AppContext } from '../base';
|
|
5
6
|
import { ConceptService } from '../services/ConceptService';
|
|
6
7
|
|
|
7
8
|
@injectable()
|
|
8
9
|
export class ConceptWorkflow {
|
|
9
|
-
constructor(
|
|
10
|
+
constructor(
|
|
11
|
+
@inject(AppContext) private context: AppContext,
|
|
12
|
+
@inject(ConceptService) private conceptService: ConceptService
|
|
13
|
+
) {}
|
|
10
14
|
|
|
11
15
|
async processSingle(
|
|
12
16
|
conceptSlug: Concept,
|
|
@@ -66,34 +70,79 @@ export class ConceptWorkflow {
|
|
|
66
70
|
conceptSlug: Concept,
|
|
67
71
|
combinations: string[],
|
|
68
72
|
phase: ConceptPhase,
|
|
69
|
-
override
|
|
73
|
+
override: boolean = false
|
|
70
74
|
) {
|
|
71
75
|
console.log(
|
|
72
76
|
`🚀 Processing batch for concept: ${conceptSlug}, Phase: ${phase}, Total Combinations: ${combinations.length}`
|
|
73
77
|
);
|
|
74
78
|
|
|
75
|
-
const concurrency = 20;
|
|
79
|
+
const concurrency = 20;
|
|
76
80
|
const failedCombinations: string[] = [];
|
|
77
81
|
|
|
82
|
+
// ✅ Get Durable Object stub for progress tracking
|
|
83
|
+
const id = this.context.env.CONCEPT_NAMES_DO.idFromName(conceptSlug);
|
|
84
|
+
const stub = this.context.env.CONCEPT_NAMES_DO.get(id);
|
|
85
|
+
|
|
86
|
+
// ✅ Try to load last checkpoint from DO
|
|
87
|
+
let lastIndex = 0;
|
|
88
|
+
try {
|
|
89
|
+
const progressRes = await stub.fetch(`https://internal/progress`);
|
|
90
|
+
if (progressRes.ok) {
|
|
91
|
+
const progressData = (await progressRes.json()) as ConceptProgress;
|
|
92
|
+
lastIndex = progressData.lastIndex || 0;
|
|
93
|
+
console.log(`🔄 Resuming from index ${lastIndex}`);
|
|
94
|
+
} else {
|
|
95
|
+
console.log(`📌 No previous checkpoint found, starting from scratch.`);
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(`❌ Error fetching progress from DO:`, error);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ✅ Filter unprocessed combinations
|
|
102
|
+
const remainingCombinations = combinations.slice(lastIndex);
|
|
103
|
+
console.log(
|
|
104
|
+
`⏳ Processing ${remainingCombinations.length} remaining combinations...`
|
|
105
|
+
);
|
|
106
|
+
|
|
78
107
|
await pMap(
|
|
79
|
-
|
|
80
|
-
async (combination) => {
|
|
108
|
+
remainingCombinations,
|
|
109
|
+
async (combination, index) => {
|
|
110
|
+
const currentIndex = lastIndex + index;
|
|
81
111
|
try {
|
|
112
|
+
console.log(
|
|
113
|
+
`🛠️ Processing combination ${currentIndex}/${combinations.length}: ${combination}`
|
|
114
|
+
);
|
|
82
115
|
await this.processSingle(conceptSlug, combination, phase, override);
|
|
116
|
+
|
|
117
|
+
// ✅ Save progress every 10 iterations in DO
|
|
118
|
+
if (currentIndex % 10 === 0) {
|
|
119
|
+
await stub.fetch(`https://internal/set-progress`, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
body: JSON.stringify({ lastIndex: currentIndex }),
|
|
122
|
+
headers: { 'Content-Type': 'application/json' },
|
|
123
|
+
});
|
|
124
|
+
console.log(`✅ Checkpoint saved at index ${currentIndex}`);
|
|
125
|
+
}
|
|
83
126
|
} catch (error) {
|
|
84
127
|
console.error(`❌ Error processing ${combination}:`, error);
|
|
85
|
-
failedCombinations.push(combination);
|
|
128
|
+
failedCombinations.push(combination);
|
|
86
129
|
}
|
|
87
130
|
},
|
|
88
131
|
{ concurrency }
|
|
89
132
|
);
|
|
90
133
|
|
|
134
|
+
console.log(
|
|
135
|
+
`✅ Batch processing completed for ${conceptSlug}, Phase: ${phase}`
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// ✅ Retry failed combinations after all batches
|
|
91
139
|
if (failedCombinations.length > 0) {
|
|
92
140
|
console.warn(`⚠️ Retrying failed combinations:`, failedCombinations);
|
|
93
|
-
await this.processBatch(conceptSlug, failedCombinations, phase, override);
|
|
141
|
+
await this.processBatch(conceptSlug, failedCombinations, phase, override);
|
|
94
142
|
}
|
|
143
|
+
|
|
95
144
|
console.log(
|
|
96
|
-
|
|
145
|
+
`🎉 All combinations successfully processed for ${conceptSlug}!`
|
|
97
146
|
);
|
|
98
147
|
}
|
|
99
148
|
|
package/package.json
CHANGED
package/types/scopes/generic.ts
CHANGED
|
@@ -402,6 +402,7 @@ export type AstroKVData = {
|
|
|
402
402
|
export type ConceptProgress = {
|
|
403
403
|
count: number;
|
|
404
404
|
total: number;
|
|
405
|
+
lastIndex: number;
|
|
405
406
|
progress: string; // Percentage as string (e.g., "75.32%")
|
|
406
407
|
ratePerMinute: number;
|
|
407
408
|
estimatedTimeMinutes: number | 'N/A';
|