ugly-app 0.1.42 → 0.1.44

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/README.md CHANGED
@@ -6,9 +6,9 @@ A full-stack TypeScript framework for building production-ready web applications
6
6
 
7
7
  - **Server**: Express + WebSocket with type-safe RPC and Zod validation
8
8
  - **Client**: React + Vite with typed routing, lazy pages, and popup management
9
- - **Database**: MongoDB with typed collections, indexes, migrations, and live document tracking
9
+ - **Database**: MongoDB with typed collections, dot-notation updates, indexes, migrations, and live document tracking
10
10
  - **Auth**: JWT + HttpOnly cookies, ugly.bot OAuth out of the box, extensible via `AuthProvider`
11
- - **AI**: Text generation (Together, Claude, OpenAI, Google, Groq, Fireworks) + image generation (Together, FAL, Google, Wavespeed)
11
+ - **AI**: Text generation (Together, Claude, OpenAI, Google, Groq, Fireworks) + image generation (Together, FAL, Google, Wavespeed) + embeddings + STT/TTS
12
12
  - **Storage**: Cloudflare R2 / AWS S3 with presigned uploads
13
13
  - **CLI**: `ugly-app` commands for dev, build, deploy, migrations, logs, and auth utilities
14
14
 
@@ -143,7 +143,7 @@ export const requests = defineRequests({
143
143
 
144
144
  - `fn({ input, output })` — defines a mutation (write operation). `input` and `output` are Zod schemas.
145
145
  - `req({ input, output })` — defines a query (read operation).
146
- - `defineFunctions()` / `defineRequests()` — identity wrappers that preserve types.
146
+ - `defineFunctions()` / `defineRequests()` — identity wrappers that preserve types. `defineCalls` is an alias for `defineFunctions`.
147
147
  - `z` is re-exported from Zod for convenience.
148
148
 
149
149
  ### Collections (`shared/collections.ts`)
@@ -164,11 +164,14 @@ export const collections = defineCollections({
164
164
  });
165
165
  ```
166
166
 
167
- Collection meta fields:
168
- - `cache` — enable in-memory caching
169
- - `trackable` — allow real-time `trackDoc`/`trackDocs` subscriptions
170
- - `public` — accessible without auth
171
- - `parent` — parent collection name for cascade deletes (or `null`)
167
+ Each collection definition has:
168
+ - `type` — phantom field for TypeScript type inference (never read at runtime)
169
+ - `meta` — runtime metadata:
170
+ - `cache` — enable in-memory caching
171
+ - `trackable` — allow real-time `trackDoc`/`trackDocs` subscriptions
172
+ - `public` — accessible without auth
173
+ - `parent` — parent collection name for cascade deletes (or `null`)
174
+ - `onDelete?` — optional callback invoked on document deletion
172
175
 
173
176
  All documents extend `DBObject`: `{ id: string, version: number, created: Date, updated: Date }`.
174
177
 
@@ -282,6 +285,10 @@ Wraps your app with context for `useApp()`. Provides user info, socket access, p
282
285
  | `splashDone(step)` | Mark a splash screen step as complete |
283
286
  | `localizer(key, params?)` | Localize a string key |
284
287
 
288
+ `useAppOptional()` returns the same context or `null` if outside `<AppProvider>`.
289
+
290
+ `useLocalizer()` returns the localizer function (falls back to identity if outside provider).
291
+
285
292
  ### Router
286
293
 
287
294
  #### Setup (`client/router.ts`)
@@ -315,7 +322,18 @@ export const allPages = {
315
322
  ```
316
323
 
317
324
  - **`lazyPage(factory)`** — lazy-imports a default-exported React component. The component receives route params as props.
318
- - **`lazyPageLoader(factory)`** — lazy-imports an async loader function `(params) => Promise<ReactElement>` for routes that need data fetching before render. The loader file is the chunk boundary.
325
+ - **`lazyPageLoader(factory)`** — lazy-imports an async loader function `(params) => Promise<ReactElement>` for routes that need data fetching before render. The loader file is the chunk boundary — it can statically import its page component.
326
+
327
+ **`lazyPageLoader` example:**
328
+
329
+ ```typescript
330
+ // pages/SlowPageLoader.tsx
331
+ import SlowPage from './SlowPage'; // static import OK — same chunk
332
+ export default async function PageLoader({ id }: { id: string }) {
333
+ const data = await fetchSlowData(id);
334
+ return <SlowPage {...data} />;
335
+ }
336
+ ```
319
337
 
320
338
  #### Navigation with `useRouter()`
321
339
 
@@ -354,10 +372,12 @@ handle.hide(); // dismiss programmatically
354
372
  ```
355
373
 
356
374
  Popup modes:
357
- - **`block`** — dark backdrop (40% opacity), clicking backdrop does NOT dismiss
375
+ - **`block`** (default) — dark backdrop (40% opacity), clicking backdrop does NOT dismiss
358
376
  - **`transient`** — light backdrop (20% opacity), clicking backdrop dismisses
359
377
  - **`contextMenu`** — same as transient, intended for menus and pickers
360
378
 
379
+ `renderLayer` receives `{ content, spring, hide }` — `spring` is an animated value from 0 to 1, `hide` is a function to close the popup.
380
+
361
381
  #### `Link` component
362
382
 
363
383
  ```tsx
@@ -370,6 +390,49 @@ import { Link } from 'ugly-app/client';
370
390
 
371
391
  Renders an `<a>` tag with the correct `href`. Intercepts clicks for client-side navigation (ctrl/cmd+click opens in new tab).
372
392
 
393
+ #### Animation system
394
+
395
+ Built-in spring-like animation primitives for transitions and popups.
396
+
397
+ ```typescript
398
+ import {
399
+ Animated,
400
+ createAnimatedValue,
401
+ useAnimatedValue,
402
+ easingFunctions,
403
+ FadeIn,
404
+ SlideFromBottom,
405
+ SlideFromRight,
406
+ } from 'ugly-app/client';
407
+
408
+ // Create an animated value (0→1 spring)
409
+ const spring = createAnimatedValue(0);
410
+ spring.start(1, { duration: 300, easing: easingFunctions.easeOut });
411
+
412
+ // Use in components
413
+ <Animated.div style={{ opacity: spring.to((v) => String(v)) }}>
414
+ Content
415
+ </Animated.div>
416
+
417
+ // Hook version — creates and manages an animated value in a component
418
+ const anim = useAnimatedValue(0);
419
+
420
+ // Pre-built entrance animations (wrap any children)
421
+ <FadeIn>{children}</FadeIn>
422
+ <SlideFromBottom>{children}</SlideFromBottom>
423
+ <SlideFromRight>{children}</SlideFromRight>
424
+ ```
425
+
426
+ Available easings: `easingFunctions.linear`, `easeIn`, `easeOut`, `easeInOut`.
427
+
428
+ #### Screenshot capture
429
+
430
+ ```typescript
431
+ import { captureScreenshot } from 'ugly-app/client';
432
+
433
+ const dataUrl = await captureScreenshot(); // captures the current viewport
434
+ ```
435
+
373
436
  ---
374
437
 
375
438
  ## Auth
@@ -423,7 +486,7 @@ configurator.setAuth({
423
486
  await ctx.db.setDoc(collections.note, doc);
424
487
  await ctx.db.setDoc(collections.note, doc, { skipIfExists: true });
425
488
 
426
- // Partial update — only specified fields
489
+ // Partial update — only specified fields (supports dot-notation paths)
427
490
  await ctx.db.setDocFields(collections.note, id, { title: 'New title' });
428
491
 
429
492
  // Partial update — returns null if document doesn't exist (no error)
@@ -447,6 +510,10 @@ const notes = await ctx.db.getDocs(collections.note, { userId }, { sort: { creat
447
510
  const results = await ctx.db.getQuery<MyResult>('note', pipeline, { skip, limit });
448
511
  const count = await ctx.db.getQueryCount('note', pipeline);
449
512
  const raw = await ctx.db.getQueryRaw<T>('note', pipeline);
513
+
514
+ // Dynamic/untyped access (when collection name is a runtime string)
515
+ const doc = await ctx.db.rawGetDoc(collectionName, id);
516
+ const docs = await ctx.db.rawGetDocs(collectionName, filter);
450
517
  ```
451
518
 
452
519
  ### Deleting
@@ -474,7 +541,7 @@ import { dbDefaults } from 'ugly-app/shared';
474
541
  // dbDefaults() returns { version: 1, created: new Date(), updated: new Date() }
475
542
  const doc = { id: newId(), ...dbDefaults(), title: 'Hello' };
476
543
 
477
- // createUserHelper — typed user CRUD
544
+ // createUserHelper — typed user CRUD with get, set, update methods
478
545
  const userHelper = createUserHelper<User>(collections.user);
479
546
  const user = await userHelper.get(db, userId);
480
547
  await userHelper.set(db, { id: userId, ...dbDefaults(), email });
@@ -494,6 +561,8 @@ export const dbIndexes = defineDbIndexes({
494
561
  });
495
562
  ```
496
563
 
564
+ Index types supported: standard (`IndexDef`), text search (`SearchIndexDef`), and vector/embedding (`VectorIndexDef` with cosine, euclidean, or dotProduct similarity).
565
+
497
566
  Run `npm run db:init` to create/update indexes.
498
567
 
499
568
  ---
@@ -547,13 +616,33 @@ import { createImageGenClient } from 'ugly-app';
547
616
  const imageGen = createImageGenClient();
548
617
  ```
549
618
 
619
+ ### Embeddings
620
+
621
+ ```typescript
622
+ import { createEmbeddingClient, cosineSimilarity } from 'ugly-app';
623
+ const embeddings = createEmbeddingClient();
624
+ const similarity = cosineSimilarity(vectorA, vectorB);
625
+ ```
626
+
627
+ ### Speech-to-text / Text-to-speech
628
+
629
+ ```typescript
630
+ // Server-side providers
631
+ import { registerSTTProvider, groqWhisperSTTProvider, loadVADModel } from 'ugly-app';
632
+ import { registerTTSProvider, azureTTSProvider } from 'ugly-app';
633
+
634
+ // Client-side hooks
635
+ import { useSTT, useTTS, AudioPlayer, AudioRecorder } from 'ugly-app/client';
636
+ ```
637
+
550
638
  ### Custom providers
551
639
 
552
640
  ```typescript
553
- import { registerTextGenProvider, registerImageGenProvider } from 'ugly-app';
641
+ import { registerTextGenProvider, registerImageGenProvider, registerEmbeddingProvider } from 'ugly-app';
554
642
 
555
643
  registerTextGenProvider('myProvider', myTextGenImplementation);
556
644
  registerImageGenProvider('myProvider', myImageGenImplementation);
645
+ registerEmbeddingProvider('myProvider', myEmbeddingImplementation);
557
646
  ```
558
647
 
559
648
  ### Client-side AI calls
@@ -588,7 +677,7 @@ const url = storage.url('public', destKey);
588
677
  const { uploadUrl, resultUrl } = await storage.presignedPut('temp', key);
589
678
  ```
590
679
 
591
- Buckets: `'public'` and `'temp'`.
680
+ Buckets: `'public'` and `'temp'`. Supports Cloudflare R2 (production) or MinIO (dev).
592
681
 
593
682
  Static build-time assets go in `client/public/`. Never hardcode `/asset/...` paths — use the `buildId` from `shared/Build.ts`.
594
683
 
@@ -626,7 +715,7 @@ sub(); // unsubscribe
626
715
  ### Redis
627
716
 
628
717
  ```typescript
629
- import { getRedisClient } from 'ugly-app';
718
+ import { getRedisClient, redisGet, redisSet, redisDel, redisPublish, redisSubscribe } from 'ugly-app';
630
719
  const redis = getRedisClient();
631
720
  ```
632
721
 
@@ -641,6 +730,15 @@ configurator.setWorkerQueue(queue);
641
730
  await enqueueTask('taskName', payload);
642
731
  ```
643
732
 
733
+ ### Billing
734
+
735
+ ```typescript
736
+ import { initBillingGateway, getBillingGateway } from 'ugly-app';
737
+
738
+ await initBillingGateway({ /* config */ });
739
+ const billing = getBillingGateway();
740
+ ```
741
+
644
742
  ### Client error capture
645
743
 
646
744
  ```typescript
@@ -672,21 +770,20 @@ Server-side, `getFeedbackHandlers(maintainBotUserId)` provides the RPC handlers
672
770
  await ctx.rateLimit.check();
673
771
  ```
674
772
 
675
- ### Embeddings
676
-
677
- ```typescript
678
- import { createEmbeddingClient, registerEmbeddingProvider } from 'ugly-app';
679
- const embeddings = createEmbeddingClient();
680
- ```
681
-
682
- ### Billing
773
+ ---
683
774
 
684
- ```typescript
685
- import { initBillingGateway, getBillingGateway } from 'ugly-app';
775
+ ## Built-in endpoints
686
776
 
687
- await initBillingGateway({ /* config */ });
688
- const billing = getBillingGateway();
689
- ```
777
+ | Endpoint | Description |
778
+ |----------|-------------|
779
+ | `GET /health` | Health check — returns `{ status: 'ok', timestamp }` |
780
+ | `POST /auth/verify` | Exchange OAuth code for a session cookie |
781
+ | `POST /auth/logout` | Clear the auth cookie |
782
+ | `GET /auth/token` | Refresh and return the current token |
783
+ | `GET /auth/url` | Get the OAuth popup URL |
784
+ | `POST /ai/request` | AI proxy — forwards to ugly.bot (requires auth) |
785
+ | `POST /api/client-error` | Client-side error capture |
786
+ | `GET /my_feedback` | User feedback history (markdown, requires auth) |
690
787
 
691
788
  ---
692
789
 
@@ -734,6 +831,7 @@ Run with `npm run db:migrate`. Use `npm run db:migrate -- --status` to preview p
734
831
  | Variable | Description |
735
832
  |----------|-------------|
736
833
  | `JWT_SECRET` | **Required** — signs auth tokens |
834
+ | `JWT_EXPIRY_SECONDS` | Token lifetime (optional) |
737
835
  | `MONGODB_URI` | MongoDB connection string |
738
836
  | `PORT` | Server port (default: 3000) |
739
837
  | `NODE_ENV` | `development` or `production` |
@@ -761,3 +859,37 @@ Client-side variables must be prefixed with `VITE_`.
761
859
  ## Tech stack
762
860
 
763
861
  Node.js · TypeScript · Express · React 19 · Vite · Tailwind CSS · MongoDB · NATS · Redis · Cloudflare R2 · Zod · JWT (jose) · ugly.bot OAuth
862
+
863
+ ---
864
+
865
+ ## Shared utilities
866
+
867
+ `ugly-app/shared` exports common helpers used by both server and client:
868
+
869
+ ```typescript
870
+ import {
871
+ isDefined,
872
+ compact,
873
+ debounce,
874
+ formatDate,
875
+ formatRelativeTime,
876
+ oneSecond,
877
+ oneMinute,
878
+ oneHour,
879
+ oneDay,
880
+ oneWeek,
881
+ } from 'ugly-app/shared';
882
+
883
+ isDefined(value); // type guard — true if not null/undefined
884
+ compact([1, null, 2, undefined]); // [1, 2] — filters out null/undefined
885
+ const debouncedFn = debounce(fn, 300);
886
+ formatDate(new Date()); // locale-formatted date string
887
+ formatRelativeTime(new Date()); // "2 hours ago", "just now", etc.
888
+
889
+ // Time constants (milliseconds)
890
+ oneSecond; // 1000
891
+ oneMinute; // 60_000
892
+ oneHour; // 3_600_000
893
+ oneDay; // 86_400_000
894
+ oneWeek; // 604_800_000
895
+ ```
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"AA8DA,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DxE"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"AAyFA,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DxE"}
@@ -38,7 +38,33 @@ function generateEnv(examplePath, projectName) {
38
38
  })
39
39
  .join('\n');
40
40
  }
41
+ /** Locate the `code` binary — checks PATH then known macOS app bundle locations. */
42
+ function findCodeCli() {
43
+ try {
44
+ execSync('code --version', { stdio: 'ignore' });
45
+ return 'code';
46
+ }
47
+ catch { /* not in PATH */ }
48
+ const candidates = process.platform === 'win32'
49
+ ? [
50
+ path.join(process.env['LOCALAPPDATA'] ?? '', 'Programs\\Microsoft VS Code\\bin\\code.cmd'),
51
+ path.join(process.env['ProgramFiles'] ?? '', 'Microsoft VS Code\\bin\\code.cmd'),
52
+ path.join(process.env['ProgramFiles(x86)'] ?? '', 'Microsoft VS Code\\bin\\code.cmd'),
53
+ ]
54
+ : [
55
+ '/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code',
56
+ path.join(os.homedir(), 'Applications/Visual Studio Code.app/Contents/Resources/app/bin/code'),
57
+ ];
58
+ for (const p of candidates) {
59
+ if (fs.existsSync(p))
60
+ return `"${p}"`;
61
+ }
62
+ return null;
63
+ }
41
64
  async function installVsixExtension() {
65
+ const codeCli = findCodeCli();
66
+ if (!codeCli)
67
+ return; // VSCode not found — skip silently
42
68
  const url = 'https://ugly.bot/asset/ugly-bot.vsix';
43
69
  const vsixPath = path.join(os.tmpdir(), 'ugly-bot.vsix');
44
70
  try {
@@ -48,11 +74,11 @@ async function installVsixExtension() {
48
74
  throw new Error(`HTTP ${res.status}`);
49
75
  const buf = await res.arrayBuffer();
50
76
  await fs.writeFile(vsixPath, Buffer.from(buf));
51
- execSync(`code --install-extension "${vsixPath}"`, { stdio: 'inherit' });
77
+ execSync(`${codeCli} --install-extension "${vsixPath}"`, { stdio: 'inherit' });
52
78
  console.log('[ugly-app] VSCode extension installed.');
53
79
  }
54
- catch {
55
- console.warn('[ugly-app] Could not install VSCode extension (skipping).');
80
+ catch (err) {
81
+ console.warn(`[ugly-app] Could not install VSCode extension (skipping): ${err.message}`);
56
82
  }
57
83
  finally {
58
84
  await fs.remove(vsixPath).catch(() => { });
@@ -77,6 +103,11 @@ export async function scaffoldProject(projectName) {
77
103
  await fs.writeFile(path.join(destDir, '.env'), envContent, 'utf-8');
78
104
  console.log('[ugly-app] Installing dependencies...');
79
105
  execSync('npm install', { cwd: destDir, stdio: 'inherit' });
106
+ const portStart = 3000;
107
+ const projectId = nanoid(10).toLowerCase();
108
+ writeUglyAppConfig(destDir, { projectId, portStart });
109
+ console.log(`\n[uglyapp] Generated .uglyapp for ${projectName}`);
110
+ printPortTable(portStart);
80
111
  console.log('[ugly-app] Initialising git repository...');
81
112
  execSync('git init', { cwd: destDir, stdio: 'inherit' });
82
113
  execSync('git add .', { cwd: destDir, stdio: 'inherit' });
@@ -84,11 +115,6 @@ export async function scaffoldProject(projectName) {
84
115
  cwd: destDir,
85
116
  stdio: 'inherit',
86
117
  });
87
- const portStart = 3000;
88
- const projectId = nanoid(10).toLowerCase();
89
- writeUglyAppConfig(destDir, { projectId, portStart });
90
- console.log(`\n[uglyapp] Generated .uglyapp for ${projectName}`);
91
- printPortTable(portStart);
92
118
  console.log('Edit .uglyapp to change portStart if running multiple projects simultaneously.');
93
119
  const envFilePath = path.join(destDir, '.env');
94
120
  await runUglyBotSetup(projectId, envFilePath);
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"callAI.d.ts","sourceRoot":"","sources":["../../src/client/callAI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACb,MAAM,oBAAoB,CAAC;AAsB5B,eAAO,MAAM,WAAW,GAAI,OAAO,YAAY,KAAG,OAAO,CAAC,MAAM,CACZ,CAAC;AAErD,eAAO,MAAM,WAAW,GAAI,CAAC,GAAG,OAAO,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,CAAC,CACzB,CAAC;AAEhD,eAAO,MAAM,YAAY,GAAI,OAAO,aAAa,KAAG,OAAO,CAAC,MAAM,CACb,CAAC"}
1
+ {"version":3,"file":"callAI.d.ts","sourceRoot":"","sources":["../../src/client/callAI.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACb,MAAM,oBAAoB,CAAC;AA0B5B,eAAO,MAAM,WAAW,GAAI,OAAO,YAAY,KAAG,OAAO,CAAC,MAAM,CACZ,CAAC;AAErD,eAAO,MAAM,WAAW,GAAI,CAAC,GAAG,OAAO,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,CAAC,CACzB,CAAC;AAEhD,eAAO,MAAM,YAAY,GAAI,OAAO,aAAa,KAAG,OAAO,CAAC,MAAM,CACb,CAAC"}
@@ -15,7 +15,13 @@ async function callViaServer(op, input) {
15
15
  const body = await res.text().catch(() => '');
16
16
  throw new Error(`${res.status}: ${body}`);
17
17
  }
18
- return (await res.json()).result;
18
+ const json = (await res.json());
19
+ console.log('[callAI]', op, json);
20
+ if (json.error)
21
+ throw new Error(json.error);
22
+ if (json.result === undefined)
23
+ throw new Error('AI request returned no result');
24
+ return json.result;
19
25
  }
20
26
  export const callTextGen = (input) => callViaServer('textGen', input);
21
27
  export const callJsonGen = (input) => callViaServer('jsonGen', input);
@@ -1 +1 @@
1
- {"version":3,"file":"callAI.js","sourceRoot":"","sources":["../../src/client/callAI.ts"],"names":[],"mappings":"AAMA,2EAA2E;AAC3E,+CAA+C;AAC/C,KAAK,UAAU,aAAa,CAAC,EAAU,EAAE,KAAc;IACrD,MAAM,KAAK,GAAI,MAAiD;SAC7D,cAAc,CAAC;IAClB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE;QACrC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,KAAK,IAAI,EAAE,EAAE;YACtC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;KACzD,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,OAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyB,CAAC,MAAM,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAmB,EAAmB,EAAE,CAClE,aAAa,CAAC,SAAS,EAAE,KAAK,CAAoB,CAAC;AAErD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAc,KAAmB,EAAc,EAAE,CAC1E,aAAa,CAAC,SAAS,EAAE,KAAK,CAAe,CAAC;AAEhD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAoB,EAAmB,EAAE,CACpE,aAAa,CAAC,UAAU,EAAE,KAAK,CAAoB,CAAC"}
1
+ {"version":3,"file":"callAI.js","sourceRoot":"","sources":["../../src/client/callAI.ts"],"names":[],"mappings":"AAMA,2EAA2E;AAC3E,+CAA+C;AAC/C,KAAK,UAAU,aAAa,CAAC,EAAU,EAAE,KAAc;IACrD,MAAM,KAAK,GAAI,MAAiD;SAC7D,cAAc,CAAC;IAClB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,aAAa,EAAE;QACrC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,KAAK,IAAI,EAAE,EAAE;YACtC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;KACzD,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAyC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAChF,OAAO,IAAI,CAAC,MAAM,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAmB,EAAmB,EAAE,CAClE,aAAa,CAAC,SAAS,EAAE,KAAK,CAAoB,CAAC;AAErD,MAAM,CAAC,MAAM,WAAW,GAAG,CAAc,KAAmB,EAAc,EAAE,CAC1E,aAAa,CAAC,SAAS,EAAE,KAAK,CAAe,CAAC;AAEhD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAoB,EAAmB,EAAE,CACpE,aAAa,CAAC,UAAU,EAAE,KAAK,CAAoB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-app",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "type": "module",
5
5
  "main": "./dist/server/index.js",
6
6
  "exports": {
@@ -11,7 +11,7 @@ import type { ImageGenModel, TextGenModel } from 'ugly-app/shared';
11
11
  import { imageGenModels, textGenModels } from 'ugly-app/shared';
12
12
 
13
13
  type Mode = 'text' | 'image';
14
- type LogEntry = { ts: number; msg: string; kind: 'info' | 'ok' | 'err' };
14
+ interface LogEntry { ts: number; msg: string; kind: 'info' | 'ok' | 'err' }
15
15
 
16
16
  function fmt(ms: number): string {
17
17
  return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
@@ -52,8 +52,8 @@ export default function AITestPage(): React.ReactElement {
52
52
  messages: [{ role: 'user', content: prompt }],
53
53
  });
54
54
  const elapsed = Date.now() - started;
55
- addLog(`Done in ${fmt(elapsed)} — ${text.length} chars`, 'ok');
56
- setResult(text);
55
+ addLog(`Done in ${fmt(elapsed)} — ${text?.length ?? 0} chars`, 'ok');
56
+ setResult(text ?? '');
57
57
  } else {
58
58
  const url = await callImageGen({ model: imageModel, prompt });
59
59
  const elapsed = Date.now() - started;