@zodic/shared 0.0.175 → 0.0.177
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,69 +1,135 @@
|
|
|
1
|
-
import { DurableObjectState } from
|
|
1
|
+
import { DurableObjectState } from "@cloudflare/workers-types";
|
|
2
|
+
import { BackendBindings, ConceptProgress } from "../../types";
|
|
2
3
|
|
|
3
|
-
export class ConceptNameDO
|
|
4
|
+
export class ConceptNameDO {
|
|
4
5
|
state: DurableObjectState;
|
|
5
6
|
env: any;
|
|
6
|
-
names: { 'en-us': string[]; 'pt-br': string[] }
|
|
7
|
+
names: Record<string, { 'en-us': string[]; 'pt-br': string[] }>;
|
|
8
|
+
counts: Record<string, number>;
|
|
9
|
+
timestamps: Record<string, number[]>;
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
static TOTAL_NAMES = 1728; // Max names per concept
|
|
12
|
+
|
|
13
|
+
constructor(state: DurableObjectState, env: BackendBindings) {
|
|
10
14
|
this.state = state;
|
|
11
15
|
this.env = env;
|
|
12
|
-
this.names = {
|
|
16
|
+
this.names = {};
|
|
17
|
+
this.counts = {};
|
|
18
|
+
this.timestamps = {};
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
async fetch(request: Request): Promise<Response> {
|
|
16
22
|
const url = new URL(request.url);
|
|
17
23
|
const method = request.method;
|
|
24
|
+
const conceptSlug = url.pathname.split('/')[2]; // Extract conceptSlug from URL
|
|
25
|
+
|
|
26
|
+
if (!conceptSlug) {
|
|
27
|
+
return new Response('Concept slug required', { status: 400 });
|
|
28
|
+
}
|
|
18
29
|
|
|
19
|
-
if (method === 'GET' && url.pathname
|
|
20
|
-
await this.
|
|
21
|
-
return new Response(JSON.stringify(this.names), {
|
|
30
|
+
if (method === 'GET' && url.pathname.endsWith('/names')) {
|
|
31
|
+
return new Response(JSON.stringify(await this.getNames(conceptSlug)), {
|
|
22
32
|
headers: { 'Content-Type': 'application/json' },
|
|
23
33
|
});
|
|
24
34
|
}
|
|
25
35
|
|
|
26
|
-
if (method === '
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
36
|
+
if (method === 'GET' && url.pathname.endsWith('/progress')) {
|
|
37
|
+
return new Response(JSON.stringify(await this.calculateProgress(conceptSlug)), {
|
|
38
|
+
headers: { 'Content-Type': 'application/json' },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
if (method === 'POST' && url.pathname.endsWith('/add-name')) {
|
|
43
|
+
const { language, name } = (await request.json()) as {
|
|
44
|
+
language: 'en-us' | 'pt-br';
|
|
45
|
+
name: string;
|
|
46
|
+
};
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
return new Response('
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error('❌ Error processing request:', error);
|
|
41
|
-
return new Response('Bad Request', { status: 400 });
|
|
48
|
+
if (!['en-us', 'pt-br'].includes(language)) {
|
|
49
|
+
return new Response('Invalid language', { status: 400 });
|
|
42
50
|
}
|
|
51
|
+
|
|
52
|
+
await this.addName(conceptSlug, language, name);
|
|
53
|
+
return new Response('OK', { status: 200 });
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
return new Response('Not Found', { status: 404 });
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
async
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
async getNames(conceptSlug: string): Promise<{ 'en-us': string[]; 'pt-br': string[] }> {
|
|
60
|
+
if (!this.names[conceptSlug]) {
|
|
61
|
+
this.names[conceptSlug] =
|
|
62
|
+
(await this.state.storage.get<{ 'en-us': string[]; 'pt-br': string[] }>(`names:${conceptSlug}`)) || {
|
|
63
|
+
'en-us': [],
|
|
64
|
+
'pt-br': [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return this.names[conceptSlug];
|
|
68
|
+
}
|
|
53
69
|
|
|
54
|
-
|
|
55
|
-
|
|
70
|
+
async getCount(conceptSlug: string): Promise<number> {
|
|
71
|
+
if (this.counts[conceptSlug] === undefined) {
|
|
72
|
+
this.counts = (await this.state.storage.get<Record<string, number>>('counts')) || {};
|
|
56
73
|
}
|
|
74
|
+
return this.counts[conceptSlug] || 0;
|
|
57
75
|
}
|
|
58
76
|
|
|
59
|
-
async
|
|
60
|
-
|
|
77
|
+
async getTimestamps(conceptSlug: string): Promise<number[]> {
|
|
78
|
+
if (!this.timestamps[conceptSlug]) {
|
|
79
|
+
this.timestamps =
|
|
80
|
+
(await this.state.storage.get<Record<string, number[]>>('timestamps')) || {};
|
|
81
|
+
}
|
|
82
|
+
return this.timestamps[conceptSlug] || [];
|
|
83
|
+
}
|
|
61
84
|
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
async addName(conceptSlug: string, language: 'en-us' | 'pt-br', name: string): Promise<void> {
|
|
86
|
+
const names = await this.getNames(conceptSlug);
|
|
87
|
+
if (!names[language].includes(name)) {
|
|
88
|
+
names[language].push(name);
|
|
89
|
+
this.state.storage.put(`names:${conceptSlug}`, names); // ✅ No need to await
|
|
64
90
|
|
|
65
|
-
|
|
66
|
-
|
|
91
|
+
this.incrementCount(conceptSlug);
|
|
92
|
+
this.recordTimestamp(conceptSlug);
|
|
67
93
|
}
|
|
68
94
|
}
|
|
69
|
-
|
|
95
|
+
|
|
96
|
+
async incrementCount(conceptSlug: string): Promise<void> {
|
|
97
|
+
const count = (await this.getCount(conceptSlug)) + 1;
|
|
98
|
+
this.counts[conceptSlug] = count;
|
|
99
|
+
this.state.storage.put('counts', this.counts); // ✅ No need to await
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async recordTimestamp(conceptSlug: string): Promise<void> {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const timestamps = await this.getTimestamps(conceptSlug);
|
|
105
|
+
|
|
106
|
+
// Keep only last 60 minutes
|
|
107
|
+
const filteredTimestamps = timestamps.filter((ts) => now - ts < 60 * 60 * 1000);
|
|
108
|
+
filteredTimestamps.push(now);
|
|
109
|
+
|
|
110
|
+
this.timestamps[conceptSlug] = filteredTimestamps;
|
|
111
|
+
this.state.storage.put('timestamps', this.timestamps); // ✅ No need to await
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async calculateProgress(conceptSlug: string): Promise<ConceptProgress> {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
const oneMinuteAgo = now - 60 * 1000;
|
|
117
|
+
|
|
118
|
+
const count = await this.getCount(conceptSlug);
|
|
119
|
+
const timestamps = await this.getTimestamps(conceptSlug);
|
|
120
|
+
const recentTimestamps = timestamps.filter((ts) => ts >= oneMinuteAgo);
|
|
121
|
+
const ratePerMinute = recentTimestamps.length;
|
|
122
|
+
|
|
123
|
+
const remainingNames = ConceptNameDO.TOTAL_NAMES - count;
|
|
124
|
+
const estimatedTimeMinutes = ratePerMinute > 0 ? remainingNames / ratePerMinute : null;
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
concept: conceptSlug,
|
|
128
|
+
count,
|
|
129
|
+
total: ConceptNameDO.TOTAL_NAMES,
|
|
130
|
+
progress: ((count / ConceptNameDO.TOTAL_NAMES) * 100).toFixed(2) + '%',
|
|
131
|
+
ratePerMinute,
|
|
132
|
+
estimatedTimeMinutes: estimatedTimeMinutes ? Math.round(estimatedTimeMinutes) : 'N/A',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -2,7 +2,7 @@ import { and, eq } from 'drizzle-orm';
|
|
|
2
2
|
import { inject, injectable } from 'inversify';
|
|
3
3
|
import 'reflect-metadata';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
-
import {
|
|
5
|
+
import { schema } from '../..';
|
|
6
6
|
import { Concept, ControlNetConfig, Languages } from '../../types';
|
|
7
7
|
import {
|
|
8
8
|
KVConcept,
|
|
@@ -51,7 +51,9 @@ export class ConceptService {
|
|
|
51
51
|
// ✅ Fetch the latest name list from Durable Object
|
|
52
52
|
let allNamesEN: string[] = [];
|
|
53
53
|
let allNamesPT: string[] = [];
|
|
54
|
-
const response = await stub.fetch(
|
|
54
|
+
const response = await stub.fetch(
|
|
55
|
+
`https://internal/${conceptSlug}/names`
|
|
56
|
+
);
|
|
55
57
|
if (response.ok) {
|
|
56
58
|
const data = (await response.json()) as {
|
|
57
59
|
'en-us': string[];
|
|
@@ -60,7 +62,7 @@ export class ConceptService {
|
|
|
60
62
|
allNamesEN = data['en-us'] || [];
|
|
61
63
|
allNamesPT = data['pt-br'] || [];
|
|
62
64
|
} else {
|
|
63
|
-
console.log(
|
|
65
|
+
console.log(`!-- Error fetching names from DO for ${conceptSlug}`);
|
|
64
66
|
console.log('!-- Response:', await response.text());
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -71,7 +73,7 @@ export class ConceptService {
|
|
|
71
73
|
// 🔥 If names already exist in KV but not in DO, add them
|
|
72
74
|
if (existingEN.name && !allNamesEN.includes(existingEN.name)) {
|
|
73
75
|
console.log(`⚡ Backfilling existing name to DO: ${existingEN.name}`);
|
|
74
|
-
await stub.fetch(
|
|
76
|
+
await stub.fetch(`https://internal/${conceptSlug}/add-name`, {
|
|
75
77
|
method: 'POST',
|
|
76
78
|
body: JSON.stringify({ language: 'en-us', name: existingEN.name }),
|
|
77
79
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -79,7 +81,7 @@ export class ConceptService {
|
|
|
79
81
|
}
|
|
80
82
|
if (existingPT.name && !allNamesPT.includes(existingPT.name)) {
|
|
81
83
|
console.log(`⚡ Backfilling existing name to DO: ${existingPT.name}`);
|
|
82
|
-
await stub.fetch(
|
|
84
|
+
await stub.fetch(`https://internal/${conceptSlug}/add-name`, {
|
|
83
85
|
method: 'POST',
|
|
84
86
|
body: JSON.stringify({ language: 'pt-br', name: existingPT.name }),
|
|
85
87
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -127,13 +129,13 @@ export class ConceptService {
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
// ✅ **Immediately update Durable Object with the new name**
|
|
130
|
-
await stub.fetch(
|
|
132
|
+
await stub.fetch(`https://internal/${conceptSlug}/add-name`, {
|
|
131
133
|
method: 'POST',
|
|
132
134
|
body: JSON.stringify({ language: 'en-us', name: nameEN }),
|
|
133
135
|
headers: { 'Content-Type': 'application/json' },
|
|
134
136
|
});
|
|
135
137
|
|
|
136
|
-
await stub.fetch(
|
|
138
|
+
await stub.fetch(`https://internal/${conceptSlug}/add-name`, {
|
|
137
139
|
method: 'POST',
|
|
138
140
|
body: JSON.stringify({ language: 'pt-br', name: namePT }),
|
|
139
141
|
headers: { 'Content-Type': 'application/json' },
|
package/package.json
CHANGED
|
@@ -26,7 +26,7 @@ export type CentralBindings = {
|
|
|
26
26
|
KV_CONCEPT_FAILURES: KVNamespace;
|
|
27
27
|
KV_CONCEPT_NAMES: KVNamespace;
|
|
28
28
|
CONCEPT_GENERATION_QUEUE: Queue;
|
|
29
|
-
CONCEPT_NAMES_DO: DurableObjectNamespace
|
|
29
|
+
CONCEPT_NAMES_DO: DurableObjectNamespace;
|
|
30
30
|
|
|
31
31
|
PROMPT_IMAGE_DESCRIBER: string;
|
|
32
32
|
PROMPT_GENERATE_ARCHETYPE_BASIC_INFO: string;
|
package/types/scopes/generic.ts
CHANGED
|
@@ -398,3 +398,12 @@ export type AstroKVData = {
|
|
|
398
398
|
diff: number;
|
|
399
399
|
}[];
|
|
400
400
|
};
|
|
401
|
+
|
|
402
|
+
export type ConceptProgress = {
|
|
403
|
+
concept: string;
|
|
404
|
+
count: number;
|
|
405
|
+
total: number;
|
|
406
|
+
progress: string; // Percentage as string (e.g., "75.32%")
|
|
407
|
+
ratePerMinute: number;
|
|
408
|
+
estimatedTimeMinutes: number | 'N/A';
|
|
409
|
+
};
|