@zodic/shared 0.0.37 → 0.0.38

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.
@@ -0,0 +1,93 @@
1
+ import { drizzle } from 'drizzle-orm/d1';
2
+ import { Api } from '../api';
3
+ import * as schema from '../db/schema';
4
+ import { BackendBindings } from '../types';
5
+ import { buildChatGPTMessages } from '../utils/buildMessages';
6
+
7
+ export class AppContext {
8
+ private _drizzle?: ReturnType<typeof drizzle>;
9
+
10
+ constructor(public env: BackendBindings) {}
11
+
12
+ buildChatGPTMessages() {
13
+ return buildChatGPTMessages(this.env);
14
+ }
15
+
16
+ kvCosmicMirrorArchetypesStore() {
17
+ if (!this.env.KV_COSMIC_MIRROR_ARCHETYPES) {
18
+ throw new Error(
19
+ 'KV_COSMIC_MIRROR_ARCHETYPES is not defined in the environment.'
20
+ );
21
+ }
22
+ return this.env.KV_COSMIC_MIRROR_ARCHETYPES;
23
+ }
24
+
25
+ kvConceptsStore() {
26
+ if (!this.env.KV_CONCEPTS) {
27
+ throw new Error(
28
+ 'KV_COSMIC_MIRROR_ARCHETYPES is not defined in the environment.'
29
+ );
30
+ }
31
+ return this.env.KV_CONCEPTS;
32
+ }
33
+
34
+ kvGenerationStore() {
35
+ if (!this.env.KV_GENERATION_MANAGEMENT) {
36
+ throw new Error(
37
+ 'KV_GENERATION_MANAGEMENT is not defined in the environment.'
38
+ );
39
+ }
40
+ return this.env.KV_GENERATION_MANAGEMENT;
41
+ }
42
+
43
+ kvUserGenerationStore() {
44
+ if (!this.env.KV_USER_GENERATIONS) {
45
+ throw new Error('KV_USER_GENERATIONS is not defined in the environment.');
46
+ }
47
+ return this.env.KV_GENERATION_MANAGEMENT;
48
+ }
49
+
50
+ kvPromptsStore() {
51
+ if (!this.env.KV_LEONARDO_PROMPTS) {
52
+ throw new Error('KV_LEONARDO_PROMPTS is not defined in the environment.');
53
+ }
54
+ return this.env.KV_LEONARDO_PROMPTS;
55
+ }
56
+
57
+ kvUserProductStatus() {
58
+ if (!this.env.KV_USER_PRODUCT_STATUS) {
59
+ throw new Error('KV_LEONARDO_PROMPTS is not defined in the environment.');
60
+ }
61
+ return this.env.KV_USER_PRODUCT_STATUS;
62
+ }
63
+
64
+ api() {
65
+ return Api(this.env);
66
+ }
67
+
68
+ drizzle() {
69
+ if (!this._drizzle) {
70
+ if (!this.env.DB) {
71
+ throw new Error('Database binding (DB) is not defined.');
72
+ }
73
+ this._drizzle = drizzle(this.env.DB, { schema });
74
+ }
75
+
76
+ return this._drizzle;
77
+ }
78
+
79
+ get queues() {
80
+ return {
81
+ conceptGenerationQueue: this.conceptGenerationQueue(),
82
+ };
83
+ }
84
+
85
+ private conceptGenerationQueue() {
86
+ if (!this.env.CONCEPT_GENERATION_QUEUE) {
87
+ throw new Error(
88
+ 'CONCEPT_GENERATION_QUEUE is not defined in the environment.'
89
+ );
90
+ }
91
+ return this.env.CONCEPT_GENERATION_QUEUE;
92
+ }
93
+ }
package/base/index.ts ADDED
File without changes
package/db/schema.ts CHANGED
@@ -159,8 +159,8 @@ export const concepts = sqliteTable(
159
159
  'concepts',
160
160
  {
161
161
  id: text('id').primaryKey(), // Unique Concept ID
162
- name: text('name').notNull(), // Name of the Concept (e.g., "The Crown", "The Shield")
163
- slug: text('slug').notNull(), // Name of the Concept (e.g., "crown", "shield")
162
+ name: text('name').notNull(), // Name of the Concept (e.g., "The Crown", "The Amulet")
163
+ slug: text('slug').notNull(), // Name of the Concept (e.g., "crown", "amulet")
164
164
  planet1: text('planet1').notNull(), // First planet (e.g., "Sun")
165
165
  planet2: text('planet2').notNull(), // Second planet (e.g., "Moon")
166
166
  planet3: text('planet3'), // Third planet (e.g., "Ascendant"), optional for some Concepts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodic/shared",
3
- "version": "0.0.37",
3
+ "version": "0.0.38",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -29,6 +29,7 @@
29
29
  "drizzle-orm": "^0.38.0",
30
30
  "hono": "^4.6.13",
31
31
  "inversify": "^6.2.1",
32
- "jose": "^5.9.6"
32
+ "jose": "^5.9.6",
33
+ "reflect-metadata": "^0.2.2"
33
34
  }
34
35
  }
@@ -0,0 +1,207 @@
1
+ import { inject, injectable } from 'inversify';
2
+ import 'reflect-metadata';
3
+ import { AppContext } from '../base/AppContext';
4
+ import { KVConcept, sizes } from '../types/scopes/legacy';
5
+
6
+ @injectable()
7
+ export class ConceptService {
8
+ constructor(@inject(AppContext) private context: AppContext) {}
9
+
10
+ /**
11
+ * Generate basic info for a concept: name, description, and poem.
12
+ */
13
+ async generateBasicInfo(
14
+ conceptSlug: string,
15
+ combinationString: string
16
+ ): Promise<void> {
17
+ const kvKey = `concepts:${conceptSlug}:${combinationString}`;
18
+ console.log(
19
+ `Generating basic info for concept: ${conceptSlug}, combination: ${combinationString}`
20
+ );
21
+
22
+ const messages = this.context
23
+ .buildChatGPTMessages()
24
+ .generateConceptBasicInfo({
25
+ combination: combinationString,
26
+ conceptSlug,
27
+ });
28
+
29
+ const response = await this.context.api().callChatGPT(messages, {});
30
+ if (!response) {
31
+ throw new Error(
32
+ `Failed to generate basic info for concept: ${conceptSlug}`
33
+ );
34
+ }
35
+
36
+ const { name, description, poem } = this.parseBasicInfoResponse(response);
37
+
38
+ // Save basic info to KV
39
+ const concept = await this.getKVConcept(kvKey);
40
+ Object.assign(concept, { name, description, poem, status: 'idle' });
41
+
42
+ await this.context.kvConceptsStore().put(kvKey, JSON.stringify(concept));
43
+ console.log(
44
+ `Basic info stored for concept: ${conceptSlug}, combination: ${combinationString}`
45
+ );
46
+ }
47
+
48
+ private parseBasicInfoResponse(response: string): {
49
+ name: string;
50
+ description: string;
51
+ poem: string;
52
+ } {
53
+ console.log('Parsing basic info response from ChatGPT');
54
+ const nameMatch = response.match(/- Name: (.+)/);
55
+ const descriptionMatch = response.match(/- Description: (.+)/);
56
+ const poemMatch = response.match(/- Poem: (.+)/);
57
+
58
+ if (!nameMatch || !descriptionMatch || !poemMatch) {
59
+ throw new Error('Invalid basic info response format');
60
+ }
61
+
62
+ return {
63
+ name: nameMatch[1].trim(),
64
+ description: descriptionMatch[1].trim(),
65
+ poem: poemMatch[1].trim(),
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Generate the Leonardo prompt for a concept.
71
+ */
72
+ async generatePrompt(
73
+ conceptSlug: string,
74
+ combinationString: string
75
+ ): Promise<void> {
76
+ const kvKey = `concepts:${conceptSlug}:${combinationString}`;
77
+ console.log(
78
+ `Generating Leonardo prompt for concept: ${conceptSlug}, combination: ${combinationString}`
79
+ );
80
+
81
+ const concept = await this.getKVConcept(kvKey);
82
+ if (!concept.name || !concept.description || !concept.poem) {
83
+ throw new Error(
84
+ `Basic info must be populated before generating prompt for concept: ${conceptSlug}`
85
+ );
86
+ }
87
+
88
+ const messages = this.context
89
+ .buildChatGPTMessages()
90
+ .generateConceptLeonardoPrompt({
91
+ conceptSlug,
92
+ combination: combinationString,
93
+ });
94
+
95
+ const response = await this.context.api().callChatGPT(messages, {});
96
+ if (!response) {
97
+ throw new Error(
98
+ `Failed to generate Leonardo prompt for concept: ${conceptSlug}`
99
+ );
100
+ }
101
+
102
+ concept.leonardoPrompt = response.trim();
103
+ await this.context.kvConceptsStore().put(kvKey, JSON.stringify(concept));
104
+ console.log(
105
+ `Leonardo prompt stored for concept: ${conceptSlug}, combination: ${combinationString}`
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Generate additional content for a concept.
111
+ */
112
+ async generateContent(
113
+ conceptSlug: string,
114
+ combinationString: string
115
+ ): Promise<void> {
116
+ const kvKey = `concepts:${conceptSlug}:${combinationString}`;
117
+ console.log(
118
+ `Generating additional content for concept: ${conceptSlug}, combination: ${combinationString}`
119
+ );
120
+
121
+ const concept = await this.getKVConcept(kvKey);
122
+ if (!concept.name || !concept.description || !concept.poem) {
123
+ throw new Error(
124
+ `Basic info must be populated before generating content for concept: ${conceptSlug}`
125
+ );
126
+ }
127
+
128
+ const messages = this.context
129
+ .buildChatGPTMessages()
130
+ .generateConceptContent({
131
+ conceptSlug,
132
+ combination: combinationString,
133
+ name: concept.name,
134
+ description: concept.description,
135
+ poem: concept.poem,
136
+ });
137
+
138
+ const response = await this.context.api().callChatGPT(messages, {});
139
+ if (!response) {
140
+ throw new Error(`Failed to generate content for concept: ${conceptSlug}`);
141
+ }
142
+
143
+ concept.content = response.trim();
144
+ await this.context.kvConceptsStore().put(kvKey, JSON.stringify(concept));
145
+ console.log(
146
+ `Content stored for concept: ${conceptSlug}, combination: ${combinationString}`
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Queue image generation for a concept.
152
+ */
153
+ async generateImages(
154
+ conceptSlug: string,
155
+ combinationString: string
156
+ ): Promise<void> {
157
+ const kvKey = `concepts:${conceptSlug}:${combinationString}`;
158
+ console.log(
159
+ `Queuing image generation for concept: ${conceptSlug}, combination: ${combinationString}`
160
+ );
161
+
162
+ const concept = await this.getKVConcept(kvKey);
163
+ if (!concept.leonardoPrompt) {
164
+ throw new Error(
165
+ `Leonardo prompt must be populated before generating images for concept: ${conceptSlug}`
166
+ );
167
+ }
168
+
169
+ const { width, height } = sizes['alchemy']['post4:5'];
170
+
171
+ await this.context.api().callLeonardo.generateImage({
172
+ prompt: concept.leonardoPrompt,
173
+ width,
174
+ height,
175
+ });
176
+
177
+ console.log(
178
+ `Post images queued for concept: ${conceptSlug}, combination: ${combinationString}`
179
+ );
180
+ }
181
+
182
+ /**
183
+ * Fetch and initialize a KVConcept object if not present.
184
+ */
185
+ private async getKVConcept(kvKey: string): Promise<KVConcept> {
186
+ const existingData = await this.context
187
+ .kvConceptsStore()
188
+ .get<KVConcept>(kvKey, 'json');
189
+ if (existingData) {
190
+ return existingData;
191
+ }
192
+
193
+ console.log(
194
+ `No existing KV data found. Creating default entry for key: ${kvKey}`
195
+ );
196
+ return {
197
+ name: '',
198
+ description: '',
199
+ content: '',
200
+ poem: '',
201
+ leonardoPrompt: '',
202
+ postImages: [],
203
+ reelImages: [null, null, null],
204
+ status: 'idle',
205
+ };
206
+ }
207
+ }
package/tsconfig.json CHANGED
@@ -14,6 +14,8 @@
14
14
  "jsx": "react-jsx",
15
15
  "allowSyntheticDefaultImports": true,
16
16
  "forceConsistentCasingInFileNames": true,
17
+ "experimentalDecorators": true,
18
+ "emitDecoratorMetadata": true,
17
19
  "allowJs": true,
18
20
  "types": [
19
21
  "bun-types" // add Bun global
@@ -118,3 +118,17 @@ export type DominantSign = {
118
118
  sign_name: string;
119
119
  percentage: number;
120
120
  };
121
+
122
+ export type ChatMessages = ChatMessage[];
123
+ export type ChatMessage = {
124
+ role: 'user' | 'assistant' | 'system';
125
+ content: string;
126
+ };
127
+
128
+ export interface Crown {
129
+ sun: string;
130
+ ascendant: string;
131
+ moon: string;
132
+ }
133
+
134
+ export type Concept = 'crown' | 'scepter' | 'amulet' | 'lantern' | 'orb';
@@ -0,0 +1,255 @@
1
+ import { BackendBindings } from './cloudflare';
2
+ import { Gender } from './generic';
3
+
4
+ export type ConceptPhase =
5
+ | 'name-description-poem'
6
+ | 'leonardo-prompt'
7
+ | 'content'
8
+ | 'images';
9
+
10
+ export const zodiacSigns = [
11
+ 'Aries',
12
+ 'Taurus',
13
+ 'Gemini',
14
+ 'Cancer',
15
+ 'Leo',
16
+ 'Virgo',
17
+ 'Libra',
18
+ 'Scorpio',
19
+ 'Sagittarius',
20
+ 'Capricorn',
21
+ 'Aquarius',
22
+ 'Pisces',
23
+ ];
24
+
25
+ export interface ChatGPTOptions {
26
+ model?: string;
27
+ options?: Record<string, any>;
28
+ }
29
+
30
+ export interface LeonardoGenerateImageParams {
31
+ prompt: string;
32
+ width: number;
33
+ height: number;
34
+ controlNets?: Array<{
35
+ initImageId: string;
36
+ initImageType: 'UPLOADED' | 'GENERATED';
37
+ preprocessorId: number;
38
+ strengthType: 'Low' | 'Mid' | 'High';
39
+ }>;
40
+ quantity?: number;
41
+ }
42
+
43
+ export type HouseCuspsReport = {
44
+ planetName: string; // Name of the planet (e.g., "Sun")
45
+ house: number; // House number (e.g., 5)
46
+ report: string; // Detailed report or description
47
+ };
48
+
49
+ export type NatalHouseCuspReport = {
50
+ house: number; // House number (e.g., 1)
51
+ sign: string; // Sign associated with the house (e.g., "Aquarius")
52
+ degree: number; // Degree value (e.g., 303.73276)
53
+ report: string; // Detailed report or description
54
+ };
55
+
56
+ export interface LeonardoGeneratePresignedUrlParams {
57
+ extension: string;
58
+ }
59
+
60
+ export interface LeonardoUploadImageParams {
61
+ presignedUrl: string;
62
+ fields: string;
63
+ file: Blob | Uint8Array | ArrayBuffer;
64
+ }
65
+
66
+ export interface LeonardoGenerateImageResponse {
67
+ sdGenerationJob: {
68
+ generationId?: string;
69
+ apiCreditCost?: number;
70
+ };
71
+ }
72
+
73
+ export type Size = 'post1:1' | 'post4:5' | 'post1.91:1' | 'reels';
74
+
75
+ export interface ImageDescriberOptions {
76
+ imageUrl: string;
77
+ prompt: string;
78
+ }
79
+
80
+ export interface Crown {
81
+ sun: string;
82
+ ascendant: string;
83
+ moon: string;
84
+ }
85
+
86
+ export interface GenerateLeonardoPromptParams {
87
+ env: BackendBindings;
88
+ archetypeName: string;
89
+ archetypeDescription: string;
90
+ gender: Gender;
91
+ }
92
+
93
+ export interface GenerateEndpointParams {
94
+ crown: Crown;
95
+ gender: Gender;
96
+ }
97
+
98
+ export type GenerateLeonardoPromptParamsWithoutEnv = Omit<
99
+ GenerateLeonardoPromptParams,
100
+ 'env'
101
+ >;
102
+
103
+ export type personalizeCosmicMirrorLeonardoPrompt = {
104
+ leonardoPrompt: string;
105
+ traits: string;
106
+ };
107
+
108
+ export type KVArchetype = {
109
+ name: string;
110
+ description: string;
111
+ content: string;
112
+ leonardoPrompt: string;
113
+ virtues: string;
114
+ images: LeonardoImage[];
115
+ status: 'idle' | 'generating' | 'completed';
116
+ };
117
+ export type KVConcept = {
118
+ name: string;
119
+ description: string;
120
+ content: string;
121
+ poem: string;
122
+ leonardoPrompt: string;
123
+ postImages: LeonardoImage[]; // Pre-generated in sequence
124
+ reelImages: Array<LeonardoImage | null>; // Initially null, aligned by index
125
+ status: 'idle' | 'generating' | 'completed';
126
+ };
127
+
128
+ export type KVGenerationObject = {
129
+ crown: string;
130
+ gender: Gender;
131
+ archetypeIndex: number;
132
+ prompt: string;
133
+ };
134
+
135
+ export type KVUserGenerationObject = {
136
+ userId: string; // ID of the user
137
+ productId: string; // ID of the product
138
+ productName: string;
139
+ createdAt: string; // Timestamp when the generation was created
140
+ };
141
+
142
+ export type ArchetypeObj = {
143
+ name: string;
144
+ description: string;
145
+ generationId: string;
146
+ leonardoPrompt: string;
147
+ virtues: string;
148
+ imageCounter: number;
149
+ images: LeonardoImage[];
150
+ };
151
+
152
+ export type LeonardoImage = {
153
+ leonardoId: string;
154
+ url: string;
155
+ height: number;
156
+ width: number;
157
+ };
158
+
159
+ export interface LeonardoRequestBody {
160
+ prompt: string;
161
+ width: number;
162
+ height: number;
163
+ negative_prompt: string;
164
+ num_images: number;
165
+ modelId: string;
166
+ presetStyle: string;
167
+ alchemy: boolean;
168
+ controlnets?: Array<{
169
+ initImageId: string;
170
+ initImageType: string;
171
+ preprocessorId: number;
172
+ strengthType: string;
173
+ }>;
174
+ init_generation_image_id?: string;
175
+ init_strength?: number;
176
+ }
177
+
178
+ export interface LeonardoInitImageResponse {
179
+ id: string;
180
+ createdAt: string;
181
+ userId: string;
182
+ url: string;
183
+ type: string; // e.g., "UPLOADED" or "GENERATED"
184
+ metadata: Record<string, any>; // Optional metadata if available
185
+ }
186
+
187
+ export type TimezoneResponse = {
188
+ dstOffset: number; // Daylight Savings Time offset in seconds
189
+ rawOffset: number; // Raw UTC offset in seconds
190
+ status: string; // Response status, e.g., "OK"
191
+ timeZoneId: string; // Time zone ID, e.g., "America/Los_Angeles"
192
+ timeZoneName: string; // Time zone name, e.g., "Pacific Standard Time"
193
+ };
194
+
195
+ export type AstrologyPlanetData = {
196
+ name: string; // Planet name (e.g., "Sun", "Moon")
197
+ fullDegree: number; // Full degree position of the planet
198
+ normDegree: number; // Normalized degree (0-29°)
199
+ speed: number; // Speed of the planet
200
+ isRetro: 'true' | 'false'; // Retrograde status as a string
201
+ sign: string; // Sign the planet is in (e.g., "Sagittarius")
202
+ house: number; // House number (1-12)
203
+ };
204
+
205
+ export type AstrologyApiResponse = AstrologyPlanetData[];
206
+
207
+ type Sizes = {
208
+ [k: string]: {
209
+ [key in Size]: {
210
+ width: number;
211
+ height: number;
212
+ };
213
+ };
214
+ };
215
+
216
+ export const sizes: Sizes = {
217
+ alchemy: {
218
+ // closest values of input to get the closest possible values of output fit for the social media
219
+ 'post1:1': {
220
+ width: 624,
221
+ height: 624,
222
+ },
223
+ 'post4:5': {
224
+ width: 624,
225
+ height: 784,
226
+ },
227
+ 'post1.91:1': {
228
+ width: 912,
229
+ height: 512,
230
+ },
231
+ reels: {
232
+ width: 756,
233
+ height: 1344,
234
+ },
235
+ },
236
+ noAlchemy: {
237
+ // exact values available on leonardo's dashboard
238
+ 'post1:1': {
239
+ width: 1110,
240
+ height: 1110,
241
+ },
242
+ 'post4:5': {
243
+ width: 990,
244
+ height: 1240,
245
+ },
246
+ 'post1.91:1': {
247
+ width: 1500,
248
+ height: 810,
249
+ },
250
+ reels: {
251
+ width: 830,
252
+ height: 1480,
253
+ },
254
+ },
255
+ };