@kokimoki/app 3.1.1 → 3.1.3

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.
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { APP_META_STORE_NAME } from "./core/kokimoki-client";
3
3
  export type { AppMetaState } from "./core/kokimoki-client";
4
4
  export { KokimokiStore } from "./stores";
5
5
  export type { KokimokiClientEvents, KokimokiEnv, Paginated, PollOptions, Upload, } from "./types";
6
- export type { AllLanguagesStatus, I18nOptions, LanguageStatus, NamespaceStatus, RequestTranslationResult, TranslationStatus, } from "./services/kokimoki-i18n";
6
+ export type { I18nOptions, TranslationStatus as LanguageStatus, TranslationStatus as NamespaceStatus, TranslationStatus, } from "./services/kokimoki-i18n";
7
7
  export type { AiJob, AiJobStatus, AiJobType } from "./services/kokimoki-ai";
8
8
  export { getKmClient } from "./utils/kokimoki-client";
9
9
  export { getKmEnv } from "./utils/kokimoki-env";
@@ -1,16 +1,15 @@
1
- import { z, ZodType } from "zod/v4";
1
+ import { ZodType } from "zod/v4";
2
2
  import { KokimokiClient } from "../core";
3
- import type { PollOptions, Upload } from "../types";
4
3
  /** AI job status */
5
4
  export type AiJobStatus = "processing" | "queued" | "completed" | "failed";
6
5
  /** AI job type */
7
6
  export type AiJobType = "text" | "json" | "image";
8
7
  /** AI job information */
9
- export interface AiJob {
8
+ export interface AiJob<T = unknown> {
10
9
  jobId: string;
11
10
  type: AiJobType;
12
11
  status: AiJobStatus;
13
- result?: unknown;
12
+ result?: T;
14
13
  error?: {
15
14
  message: string;
16
15
  code?: string;
@@ -109,7 +108,7 @@ export declare class KokimokiAiService {
109
108
  * Generate text content from the AI model.
110
109
  *
111
110
  * Submits a text generation job and returns immediately with a jobId.
112
- * Use `pollText()` to wait for the result.
111
+ * Use `getJob()` to poll for the result.
113
112
  *
114
113
  * @param req The generation request parameters.
115
114
  * @returns A promise that resolves to an object containing the jobId.
@@ -122,7 +121,8 @@ export declare class KokimokiAiService {
122
121
  * temperature: 0.8
123
122
  * });
124
123
  *
125
- * const text = await kmClient.ai.pollText(jobId);
124
+ * const job = await kmClient.ai.getJob<string>(jobId);
125
+ * console.log('Generated quest:', job.result);
126
126
  * ```
127
127
  */
128
128
  generateText(req: TextGenerateRequest): Promise<JobSubmitResponse>;
@@ -131,7 +131,7 @@ export declare class KokimokiAiService {
131
131
  *
132
132
  * Submits a JSON generation job and returns immediately with a jobId.
133
133
  * The Zod schema is converted to JSON Schema and sent to the AI.
134
- * Use `pollJson()` to wait for the result with type inference.
134
+ * Use `getJob()` to poll for the result with type inference.
135
135
  *
136
136
  * @param req The generation request parameters including Zod schema.
137
137
  * @returns A promise that resolves to an object containing the jobId.
@@ -144,13 +144,16 @@ export declare class KokimokiAiService {
144
144
  * attack: z.number()
145
145
  * });
146
146
  *
147
+ * type Enemy = z.infer<typeof enemySchema>;
148
+ *
147
149
  * const { jobId } = await kmClient.ai.generateJson({
148
150
  * schema: enemySchema,
149
151
  * prompt: 'Create a level 5 goblin warrior'
150
152
  * });
151
153
  *
152
154
  * // Poll with schema for type inference and validation
153
- * const enemy = await kmClient.ai.pollJson(jobId, { schema: enemySchema });
155
+ * const job = await kmClient.ai.getJob<Enemy>(jobId);
156
+ * console.log('Generated enemy:', job.result);
154
157
  * ```
155
158
  */
156
159
  generateJson<T extends ZodType>(req: JsonGenerateRequest<T>): Promise<JobSubmitResponse>;
@@ -158,7 +161,7 @@ export declare class KokimokiAiService {
158
161
  * Generate or modify an image using the AI model.
159
162
  *
160
163
  * Submits an image generation job and returns immediately with a jobId.
161
- * Use `pollImage()` to wait for the result.
164
+ * Use `getJob()` to poll for the result.
162
165
  *
163
166
  * @param req The generation request parameters.
164
167
  * @returns A promise that resolves to an object containing the jobId.
@@ -171,7 +174,8 @@ export declare class KokimokiAiService {
171
174
  * prompt: 'Make it look like pixel art'
172
175
  * });
173
176
  *
174
- * const image = await kmClient.ai.pollImage(jobId);
177
+ * const job = await kmClient.ai.getJob<string>(jobId);
178
+ * console.log('Generated image:', job.result);
175
179
  * ```
176
180
  */
177
181
  generateImage(req: ImageGenerateRequest): Promise<JobSubmitResponse>;
@@ -183,48 +187,6 @@ export declare class KokimokiAiService {
183
187
  * @param jobId The job ID to check.
184
188
  * @returns A promise that resolves to the job information.
185
189
  */
186
- getJob(jobId: string): Promise<AiJob>;
187
- /**
188
- * Poll a text generation job until completion.
189
- *
190
- * @param jobId The job ID to poll.
191
- * @param options Polling options (interval, timeout, progress callback).
192
- * @returns A promise that resolves to the generated text.
193
- * @throws If the job fails or times out.
194
- */
195
- pollText(jobId: string, options?: PollOptions<AiJobStatus>): Promise<string>;
196
- /**
197
- * Poll a JSON generation job until completion.
198
- *
199
- * @param jobId The job ID to poll.
200
- * @param options Polling options including the Zod schema for validation.
201
- * @returns A promise that resolves to the parsed and validated JSON.
202
- * @throws If the job fails, times out, or validation fails.
203
- *
204
- * @example
205
- * ```typescript
206
- * const enemy = await kmClient.ai.pollJson(savedJobId, {
207
- * schema: enemySchema,
208
- * timeout: 60000,
209
- * onProgress: (status) => console.log('Status:', status)
210
- * });
211
- * ```
212
- */
213
- pollJson<T extends ZodType>(jobId: string, options: PollOptions<AiJobStatus> & {
214
- schema: T;
215
- }): Promise<z.infer<T>>;
216
- /**
217
- * Poll an image generation job until completion.
218
- *
219
- * @param jobId The job ID to poll.
220
- * @param options Polling options (interval, timeout, progress callback).
221
- * @returns A promise that resolves to the upload information.
222
- * @throws If the job fails or times out.
223
- */
224
- pollImage(jobId: string, options?: PollOptions<AiJobStatus>): Promise<Upload>;
225
- /**
226
- * Internal polling implementation.
227
- */
228
- private pollJob;
190
+ getJob<T = unknown>(jobId: string): Promise<AiJob<T>>;
229
191
  }
230
192
  export {};
@@ -58,7 +58,7 @@ export class KokimokiAiService {
58
58
  * Generate text content from the AI model.
59
59
  *
60
60
  * Submits a text generation job and returns immediately with a jobId.
61
- * Use `pollText()` to wait for the result.
61
+ * Use `getJob()` to poll for the result.
62
62
  *
63
63
  * @param req The generation request parameters.
64
64
  * @returns A promise that resolves to an object containing the jobId.
@@ -71,7 +71,8 @@ export class KokimokiAiService {
71
71
  * temperature: 0.8
72
72
  * });
73
73
  *
74
- * const text = await kmClient.ai.pollText(jobId);
74
+ * const job = await kmClient.ai.getJob<string>(jobId);
75
+ * console.log('Generated quest:', job.result);
75
76
  * ```
76
77
  */
77
78
  async generateText(req) {
@@ -93,7 +94,7 @@ export class KokimokiAiService {
93
94
  *
94
95
  * Submits a JSON generation job and returns immediately with a jobId.
95
96
  * The Zod schema is converted to JSON Schema and sent to the AI.
96
- * Use `pollJson()` to wait for the result with type inference.
97
+ * Use `getJob()` to poll for the result with type inference.
97
98
  *
98
99
  * @param req The generation request parameters including Zod schema.
99
100
  * @returns A promise that resolves to an object containing the jobId.
@@ -106,13 +107,16 @@ export class KokimokiAiService {
106
107
  * attack: z.number()
107
108
  * });
108
109
  *
110
+ * type Enemy = z.infer<typeof enemySchema>;
111
+ *
109
112
  * const { jobId } = await kmClient.ai.generateJson({
110
113
  * schema: enemySchema,
111
114
  * prompt: 'Create a level 5 goblin warrior'
112
115
  * });
113
116
  *
114
117
  * // Poll with schema for type inference and validation
115
- * const enemy = await kmClient.ai.pollJson(jobId, { schema: enemySchema });
118
+ * const job = await kmClient.ai.getJob<Enemy>(jobId);
119
+ * console.log('Generated enemy:', job.result);
116
120
  * ```
117
121
  */
118
122
  async generateJson(req) {
@@ -136,7 +140,7 @@ export class KokimokiAiService {
136
140
  * Generate or modify an image using the AI model.
137
141
  *
138
142
  * Submits an image generation job and returns immediately with a jobId.
139
- * Use `pollImage()` to wait for the result.
143
+ * Use `getJob()` to poll for the result.
140
144
  *
141
145
  * @param req The generation request parameters.
142
146
  * @returns A promise that resolves to an object containing the jobId.
@@ -149,7 +153,8 @@ export class KokimokiAiService {
149
153
  * prompt: 'Make it look like pixel art'
150
154
  * });
151
155
  *
152
- * const image = await kmClient.ai.pollImage(jobId);
156
+ * const job = await kmClient.ai.getJob<string>(jobId);
157
+ * console.log('Generated image:', job.result);
153
158
  * ```
154
159
  */
155
160
  async generateImage(req) {
@@ -169,9 +174,6 @@ export class KokimokiAiService {
169
174
  }
170
175
  return await res.json();
171
176
  }
172
- // ============================================================
173
- // Job Status & Polling Methods
174
- // ============================================================
175
177
  /**
176
178
  * Get the current status of an AI job.
177
179
  *
@@ -190,77 +192,4 @@ export class KokimokiAiService {
190
192
  }
191
193
  return await res.json();
192
194
  }
193
- /**
194
- * Poll a text generation job until completion.
195
- *
196
- * @param jobId The job ID to poll.
197
- * @param options Polling options (interval, timeout, progress callback).
198
- * @returns A promise that resolves to the generated text.
199
- * @throws If the job fails or times out.
200
- */
201
- async pollText(jobId, options) {
202
- const result = await this.pollJob(jobId, options);
203
- return result;
204
- }
205
- /**
206
- * Poll a JSON generation job until completion.
207
- *
208
- * @param jobId The job ID to poll.
209
- * @param options Polling options including the Zod schema for validation.
210
- * @returns A promise that resolves to the parsed and validated JSON.
211
- * @throws If the job fails, times out, or validation fails.
212
- *
213
- * @example
214
- * ```typescript
215
- * const enemy = await kmClient.ai.pollJson(savedJobId, {
216
- * schema: enemySchema,
217
- * timeout: 60000,
218
- * onProgress: (status) => console.log('Status:', status)
219
- * });
220
- * ```
221
- */
222
- async pollJson(jobId, options) {
223
- const { schema, ...pollOptions } = options;
224
- const result = await this.pollJob(jobId, pollOptions);
225
- return schema.parse(result);
226
- }
227
- /**
228
- * Poll an image generation job until completion.
229
- *
230
- * @param jobId The job ID to poll.
231
- * @param options Polling options (interval, timeout, progress callback).
232
- * @returns A promise that resolves to the upload information.
233
- * @throws If the job fails or times out.
234
- */
235
- async pollImage(jobId, options) {
236
- const result = await this.pollJob(jobId, options);
237
- return result;
238
- }
239
- /**
240
- * Internal polling implementation.
241
- */
242
- async pollJob(jobId, options) {
243
- const pollInterval = Math.max(1000, options?.pollInterval ?? 2000);
244
- const timeout = options?.timeout ?? 120000;
245
- const startTime = Date.now();
246
- while (true) {
247
- const job = await this.getJob(jobId);
248
- // Call progress callback
249
- options?.onProgress?.(job.status);
250
- if (job.status === "completed") {
251
- return job.result;
252
- }
253
- if (job.status === "failed") {
254
- throw new Error(job.error?.message ?? "AI job failed");
255
- }
256
- // processing and queued both continue polling
257
- // queued jobs will be promoted when slots become available
258
- // Check timeout
259
- if (Date.now() - startTime > timeout) {
260
- throw new Error(`AI job timed out after ${timeout}ms`);
261
- }
262
- // Wait before next poll
263
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
264
- }
265
- }
266
195
  }
@@ -1,6 +1,5 @@
1
1
  import i18next, { type i18n } from "i18next";
2
2
  import type { KokimokiClient } from "../core";
3
- import type { PollOptions } from "../types";
4
3
  /**
5
4
  * Options for creating an i18n instance.
6
5
  */
@@ -15,39 +14,7 @@ export interface I18nOptions {
15
14
  /**
16
15
  * Status of a single namespace translation.
17
16
  */
18
- export type NamespaceStatus = "available" | "processing" | "failed" | "not_available";
19
- /**
20
- * Aggregated status of a language across all namespaces.
21
- */
22
- export type LanguageStatus = "available" | "processing" | "failed" | "partial";
23
- /**
24
- * Translation status for a specific language.
25
- */
26
- export interface TranslationStatus {
27
- /** Overall status of the language */
28
- status: "available" | "processing" | "not_available";
29
- /** Status per namespace */
30
- namespaces: Record<string, NamespaceStatus>;
31
- }
32
- /**
33
- * Status of all languages that have been requested.
34
- */
35
- export interface AllLanguagesStatus {
36
- /** Array of language statuses */
37
- languages: {
38
- lng: string;
39
- status: LanguageStatus;
40
- }[];
41
- }
42
- /**
43
- * Result of requesting a translation.
44
- */
45
- export interface RequestTranslationResult {
46
- /** Language code that was requested */
47
- lng: string;
48
- /** Status of the request */
49
- status: "started" | "already_processing" | "already_available";
50
- }
17
+ export type TranslationStatus = "available" | "processing" | "failed";
51
18
  /**
52
19
  * Kokimoki i18n Service
53
20
  *
@@ -61,7 +28,7 @@ export interface RequestTranslationResult {
61
28
  * - Pre-configured i18next instance creation
62
29
  * - Consistent HTTP-based loading in dev and prod
63
30
  * - URL resolution for translation namespaces
64
- * - AI-powered translation requests with polling support
31
+ * - AI-powered translation requests and status polling
65
32
  *
66
33
  * Access via `kmClient.i18n`
67
34
  *
@@ -75,16 +42,20 @@ export interface RequestTranslationResult {
75
42
  * });
76
43
  *
77
44
  * // Initialize with primary language
78
- * await kmClient.i18n.init('en');
45
+ * await kmClient.i18n.init();
79
46
  *
80
47
  * // Request AI translation and wait for it
81
48
  * await kmClient.i18n.requestTranslation('de');
82
- * await kmClient.i18n.pollTranslation('de', {
83
- * onProgress: (status) => console.log('Translation status:', status.status)
84
- * });
85
49
  *
86
- * // Now safe to switch language
87
- * i18next.changeLanguage('de');
50
+ * // Poll until ready
51
+ * const checkInterval = setInterval(async () => {
52
+ * const status = await kmClient.i18n.getTranslationStatus('de');
53
+ * if (status.status === 'available') {
54
+ * clearInterval(checkInterval);
55
+ * // Now safe to switch language
56
+ * i18next.changeLanguage('de');
57
+ * }
58
+ * }, 2000);
88
59
  * ```
89
60
  */
90
61
  export declare class KokimokiI18nService {
@@ -174,17 +145,25 @@ export declare class KokimokiI18nService {
174
145
  */
175
146
  getLanguages(): string[];
176
147
  /**
177
- * Get the status of all languages that have been requested for AI translation.
148
+ * Request AI translation for a target language.
149
+ *
150
+ * Triggers background AI translation jobs for all namespaces that are not yet available.
151
+ * Uses the build's configured primary language as the source.
178
152
  *
179
- * @returns Promise with array of language statuses
153
+ * @param lng - Target language code (e.g., 'de', 'fr', 'es')
154
+ * @returns Promise with the translation status
180
155
  *
181
156
  * @example
182
157
  * ```typescript
183
- * const { languages } = await kmClient.i18n.getAllLanguagesStatus();
184
- * // [{ lng: 'de', status: 'available' }, { lng: 'fr', status: 'processing' }]
158
+ * const status = await kmClient.i18n.requestTranslation('de');
159
+ *
160
+ * if (status === 'available') {
161
+ * // Already translated, switch immediately
162
+ * i18next.changeLanguage('de');
163
+ * }
185
164
  * ```
186
165
  */
187
- getAllLanguagesStatus(): Promise<AllLanguagesStatus>;
166
+ requestTranslation(lng: string): Promise<TranslationStatus>;
188
167
  /**
189
168
  * Get the translation status for a specific language.
190
169
  *
@@ -208,55 +187,4 @@ export declare class KokimokiI18nService {
208
187
  * ```
209
188
  */
210
189
  getTranslationStatus(lng: string): Promise<TranslationStatus>;
211
- /**
212
- * Request AI translation for a target language.
213
- *
214
- * Triggers background AI translation jobs for all namespaces that are not yet available.
215
- * Uses the build's configured primary language as the source.
216
- *
217
- * @param lng - Target language code (e.g., 'de', 'fr', 'es')
218
- * @returns Promise with the result of the request
219
- *
220
- * @example
221
- * ```typescript
222
- * const result = await kmClient.i18n.requestTranslation('de');
223
- *
224
- * if (result.status === 'already_available') {
225
- * // Already translated, switch immediately
226
- * i18next.changeLanguage('de');
227
- * } else {
228
- * // Poll until ready
229
- * await kmClient.i18n.pollTranslation('de', {
230
- * onProgress: (status) => console.log('Status:', status.status)
231
- * });
232
- * i18next.changeLanguage('de');
233
- * }
234
- * ```
235
- */
236
- requestTranslation(lng: string): Promise<RequestTranslationResult>;
237
- /**
238
- * Poll a translation request until all namespaces are available.
239
- *
240
- * @param lng - The language code to poll for.
241
- * @param options - Polling options (interval, timeout, progress callback).
242
- * @returns Promise that resolves when all namespaces are available.
243
- * @throws If the translation fails or times out.
244
- *
245
- * @example
246
- * ```typescript
247
- * // Request and poll with progress
248
- * await kmClient.i18n.requestTranslation('de');
249
- * await kmClient.i18n.pollTranslation('de', {
250
- * timeout: 60000,
251
- * onProgress: (status) => {
252
- * console.log('Overall:', status.status);
253
- * console.log('Namespaces:', status.namespaces);
254
- * }
255
- * });
256
- *
257
- * // Now safe to switch
258
- * i18next.changeLanguage('de');
259
- * ```
260
- */
261
- pollTranslation(lng: string, options?: PollOptions<TranslationStatus>): Promise<void>;
262
190
  }
@@ -14,7 +14,7 @@ import { getKmEnv } from "../utils/kokimoki-env";
14
14
  * - Pre-configured i18next instance creation
15
15
  * - Consistent HTTP-based loading in dev and prod
16
16
  * - URL resolution for translation namespaces
17
- * - AI-powered translation requests with polling support
17
+ * - AI-powered translation requests and status polling
18
18
  *
19
19
  * Access via `kmClient.i18n`
20
20
  *
@@ -28,16 +28,20 @@ import { getKmEnv } from "../utils/kokimoki-env";
28
28
  * });
29
29
  *
30
30
  * // Initialize with primary language
31
- * await kmClient.i18n.init('en');
31
+ * await kmClient.i18n.init();
32
32
  *
33
33
  * // Request AI translation and wait for it
34
34
  * await kmClient.i18n.requestTranslation('de');
35
- * await kmClient.i18n.pollTranslation('de', {
36
- * onProgress: (status) => console.log('Translation status:', status.status)
37
- * });
38
35
  *
39
- * // Now safe to switch language
40
- * i18next.changeLanguage('de');
36
+ * // Poll until ready
37
+ * const checkInterval = setInterval(async () => {
38
+ * const status = await kmClient.i18n.getTranslationStatus('de');
39
+ * if (status.status === 'available') {
40
+ * clearInterval(checkInterval);
41
+ * // Now safe to switch language
42
+ * i18next.changeLanguage('de');
43
+ * }
44
+ * }, 2000);
41
45
  * ```
42
46
  */
43
47
  export class KokimokiI18nService {
@@ -124,23 +128,31 @@ export class KokimokiI18nService {
124
128
  const namespaces = env.i18nNamespaces ?? [];
125
129
  const fallbackLng = this.options.fallbackLng ?? language;
126
130
  const defaultNS = this.options.defaultNS;
127
- this.initPromise = this.instance.init({
128
- lng: language,
129
- fallbackLng,
130
- ns: namespaces,
131
- defaultNS,
132
- // Wait for backend to load translations before resolving
133
- initImmediate: false,
134
- backend: {
135
- // i18next-http-backend passes lng as array, extract first element
136
- loadPath: (lngs, ns) => {
137
- const lang = Array.isArray(lngs) ? lngs[0] : lngs;
138
- return this.getNamespaceUrl(lang, ns);
131
+ // Wrap in a proper Promise to ensure we wait for backend loading
132
+ this.initPromise = new Promise((resolve, reject) => {
133
+ this.instance.init({
134
+ lng: language,
135
+ fallbackLng,
136
+ ns: namespaces,
137
+ defaultNS,
138
+ backend: {
139
+ // i18next-http-backend passes lng as array, extract first element
140
+ loadPath: (lngs, ns) => {
141
+ const lang = Array.isArray(lngs) ? lngs[0] : lngs;
142
+ return this.getNamespaceUrl(lang, ns);
143
+ },
144
+ },
145
+ interpolation: {
146
+ escapeValue: false,
139
147
  },
140
- },
141
- interpolation: {
142
- escapeValue: false,
143
- },
148
+ }, (err) => {
149
+ if (err) {
150
+ reject(err);
151
+ }
152
+ else {
153
+ resolve();
154
+ }
155
+ });
144
156
  });
145
157
  return this.initPromise;
146
158
  }
@@ -200,22 +212,31 @@ export class KokimokiI18nService {
200
212
  return getKmEnv().i18nLanguages ?? [];
201
213
  }
202
214
  /**
203
- * Get the status of all languages that have been requested for AI translation.
215
+ * Request AI translation for a target language.
204
216
  *
205
- * @returns Promise with array of language statuses
217
+ * Triggers background AI translation jobs for all namespaces that are not yet available.
218
+ * Uses the build's configured primary language as the source.
219
+ *
220
+ * @param lng - Target language code (e.g., 'de', 'fr', 'es')
221
+ * @returns Promise with the translation status
206
222
  *
207
223
  * @example
208
224
  * ```typescript
209
- * const { languages } = await kmClient.i18n.getAllLanguagesStatus();
210
- * // [{ lng: 'de', status: 'available' }, { lng: 'fr', status: 'processing' }]
225
+ * const status = await kmClient.i18n.requestTranslation('de');
226
+ *
227
+ * if (status === 'available') {
228
+ * // Already translated, switch immediately
229
+ * i18next.changeLanguage('de');
230
+ * }
211
231
  * ```
212
232
  */
213
- async getAllLanguagesStatus() {
214
- const res = await fetch(`${this.client.apiUrl}/i18n`, {
215
- method: "GET",
233
+ async requestTranslation(lng) {
234
+ const res = await fetch(`${this.client.apiUrl}/i18n/${encodeURIComponent(lng)}/translate`, {
235
+ method: "POST",
216
236
  headers: this.client.apiHeaders,
217
237
  });
218
- return await res.json();
238
+ const { status } = await res.json();
239
+ return status;
219
240
  }
220
241
  /**
221
242
  * Get the translation status for a specific language.
@@ -244,89 +265,7 @@ export class KokimokiI18nService {
244
265
  method: "GET",
245
266
  headers: this.client.apiHeaders,
246
267
  });
247
- return await res.json();
248
- }
249
- /**
250
- * Request AI translation for a target language.
251
- *
252
- * Triggers background AI translation jobs for all namespaces that are not yet available.
253
- * Uses the build's configured primary language as the source.
254
- *
255
- * @param lng - Target language code (e.g., 'de', 'fr', 'es')
256
- * @returns Promise with the result of the request
257
- *
258
- * @example
259
- * ```typescript
260
- * const result = await kmClient.i18n.requestTranslation('de');
261
- *
262
- * if (result.status === 'already_available') {
263
- * // Already translated, switch immediately
264
- * i18next.changeLanguage('de');
265
- * } else {
266
- * // Poll until ready
267
- * await kmClient.i18n.pollTranslation('de', {
268
- * onProgress: (status) => console.log('Status:', status.status)
269
- * });
270
- * i18next.changeLanguage('de');
271
- * }
272
- * ```
273
- */
274
- async requestTranslation(lng) {
275
- const res = await fetch(`${this.client.apiUrl}/i18n/${encodeURIComponent(lng)}/translate`, {
276
- method: "POST",
277
- headers: this.client.apiHeaders,
278
- });
279
- const data = await res.json();
280
- return { lng, ...data };
281
- }
282
- /**
283
- * Poll a translation request until all namespaces are available.
284
- *
285
- * @param lng - The language code to poll for.
286
- * @param options - Polling options (interval, timeout, progress callback).
287
- * @returns Promise that resolves when all namespaces are available.
288
- * @throws If the translation fails or times out.
289
- *
290
- * @example
291
- * ```typescript
292
- * // Request and poll with progress
293
- * await kmClient.i18n.requestTranslation('de');
294
- * await kmClient.i18n.pollTranslation('de', {
295
- * timeout: 60000,
296
- * onProgress: (status) => {
297
- * console.log('Overall:', status.status);
298
- * console.log('Namespaces:', status.namespaces);
299
- * }
300
- * });
301
- *
302
- * // Now safe to switch
303
- * i18next.changeLanguage('de');
304
- * ```
305
- */
306
- async pollTranslation(lng, options) {
307
- const pollInterval = Math.max(1000, options?.pollInterval ?? 2000);
308
- const timeout = options?.timeout ?? 120000;
309
- const startTime = Date.now();
310
- while (true) {
311
- const status = await this.getTranslationStatus(lng);
312
- // Call progress callback
313
- options?.onProgress?.(status);
314
- if (status.status === "available") {
315
- return;
316
- }
317
- // Check for failed namespaces
318
- const failedNamespaces = Object.entries(status.namespaces)
319
- .filter(([_, ns]) => ns === "failed")
320
- .map(([name]) => name);
321
- if (failedNamespaces.length > 0) {
322
- throw new Error(`Translation failed for namespaces: ${failedNamespaces.join(", ")}`);
323
- }
324
- // Check timeout
325
- if (Date.now() - startTime > timeout) {
326
- throw new Error(`Translation timed out after ${timeout}ms`);
327
- }
328
- // Wait before next poll
329
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
330
- }
268
+ const { status } = await res.json();
269
+ return status;
331
270
  }
332
271
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const KOKIMOKI_APP_VERSION = "3.1.1";
1
+ export declare const KOKIMOKI_APP_VERSION = "3.1.3";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated file. Do not edit manually.
2
- export const KOKIMOKI_APP_VERSION = '3.1.1';
2
+ export const KOKIMOKI_APP_VERSION = '3.1.3';
@@ -6,8 +6,7 @@ applyTo: "**/*.ts,**/*.tsx"
6
6
  # AI Integration
7
7
 
8
8
  Built-in methods for AI text generation and image transformation. No API keys required.
9
- All generation methods are **async job-based** - they submit a job and return immediately with a `jobId`.
10
- Use the corresponding poll method to wait for the result.
9
+ All generation methods are **async job-based** - they submit a job and return immediately with a `jobId`. Use `getJob()` to poll for the result.
11
10
 
12
11
  For core SDK concepts, see [Kokimoki SDK](./kokimoki-sdk.instructions.md).
13
12
 
@@ -19,7 +18,7 @@ Access AI API via `kmClient.ai`.
19
18
  - Text generation with configurable creativity (temperature)
20
19
  - Structured JSON output with Zod schema validation
21
20
  - AI-powered image generation and modification
22
- - Job-based async processing with polling support
21
+ - Job-based async processing
23
22
  - Resumable after page reload (persist jobId)
24
23
 
25
24
  ## Available Models
@@ -45,7 +44,7 @@ Access AI API via `kmClient.ai`.
45
44
 
46
45
  ### ai.generateText(req): Promise<{ jobId: string }>
47
46
 
48
- Submits a text generation job. Use `pollText()` to wait for the result.
47
+ Submits a text generation job. Use `getJob()` to poll the result.
49
48
 
50
49
  **Parameters:**
51
50
 
@@ -67,12 +66,13 @@ const { jobId } = await kmClient.ai.generateText({
67
66
  });
68
67
 
69
68
  // Poll for result
70
- const text = await kmClient.ai.pollText(jobId);
69
+ const job = await kmClient.ai.getJob<string>(jobId);
70
+ console.log("Generated quest:", job.result);
71
71
  ```
72
72
 
73
73
  ### ai.generateJson<T>(req): Promise<{ jobId: string }>
74
74
 
75
- Submits a JSON generation job with Zod schema validation. Use `pollJson()` to wait for the result with type inference.
75
+ Submits a JSON generation job with Zod schema validation. Use `getJob<T>()` to wait for the result with type inference.
76
76
 
77
77
  **Parameters:**
78
78
 
@@ -107,8 +107,8 @@ const { jobId } = await kmClient.ai.generateJson({
107
107
  await saveJobId(jobId);
108
108
 
109
109
  // Poll with schema for type inference and validation
110
- const enemy = await kmClient.ai.pollJson(jobId, { schema: enemySchema });
111
- console.log(enemy.name, enemy.health); // Fully typed!
110
+ const enemy = await kmClient.ai.getJob<Enemy>(jobId);
111
+ console.log(enemy.result?.name, enemy.result?.health); // Fully typed!
112
112
  ```
113
113
 
114
114
  ```typescript
@@ -127,13 +127,13 @@ const { jobId } = await kmClient.ai.generateJson({
127
127
  prompt: "Create 5 history quiz questions with 4 options each",
128
128
  });
129
129
 
130
- const questions = await kmClient.ai.pollJson(jobId, { schema: questionSchema });
131
- questions.forEach((q) => console.log(q.question));
130
+ const questions = await kmClient.ai.getJob<typeof questionSchema>(jobId);
131
+ questions.result?.forEach((q) => console.log(q.question));
132
132
  ```
133
133
 
134
134
  ### ai.generateImage(req): Promise<{ jobId: string }>
135
135
 
136
- Submits an image generation/modification job. Use `pollImage()` to wait for the result.
136
+ Submits an image generation/modification job. Use `getJob()` to wait for the result.
137
137
  The result is stored as `Upload` object (see [Storage](./kokimoki-storage.instructions.md)).
138
138
 
139
139
  **Parameters:**
@@ -152,8 +152,8 @@ const { jobId } = await kmClient.ai.generateImage({
152
152
  tags: ["background", "ai-generated"],
153
153
  });
154
154
 
155
- const upload = await kmClient.ai.pollImage(jobId);
156
- console.log(upload.url); // CDN URL to the generated image
155
+ const job = await kmClient.ai.getJob<string>(jobId);
156
+ console.log("Generated image:", job.result); // CDN URL to the generated image
157
157
  ```
158
158
 
159
159
  ```typescript
@@ -164,12 +164,13 @@ const { jobId } = await kmClient.ai.generateImage({
164
164
  tags: ["art", "ai-generated"],
165
165
  });
166
166
 
167
- const upload: Upload = await kmClient.ai.pollImage(jobId);
167
+ const job = await kmClient.ai.getJob<string>(jobId);
168
+ console.log("Generated image:", job.result); // CDN URL to the generated image
168
169
  ```
169
170
 
170
171
  ### ai.getJob(jobId): Promise<AiJob>
171
172
 
172
- Get the current status of an AI job without polling.
173
+ Get the current status of an AI job.
173
174
 
174
175
  **Parameters:**
175
176
 
@@ -199,68 +200,6 @@ if (job.status === "completed") {
199
200
  }
200
201
  ```
201
202
 
202
- ### ai.pollText(jobId, options?): Promise<string>
203
-
204
- Poll a text generation job until completion.
205
-
206
- **Parameters:**
207
-
208
- - **jobId**: `string` The job ID to poll
209
- - **options.pollInterval**: `number` Polling interval in ms (default: 2000, min: 1000)
210
- - **options.timeout**: `number` Timeout in ms (default: 120000)
211
- - **options.onProgress**: `(status: AiJobStatus) => void` Progress callback
212
-
213
- **Example:**
214
-
215
- ```typescript
216
- const text = await kmClient.ai.pollText(jobId, {
217
- timeout: 60000,
218
- onProgress: (status) => console.log("Status:", status),
219
- });
220
- ```
221
-
222
- ### ai.pollJson<T>(jobId, options): Promise<T>
223
-
224
- Poll a JSON generation job until completion with schema validation.
225
-
226
- **Parameters:**
227
-
228
- - **jobId**: `string` The job ID to poll
229
- - **options.schema**: `ZodType` Zod schema for validation and type inference (required)
230
- - **options.pollInterval**: `number` Polling interval in ms (default: 2000, min: 1000)
231
- - **options.timeout**: `number` Timeout in ms (default: 120000)
232
- - **options.onProgress**: `(status: AiJobStatus) => void` Progress callback
233
-
234
- **Example:**
235
-
236
- ```typescript
237
- const enemy = await kmClient.ai.pollJson(jobId, {
238
- schema: enemySchema,
239
- timeout: 60000,
240
- onProgress: (status) => console.log("Status:", status),
241
- });
242
- ```
243
-
244
- ### ai.pollImage(jobId, options?): Promise<Upload>
245
-
246
- Poll an image generation job until completion.
247
-
248
- **Parameters:**
249
-
250
- - **jobId**: `string` The job ID to poll
251
- - **options.pollInterval**: `number` Polling interval in ms (default: 2000, min: 1000)
252
- - **options.timeout**: `number` Timeout in ms (default: 120000)
253
- - **options.onProgress**: `(status: AiJobStatus) => void` Progress callback
254
-
255
- **Example:**
256
-
257
- ```typescript
258
- const upload = await kmClient.ai.pollImage(jobId, {
259
- timeout: 60000,
260
- onProgress: (status) => console.log("Status:", status),
261
- });
262
- ```
263
-
264
203
  ## Common Patterns
265
204
 
266
205
  ### Example: Persist Jobs Across Page Reloads
@@ -297,20 +236,24 @@ const { jobId } = await kmClient.ai.generateText({
297
236
  prompt: "Write a story",
298
237
  });
299
238
 
300
- const text = await kmClient.ai.pollText(jobId, {
301
- onProgress: (status) => {
302
- if (status === "queued") setLoadingText("Waiting in queue...");
303
- if (status === "processing") setLoadingText("Generating...");
304
- },
305
- });
306
- setLoading(false);
239
+ const checkInterval = setInterval(async () => {
240
+ const job = await kmClient.ai.getJob<string>(jobId);
241
+ if (job.status === "completed") {
242
+ console.log("Generated text:", job.result);
243
+ clearInterval(checkInterval);
244
+ setLoading(false);
245
+ } else if (job.status === "failed") {
246
+ console.error("AI generation failed:", job.error);
247
+ clearInterval(checkInterval);
248
+ setLoading(false);
249
+ }
250
+ }, 2000);
307
251
  ```
308
252
 
309
253
  ## Key Points
310
254
 
311
- - **Job-based**: All generation methods return `{ jobId }` immediately, use poll methods for results
255
+ - **Job-based**: All generation methods return `{ jobId }` immediately, use `getJob()` to poll for results
312
256
  - **Resumable**: Save `jobId` to store to resume polling after page reload
313
257
  - **Zod Schemas**: Use Zod schemas for `generateJson` to get automatic type inference and validation
314
258
  - **Temperature**: Range 0.0 (factual) to 1.0 (creative)
315
- - **Polling Options**: Configure `pollInterval`, `timeout`, and `onProgress` callback
316
259
  - **Image URLs**: Gemini models support multimodal input via `imageUrls` parameter
@@ -110,36 +110,12 @@ const url = kmClient.i18n.getNamespaceUrl("en", "game");
110
110
 
111
111
  ## AI Translation API
112
112
 
113
- ### i18n.getAllLanguagesStatus(): Promise<AllLanguagesStatus>
114
-
115
- Get the status of all languages that have been requested for AI translation.
116
-
117
- ```typescript
118
- interface AllLanguagesStatus {
119
- languages: { lng: string; status: LanguageStatus }[];
120
- }
121
-
122
- type LanguageStatus = "available" | "processing" | "failed" | "partial";
123
- ```
124
-
125
- **Example:**
126
-
127
- ```typescript
128
- const { languages } = await kmClient.i18n.getAllLanguagesStatus();
129
- // [{ lng: 'de', status: 'available' }, { lng: 'fr', status: 'processing' }]
130
- ```
131
-
132
- ### i18n.getTranslationStatus(lng): Promise<TranslationStatus>
113
+ ### i18n.getTranslationStatus(lng): Promise<{ status: TranslationStatus }>
133
114
 
134
115
  Get the translation status for a specific language.
135
116
 
136
117
  ```typescript
137
- interface TranslationStatus {
138
- status: "available" | "processing" | "not_available";
139
- namespaces: Record<string, NamespaceStatus>;
140
- }
141
-
142
- type NamespaceStatus = "available" | "processing" | "failed" | "not_available";
118
+ type TranslationStatus = "available" | "processing" | "failed";
143
119
  ```
144
120
 
145
121
  **Example:**
@@ -147,117 +123,34 @@ type NamespaceStatus = "available" | "processing" | "failed" | "not_available";
147
123
  ```typescript
148
124
  const status = await kmClient.i18n.getTranslationStatus("de");
149
125
 
150
- if (status.status === "available") {
126
+ if (status === "available") {
151
127
  i18next.changeLanguage("de");
152
- } else if (status.status === "processing") {
153
- // Show loading indicator
154
- } else {
155
- // Request translation
156
- await kmClient.i18n.requestTranslation("de");
157
128
  }
158
129
  ```
159
130
 
160
- ### i18n.requestTranslation(lng): Promise<RequestTranslationResult>
131
+ ### i18n.requestTranslation(lng): Promise<TranslationStatus>
161
132
 
162
133
  Request AI translation for a target language. Triggers background AI translation jobs for all namespaces.
163
134
 
164
135
  ```typescript
165
- interface RequestTranslationResult {
166
- lng: string;
167
- status: "started" | "already_processing" | "already_available";
168
- }
136
+ type TranslationStatus = "available" | "processing" | "failed";
169
137
  ```
170
138
 
171
139
  **Example:**
172
140
 
173
141
  ```typescript
174
- const result = await kmClient.i18n.requestTranslation("de");
142
+ const status = await kmClient.i18n.requestTranslation("de");
175
143
 
176
- if (result.status === "already_available") {
144
+ if (status === "already_available") {
177
145
  // Already translated, switch immediately
178
146
  i18next.changeLanguage("de");
179
147
  } else {
180
- // Poll until ready
181
- await kmClient.i18n.pollTranslation("de");
182
- i18next.changeLanguage("de");
148
+ // Poll until ready using getTranslationStatus
183
149
  }
184
150
  ```
185
151
 
186
- ### i18n.pollTranslation(lng, options?): Promise<void>
187
-
188
- Poll a translation request until all namespaces are available.
189
-
190
- **Parameters:**
191
-
192
- - **lng**: `string` Language code to poll for
193
- - **options.pollInterval**: `number` Polling interval in ms (default: 2000, min: 1000)
194
- - **options.timeout**: `number` Timeout in ms (default: 120000)
195
- - **options.onProgress**: `(status: TranslationStatus) => void` Progress callback
196
-
197
- **Example:**
198
-
199
- ```typescript
200
- await kmClient.i18n.pollTranslation("de", {
201
- timeout: 60000,
202
- onProgress: (status) => {
203
- console.log("Overall:", status.status);
204
- console.log("Namespaces:", status.namespaces);
205
- },
206
- });
207
-
208
- // Now safe to switch
209
- i18next.changeLanguage("de");
210
- ```
211
-
212
152
  ## Common Patterns
213
153
 
214
- ### Example: Language Switcher with AI Translation
215
-
216
- ```tsx
217
- import { useState } from "react";
218
- import { useTranslation } from "react-i18next";
219
- import { getKmClient } from "@kokimoki/app";
220
-
221
- const kmClient = getKmClient();
222
-
223
- const LanguageSwitcher = () => {
224
- const { i18n } = useTranslation();
225
- const [loading, setLoading] = useState(false);
226
-
227
- const switchLanguage = async (lng: string) => {
228
- // Check if translation is available
229
- const status = await kmClient.i18n.getTranslationStatus(lng);
230
-
231
- if (status.status === "available") {
232
- i18n.changeLanguage(lng);
233
- return;
234
- }
235
-
236
- // Request and wait for AI translation
237
- setLoading(true);
238
- await kmClient.i18n.requestTranslation(lng);
239
- await kmClient.i18n.pollTranslation(lng, {
240
- onProgress: (s) => console.log(`Translating: ${s.status}`),
241
- });
242
- setLoading(false);
243
-
244
- i18n.changeLanguage(lng);
245
- };
246
-
247
- return (
248
- <select
249
- value={i18n.language}
250
- onChange={(e) => switchLanguage(e.target.value)}
251
- disabled={loading}
252
- >
253
- <option value="en">English</option>
254
- <option value="de">Deutsch</option>
255
- <option value="fr">Français</option>
256
- </select>
257
- );
258
- };
259
- ```
260
-
261
154
  ### Example: Using Translations in Components
262
155
 
263
156
  ```tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kokimoki/app",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "type": "module",
5
5
  "description": "Kokimoki app",
6
6
  "main": "dist/index.js",