ugly-app 0.1.41 → 0.1.43
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 +129 -31
- package/dist/cli/scaffold.d.ts.map +1 -1
- package/dist/cli/scaffold.js +29 -3
- package/dist/cli/scaffold.js.map +1 -1
- package/dist/cli/upgrade.d.ts.map +1 -1
- package/dist/cli/upgrade.js +1 -0
- package/dist/cli/upgrade.js.map +1 -1
- package/package.json +1 -1
- package/templates/client/pages/AITestPage.tsx +90 -55
- package/templates/client/pages/AuthDemoPage.tsx +41 -44
- package/templates/client/pages/HomePage.tsx +54 -77
- package/templates/gitignore +1 -0
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
|
|
|
@@ -29,7 +29,14 @@ npm run dev
|
|
|
29
29
|
Entry point for the server. Creates an Express + WebSocket server with typed RPC, auth, and all framework services.
|
|
30
30
|
|
|
31
31
|
```typescript
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
createApp,
|
|
34
|
+
createUserHelper,
|
|
35
|
+
getFeedbackHandlers,
|
|
36
|
+
type CallHandlers,
|
|
37
|
+
type RequestHandlers,
|
|
38
|
+
type HandlerContext,
|
|
39
|
+
} from 'ugly-app';
|
|
33
40
|
import { dbDefaults } from 'ugly-app/shared';
|
|
34
41
|
import { functions, requests } from '../shared/api';
|
|
35
42
|
import { collections } from '../shared/collections';
|
|
@@ -39,6 +46,7 @@ import type { User } from '../shared/types';
|
|
|
39
46
|
const userHelper = createUserHelper<User>(collections.user);
|
|
40
47
|
|
|
41
48
|
const calls = {
|
|
49
|
+
...getFeedbackHandlers(maintainBotUserId),
|
|
42
50
|
myFunction: async (ctx: HandlerContext, input) => {
|
|
43
51
|
return { greeting: `Hello, ${input.name}` };
|
|
44
52
|
},
|
|
@@ -135,7 +143,7 @@ export const requests = defineRequests({
|
|
|
135
143
|
|
|
136
144
|
- `fn({ input, output })` — defines a mutation (write operation). `input` and `output` are Zod schemas.
|
|
137
145
|
- `req({ input, output })` — defines a query (read operation).
|
|
138
|
-
- `defineFunctions()` / `defineRequests()` — identity wrappers that preserve types.
|
|
146
|
+
- `defineFunctions()` / `defineRequests()` — identity wrappers that preserve types. `defineCalls` is an alias for `defineFunctions`.
|
|
139
147
|
- `z` is re-exported from Zod for convenience.
|
|
140
148
|
|
|
141
149
|
### Collections (`shared/collections.ts`)
|
|
@@ -156,11 +164,14 @@ export const collections = defineCollections({
|
|
|
156
164
|
});
|
|
157
165
|
```
|
|
158
166
|
|
|
159
|
-
|
|
160
|
-
- `
|
|
161
|
-
- `
|
|
162
|
-
- `
|
|
163
|
-
- `
|
|
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
|
|
164
175
|
|
|
165
176
|
All documents extend `DBObject`: `{ id: string, version: number, created: Date, updated: Date }`.
|
|
166
177
|
|
|
@@ -274,6 +285,10 @@ Wraps your app with context for `useApp()`. Provides user info, socket access, p
|
|
|
274
285
|
| `splashDone(step)` | Mark a splash screen step as complete |
|
|
275
286
|
| `localizer(key, params?)` | Localize a string key |
|
|
276
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
|
+
|
|
277
292
|
### Router
|
|
278
293
|
|
|
279
294
|
#### Setup (`client/router.ts`)
|
|
@@ -307,7 +322,18 @@ export const allPages = {
|
|
|
307
322
|
```
|
|
308
323
|
|
|
309
324
|
- **`lazyPage(factory)`** — lazy-imports a default-exported React component. The component receives route params as props.
|
|
310
|
-
- **`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
|
+
```
|
|
311
337
|
|
|
312
338
|
#### Navigation with `useRouter()`
|
|
313
339
|
|
|
@@ -346,10 +372,12 @@ handle.hide(); // dismiss programmatically
|
|
|
346
372
|
```
|
|
347
373
|
|
|
348
374
|
Popup modes:
|
|
349
|
-
- **`block`** — dark backdrop (40% opacity), clicking backdrop does NOT dismiss
|
|
375
|
+
- **`block`** (default) — dark backdrop (40% opacity), clicking backdrop does NOT dismiss
|
|
350
376
|
- **`transient`** — light backdrop (20% opacity), clicking backdrop dismisses
|
|
351
377
|
- **`contextMenu`** — same as transient, intended for menus and pickers
|
|
352
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
|
+
|
|
353
381
|
#### `Link` component
|
|
354
382
|
|
|
355
383
|
```tsx
|
|
@@ -415,14 +443,14 @@ configurator.setAuth({
|
|
|
415
443
|
await ctx.db.setDoc(collections.note, doc);
|
|
416
444
|
await ctx.db.setDoc(collections.note, doc, { skipIfExists: true });
|
|
417
445
|
|
|
418
|
-
// Partial update — only specified fields
|
|
446
|
+
// Partial update — only specified fields (supports dot-notation paths)
|
|
419
447
|
await ctx.db.setDocFields(collections.note, id, { title: 'New title' });
|
|
420
448
|
|
|
421
449
|
// Partial update — returns null if document doesn't exist (no error)
|
|
422
450
|
const doc = await ctx.db.setDocFieldsOrIgnore(collections.note, id, { title });
|
|
423
451
|
|
|
424
|
-
// Partial update — creates the document if it doesn't exist
|
|
425
|
-
await ctx.db.setDocFieldsOrCreate(collections.note, id, { title });
|
|
452
|
+
// Partial update — creates the document if it doesn't exist (obj = default doc for insert)
|
|
453
|
+
await ctx.db.setDocFieldsOrCreate(collections.note, id, { title }, defaultDoc);
|
|
426
454
|
|
|
427
455
|
// MongoDB update operators ($inc, $addToSet, $pull, $unset, $set)
|
|
428
456
|
await ctx.db.setDocOp(collections.note, id, { $inc: { views: 1 } });
|
|
@@ -439,6 +467,10 @@ const notes = await ctx.db.getDocs(collections.note, { userId }, { sort: { creat
|
|
|
439
467
|
const results = await ctx.db.getQuery<MyResult>('note', pipeline, { skip, limit });
|
|
440
468
|
const count = await ctx.db.getQueryCount('note', pipeline);
|
|
441
469
|
const raw = await ctx.db.getQueryRaw<T>('note', pipeline);
|
|
470
|
+
|
|
471
|
+
// Dynamic/untyped access (when collection name is a runtime string)
|
|
472
|
+
const doc = await ctx.db.rawGetDoc(collectionName, id);
|
|
473
|
+
const docs = await ctx.db.rawGetDocs(collectionName, filter);
|
|
442
474
|
```
|
|
443
475
|
|
|
444
476
|
### Deleting
|
|
@@ -460,12 +492,13 @@ const key = ctx.db.cacheKey('prefix', id); // generate a cache key
|
|
|
460
492
|
### Helpers
|
|
461
493
|
|
|
462
494
|
```typescript
|
|
463
|
-
import {
|
|
495
|
+
import { createUserHelper } from 'ugly-app';
|
|
496
|
+
import { dbDefaults } from 'ugly-app/shared';
|
|
464
497
|
|
|
465
498
|
// dbDefaults() returns { version: 1, created: new Date(), updated: new Date() }
|
|
466
499
|
const doc = { id: newId(), ...dbDefaults(), title: 'Hello' };
|
|
467
500
|
|
|
468
|
-
// createUserHelper — typed user CRUD
|
|
501
|
+
// createUserHelper — typed user CRUD with get, set, update methods
|
|
469
502
|
const userHelper = createUserHelper<User>(collections.user);
|
|
470
503
|
const user = await userHelper.get(db, userId);
|
|
471
504
|
await userHelper.set(db, { id: userId, ...dbDefaults(), email });
|
|
@@ -485,6 +518,8 @@ export const dbIndexes = defineDbIndexes({
|
|
|
485
518
|
});
|
|
486
519
|
```
|
|
487
520
|
|
|
521
|
+
Index types supported: standard (`IndexDef`), text search (`SearchIndexDef`), and vector/embedding (`VectorIndexDef` with cosine, euclidean, or dotProduct similarity).
|
|
522
|
+
|
|
488
523
|
Run `npm run db:init` to create/update indexes.
|
|
489
524
|
|
|
490
525
|
---
|
|
@@ -538,18 +573,48 @@ import { createImageGenClient } from 'ugly-app';
|
|
|
538
573
|
const imageGen = createImageGenClient();
|
|
539
574
|
```
|
|
540
575
|
|
|
576
|
+
### Embeddings
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import { createEmbeddingClient, cosineSimilarity } from 'ugly-app';
|
|
580
|
+
const embeddings = createEmbeddingClient();
|
|
581
|
+
const similarity = cosineSimilarity(vectorA, vectorB);
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Speech-to-text / Text-to-speech
|
|
585
|
+
|
|
586
|
+
```typescript
|
|
587
|
+
// Server-side providers
|
|
588
|
+
import { registerSTTProvider, groqWhisperSTTProvider, loadVADModel } from 'ugly-app';
|
|
589
|
+
import { registerTTSProvider, azureTTSProvider } from 'ugly-app';
|
|
590
|
+
|
|
591
|
+
// Client-side hooks
|
|
592
|
+
import { useSTT, useTTS, AudioPlayer, AudioRecorder } from 'ugly-app/client';
|
|
593
|
+
```
|
|
594
|
+
|
|
541
595
|
### Custom providers
|
|
542
596
|
|
|
543
597
|
```typescript
|
|
544
|
-
import { registerTextGenProvider, registerImageGenProvider } from 'ugly-app';
|
|
598
|
+
import { registerTextGenProvider, registerImageGenProvider, registerEmbeddingProvider } from 'ugly-app';
|
|
545
599
|
|
|
546
600
|
registerTextGenProvider('myProvider', myTextGenImplementation);
|
|
547
601
|
registerImageGenProvider('myProvider', myImageGenImplementation);
|
|
602
|
+
registerEmbeddingProvider('myProvider', myEmbeddingImplementation);
|
|
548
603
|
```
|
|
549
604
|
|
|
550
|
-
### AI
|
|
605
|
+
### Client-side AI calls
|
|
551
606
|
|
|
552
|
-
The client
|
|
607
|
+
The client can call AI through the server proxy without managing tokens directly:
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
import { callTextGen, callJsonGen, callImageGen } from 'ugly-app/client';
|
|
611
|
+
|
|
612
|
+
const text = await callTextGen(messages, options);
|
|
613
|
+
const json = await callJsonGen(schema, messages);
|
|
614
|
+
const image = await callImageGen(prompt, options);
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
These call `POST /ai/request` (same-origin, avoids CORS). The server verifies the user JWT and forwards to `https://ugly.bot` using the `UGLY_BOT_TOKEN` env var.
|
|
553
618
|
|
|
554
619
|
---
|
|
555
620
|
|
|
@@ -569,7 +634,7 @@ const url = storage.url('public', destKey);
|
|
|
569
634
|
const { uploadUrl, resultUrl } = await storage.presignedPut('temp', key);
|
|
570
635
|
```
|
|
571
636
|
|
|
572
|
-
Buckets: `'public'` and `'temp'`.
|
|
637
|
+
Buckets: `'public'` and `'temp'`. Supports Cloudflare R2 (production) or MinIO (dev).
|
|
573
638
|
|
|
574
639
|
Static build-time assets go in `client/public/`. Never hardcode `/asset/...` paths — use the `buildId` from `shared/Build.ts`.
|
|
575
640
|
|
|
@@ -607,7 +672,7 @@ sub(); // unsubscribe
|
|
|
607
672
|
### Redis
|
|
608
673
|
|
|
609
674
|
```typescript
|
|
610
|
-
import { getRedisClient } from 'ugly-app';
|
|
675
|
+
import { getRedisClient, redisGet, redisSet, redisDel, redisPublish, redisSubscribe } from 'ugly-app';
|
|
611
676
|
const redis = getRedisClient();
|
|
612
677
|
```
|
|
613
678
|
|
|
@@ -622,31 +687,63 @@ configurator.setWorkerQueue(queue);
|
|
|
622
687
|
await enqueueTask('taskName', payload);
|
|
623
688
|
```
|
|
624
689
|
|
|
625
|
-
###
|
|
690
|
+
### Billing
|
|
626
691
|
|
|
627
692
|
```typescript
|
|
628
|
-
|
|
629
|
-
|
|
693
|
+
import { initBillingGateway, getBillingGateway } from 'ugly-app';
|
|
694
|
+
|
|
695
|
+
await initBillingGateway({ /* config */ });
|
|
696
|
+
const billing = getBillingGateway();
|
|
630
697
|
```
|
|
631
698
|
|
|
632
|
-
###
|
|
699
|
+
### Client error capture
|
|
633
700
|
|
|
634
701
|
```typescript
|
|
635
|
-
import {
|
|
636
|
-
|
|
702
|
+
import { captureClientError, initClientLogger } from 'ugly-app/client';
|
|
703
|
+
|
|
704
|
+
initClientLogger(); // call once at startup — captures unhandled errors
|
|
705
|
+
captureClientError(error); // manually report an error to POST /api/client-error
|
|
637
706
|
```
|
|
638
707
|
|
|
639
|
-
###
|
|
708
|
+
### Feedback
|
|
640
709
|
|
|
641
710
|
```typescript
|
|
642
|
-
import {
|
|
711
|
+
import { FeedbackButton, setFeedbackContext, clearFeedbackContext } from 'ugly-app/client';
|
|
643
712
|
|
|
644
|
-
|
|
645
|
-
|
|
713
|
+
// Render the built-in feedback button (bottom-right, always at [data-id="feedback-button"])
|
|
714
|
+
<FeedbackButton />
|
|
715
|
+
|
|
716
|
+
// Set contextual data attached to feedback submissions
|
|
717
|
+
setFeedbackContext({ page: 'editor', noteId: '123' });
|
|
718
|
+
clearFeedbackContext();
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
Server-side, `getFeedbackHandlers(maintainBotUserId)` provides the RPC handlers for submitting and managing feedback. User feedback history is available at `GET /my_feedback` (requires auth cookie, returns markdown).
|
|
722
|
+
|
|
723
|
+
### Rate limiting
|
|
724
|
+
|
|
725
|
+
```typescript
|
|
726
|
+
// Inside a handler — always call before expensive operations
|
|
727
|
+
await ctx.rateLimit.check();
|
|
646
728
|
```
|
|
647
729
|
|
|
648
730
|
---
|
|
649
731
|
|
|
732
|
+
## Built-in endpoints
|
|
733
|
+
|
|
734
|
+
| Endpoint | Description |
|
|
735
|
+
|----------|-------------|
|
|
736
|
+
| `GET /health` | Health check — returns `{ status: 'ok', timestamp }` |
|
|
737
|
+
| `POST /auth/verify` | Exchange OAuth code for a session cookie |
|
|
738
|
+
| `POST /auth/logout` | Clear the auth cookie |
|
|
739
|
+
| `GET /auth/token` | Refresh and return the current token |
|
|
740
|
+
| `GET /auth/url` | Get the OAuth popup URL |
|
|
741
|
+
| `POST /ai/request` | AI proxy — forwards to ugly.bot (requires auth) |
|
|
742
|
+
| `POST /api/client-error` | Client-side error capture |
|
|
743
|
+
| `GET /my_feedback` | User feedback history (markdown, requires auth) |
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
650
747
|
## Migrations
|
|
651
748
|
|
|
652
749
|
Never change a collection field type without writing a migration:
|
|
@@ -691,6 +788,7 @@ Run with `npm run db:migrate`. Use `npm run db:migrate -- --status` to preview p
|
|
|
691
788
|
| Variable | Description |
|
|
692
789
|
|----------|-------------|
|
|
693
790
|
| `JWT_SECRET` | **Required** — signs auth tokens |
|
|
791
|
+
| `JWT_EXPIRY_SECONDS` | Token lifetime (optional) |
|
|
694
792
|
| `MONGODB_URI` | MongoDB connection string |
|
|
695
793
|
| `PORT` | Server port (default: 3000) |
|
|
696
794
|
| `NODE_ENV` | `development` or `production` |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/cli/scaffold.js
CHANGED
|
@@ -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,7 +74,7 @@ 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(
|
|
77
|
+
execSync(`${codeCli} --install-extension "${vsixPath}"`, { stdio: 'inherit' });
|
|
52
78
|
console.log('[ugly-app] VSCode extension installed.');
|
|
53
79
|
}
|
|
54
80
|
catch {
|
|
@@ -75,6 +101,8 @@ export async function scaffoldProject(projectName) {
|
|
|
75
101
|
// Generate .env from .env.example with a real JWT_SECRET and optional keys commented out
|
|
76
102
|
const envContent = generateEnv(path.join(destDir, '.env.example'), projectName);
|
|
77
103
|
await fs.writeFile(path.join(destDir, '.env'), envContent, 'utf-8');
|
|
104
|
+
console.log('[ugly-app] Installing dependencies...');
|
|
105
|
+
execSync('npm install', { cwd: destDir, stdio: 'inherit' });
|
|
78
106
|
console.log('[ugly-app] Initialising git repository...');
|
|
79
107
|
execSync('git init', { cwd: destDir, stdio: 'inherit' });
|
|
80
108
|
execSync('git add .', { cwd: destDir, stdio: 'inherit' });
|
|
@@ -82,8 +110,6 @@ export async function scaffoldProject(projectName) {
|
|
|
82
110
|
cwd: destDir,
|
|
83
111
|
stdio: 'inherit',
|
|
84
112
|
});
|
|
85
|
-
console.log('[ugly-app] Installing dependencies...');
|
|
86
|
-
execSync('npm install', { cwd: destDir, stdio: 'inherit' });
|
|
87
113
|
const portStart = 3000;
|
|
88
114
|
const projectId = nanoid(10).toLowerCase();
|
|
89
115
|
writeUglyAppConfig(destDir, { projectId, portStart });
|
package/dist/cli/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;AAE9D,gFAAgF;AAChF,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,MAAM;IACN,aAAa;IACb,kBAAkB;IAClB,uBAAuB;CACxB,CAAC,CAAC;AAEH,SAAS,WAAW,CAAC,WAAmB,EAAE,WAAmB;IAC3D,MAAM,KAAK,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChE,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,6BAA6B;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YACnC,OAAO,cAAc,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QACpC,CAAC;QACD,yEAAyE;QACzE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACtD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC;YAC7B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnD,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,oBAAoB;IACjC,MAAM,GAAG,GAAG,sCAAsC,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC;IACzD,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACnD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/C,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"AA4BA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAgIhD"}
|
package/dist/cli/upgrade.js
CHANGED
package/dist/cli/upgrade.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;AAE9D,MAAM,YAAY,GAAG;IACnB,eAAe;IACf,gBAAgB;IAChB,oBAAoB;IACpB,mBAAmB;IACnB,oBAAoB;IACpB,eAAe;IACf,mBAAmB;IACnB,WAAW;IACX,sBAAsB;IACtB,mBAAmB;
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
Card,
|
|
5
5
|
Input,
|
|
6
6
|
PageLayout,
|
|
7
|
-
Text,
|
|
8
7
|
callImageGen,
|
|
9
8
|
callTextGen,
|
|
10
9
|
} from 'ugly-app/client';
|
|
@@ -12,83 +11,102 @@ import type { ImageGenModel, TextGenModel } from 'ugly-app/shared';
|
|
|
12
11
|
import { imageGenModels, textGenModels } from 'ugly-app/shared';
|
|
13
12
|
|
|
14
13
|
type Mode = 'text' | 'image';
|
|
14
|
+
interface LogEntry { ts: number; msg: string; kind: 'info' | 'ok' | 'err' }
|
|
15
|
+
|
|
16
|
+
function fmt(ms: number): string {
|
|
17
|
+
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
export default function AITestPage(): React.ReactElement {
|
|
17
21
|
const [mode, setMode] = useState<Mode>('text');
|
|
18
22
|
const [prompt, setPrompt] = useState('');
|
|
19
23
|
const [textModel, setTextModel] = useState<TextGenModel>(textGenModels[0]!);
|
|
20
|
-
const [imageModel, setImageModel] = useState<ImageGenModel>(
|
|
21
|
-
imageGenModels[0]!,
|
|
22
|
-
);
|
|
24
|
+
const [imageModel, setImageModel] = useState<ImageGenModel>(imageGenModels[0]!);
|
|
23
25
|
const [result, setResult] = useState('');
|
|
24
26
|
const [imageUrl, setImageUrl] = useState('');
|
|
25
27
|
const [loading, setLoading] = useState(false);
|
|
26
|
-
const [
|
|
28
|
+
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
29
|
+
|
|
30
|
+
function addLog(msg: string, kind: LogEntry['kind'] = 'info'): number {
|
|
31
|
+
const ts = Date.now();
|
|
32
|
+
setLogs((prev) => [...prev, { ts, msg, kind }]);
|
|
33
|
+
return ts;
|
|
34
|
+
}
|
|
27
35
|
|
|
28
36
|
async function handleRun(): Promise<void> {
|
|
29
37
|
if (!prompt.trim()) return;
|
|
30
38
|
setLoading(true);
|
|
31
|
-
setError('');
|
|
32
39
|
setResult('');
|
|
33
40
|
setImageUrl('');
|
|
41
|
+
setLogs([]);
|
|
42
|
+
|
|
43
|
+
const started = Date.now();
|
|
44
|
+
const model = mode === 'text' ? textModel : imageModel;
|
|
45
|
+
addLog(`Using model: ${model}`);
|
|
46
|
+
addLog(`Sending ${mode} request…`);
|
|
47
|
+
|
|
34
48
|
try {
|
|
35
49
|
if (mode === 'text') {
|
|
36
50
|
const text = await callTextGen({
|
|
37
51
|
model: textModel,
|
|
38
52
|
messages: [{ role: 'user', content: prompt }],
|
|
39
53
|
});
|
|
54
|
+
const elapsed = Date.now() - started;
|
|
55
|
+
addLog(`Done in ${fmt(elapsed)} — ${text.length} chars`, 'ok');
|
|
40
56
|
setResult(text);
|
|
41
57
|
} else {
|
|
42
58
|
const url = await callImageGen({ model: imageModel, prompt });
|
|
59
|
+
const elapsed = Date.now() - started;
|
|
60
|
+
addLog(`Done in ${fmt(elapsed)}`, 'ok');
|
|
43
61
|
setImageUrl(url);
|
|
44
62
|
}
|
|
45
63
|
} catch (err) {
|
|
46
|
-
|
|
64
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65
|
+
addLog(msg, 'err');
|
|
47
66
|
} finally {
|
|
48
67
|
setLoading(false);
|
|
49
68
|
}
|
|
50
69
|
}
|
|
51
70
|
|
|
52
71
|
const model = mode === 'text' ? textModel : imageModel;
|
|
53
|
-
const models: readonly string[] =
|
|
54
|
-
mode === 'text' ? textGenModels : imageGenModels;
|
|
72
|
+
const models: readonly string[] = mode === 'text' ? textGenModels : imageGenModels;
|
|
55
73
|
|
|
56
74
|
return (
|
|
57
75
|
<PageLayout
|
|
58
76
|
header={
|
|
59
|
-
<div className="px-6 py-4">
|
|
60
|
-
<a href="/" className="text-
|
|
77
|
+
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
78
|
+
<a href="/" className="text-sm text-gray-500 hover:text-gray-900 dark:hover:text-gray-100 transition-colors">
|
|
61
79
|
← Home
|
|
62
80
|
</a>
|
|
63
81
|
</div>
|
|
64
82
|
}
|
|
65
83
|
>
|
|
66
|
-
<div className="max-w-2xl mx-auto">
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
<div className="max-w-2xl mx-auto px-4 py-6">
|
|
85
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-6">AI Test</h1>
|
|
86
|
+
|
|
87
|
+
{/* Mode toggle */}
|
|
88
|
+
<div className="flex gap-1 p-1 bg-gray-100 dark:bg-gray-800 rounded-lg w-fit mb-6">
|
|
89
|
+
{(['text', 'image'] as Mode[]).map((m) => (
|
|
90
|
+
<button
|
|
91
|
+
key={m}
|
|
92
|
+
onClick={() => setMode(m)}
|
|
93
|
+
className={`px-4 py-1.5 rounded-md text-sm font-medium transition-colors capitalize ${
|
|
94
|
+
mode === m
|
|
95
|
+
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm'
|
|
96
|
+
: 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
|
|
97
|
+
}`}
|
|
98
|
+
>
|
|
99
|
+
{m}
|
|
100
|
+
</button>
|
|
101
|
+
))}
|
|
84
102
|
</div>
|
|
85
103
|
|
|
86
|
-
<Card className="
|
|
104
|
+
<Card className="mb-4">
|
|
87
105
|
<div className="space-y-4">
|
|
88
106
|
<div>
|
|
89
|
-
<
|
|
107
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
90
108
|
Model
|
|
91
|
-
</
|
|
109
|
+
</label>
|
|
92
110
|
<select
|
|
93
111
|
value={model}
|
|
94
112
|
onChange={(e) =>
|
|
@@ -96,12 +114,10 @@ export default function AITestPage(): React.ReactElement {
|
|
|
96
114
|
? setTextModel(e.target.value as TextGenModel)
|
|
97
115
|
: setImageModel(e.target.value as ImageGenModel)
|
|
98
116
|
}
|
|
99
|
-
className="w-full border border-gray-
|
|
117
|
+
className="w-full border border-gray-200 dark:border-gray-700 rounded-lg px-3 py-2 text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
100
118
|
>
|
|
101
119
|
{models.map((m) => (
|
|
102
|
-
<option key={m} value={m}>
|
|
103
|
-
{m}
|
|
104
|
-
</option>
|
|
120
|
+
<option key={m} value={m}>{m}</option>
|
|
105
121
|
))}
|
|
106
122
|
</select>
|
|
107
123
|
</div>
|
|
@@ -110,42 +126,61 @@ export default function AITestPage(): React.ReactElement {
|
|
|
110
126
|
label="Prompt"
|
|
111
127
|
value={prompt}
|
|
112
128
|
onChange={setPrompt}
|
|
113
|
-
placeholder={
|
|
114
|
-
mode === 'text' ? 'Ask something...' : 'Describe an image...'
|
|
115
|
-
}
|
|
129
|
+
placeholder={mode === 'text' ? 'Ask something…' : 'Describe an image…'}
|
|
116
130
|
/>
|
|
117
131
|
|
|
118
|
-
<Button
|
|
119
|
-
{
|
|
132
|
+
<Button
|
|
133
|
+
onClick={handleRun}
|
|
134
|
+
disabled={loading || !prompt.trim()}
|
|
135
|
+
className="w-full"
|
|
136
|
+
>
|
|
137
|
+
{loading ? 'Running…' : 'Run'}
|
|
120
138
|
</Button>
|
|
121
139
|
</div>
|
|
122
140
|
</Card>
|
|
123
141
|
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
142
|
+
{/* Log panel — always visible once a run starts */}
|
|
143
|
+
{logs.length > 0 && (
|
|
144
|
+
<div className="mb-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 p-3 font-mono text-xs space-y-1">
|
|
145
|
+
{logs.map((entry, i) => (
|
|
146
|
+
<div
|
|
147
|
+
key={i}
|
|
148
|
+
className={
|
|
149
|
+
entry.kind === 'err'
|
|
150
|
+
? 'text-red-500'
|
|
151
|
+
: entry.kind === 'ok'
|
|
152
|
+
? 'text-green-600 dark:text-green-400'
|
|
153
|
+
: 'text-gray-500 dark:text-gray-400'
|
|
154
|
+
}
|
|
155
|
+
>
|
|
156
|
+
{entry.kind === 'err' ? '✗' : entry.kind === 'ok' ? '✓' : '·'} {entry.msg}
|
|
157
|
+
</div>
|
|
158
|
+
))}
|
|
159
|
+
{loading && (
|
|
160
|
+
<div className="text-blue-500 animate-pulse">· waiting…</div>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
130
163
|
)}
|
|
131
164
|
|
|
165
|
+
{/* Text result */}
|
|
132
166
|
{result && (
|
|
133
|
-
<Card
|
|
134
|
-
<
|
|
167
|
+
<Card>
|
|
168
|
+
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
|
|
135
169
|
Result
|
|
136
|
-
</
|
|
137
|
-
<pre className="text-sm whitespace-pre-wrap break-words
|
|
170
|
+
</p>
|
|
171
|
+
<pre className="text-sm whitespace-pre-wrap break-words text-gray-800 dark:text-gray-200 leading-relaxed">
|
|
138
172
|
{result}
|
|
139
173
|
</pre>
|
|
140
174
|
</Card>
|
|
141
175
|
)}
|
|
142
176
|
|
|
177
|
+
{/* Image result */}
|
|
143
178
|
{imageUrl && (
|
|
144
|
-
<Card
|
|
145
|
-
<
|
|
179
|
+
<Card>
|
|
180
|
+
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
|
|
146
181
|
Result
|
|
147
|
-
</
|
|
148
|
-
<img src={imageUrl} alt="Generated" className="w-full rounded" />
|
|
182
|
+
</p>
|
|
183
|
+
<img src={imageUrl} alt="Generated" className="w-full rounded-lg" />
|
|
149
184
|
</Card>
|
|
150
185
|
)}
|
|
151
186
|
</div>
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Button, Card,
|
|
3
|
-
|
|
2
|
+
import { Button, Card, PageLayout, Text, useApp } from 'ugly-app/client';
|
|
3
|
+
|
|
4
|
+
function openLogin(): void {
|
|
5
|
+
window.open(
|
|
6
|
+
`https://ugly.bot/oauth?origin=${encodeURIComponent(window.location.origin)}`,
|
|
7
|
+
'ugly-bot-login',
|
|
8
|
+
`width=480,height=640,left=${Math.round(window.screenX + (window.outerWidth - 480) / 2)},top=${Math.round(window.screenY + (window.outerHeight - 640) / 2)}`,
|
|
9
|
+
);
|
|
10
|
+
function onMessage(event: MessageEvent): void {
|
|
11
|
+
if (event.origin !== 'https://ugly.bot') return;
|
|
12
|
+
const data = event.data as { type?: string; code?: string } | null;
|
|
13
|
+
if (!data || data.type !== 'ugly-bot-oauth' || !data.code) return;
|
|
14
|
+
window.removeEventListener('message', onMessage);
|
|
15
|
+
void fetch('/auth/verify', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify({ code: data.code }),
|
|
19
|
+
}).then((res) => {
|
|
20
|
+
if (res.ok) window.location.reload();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
window.addEventListener('message', onMessage);
|
|
24
|
+
}
|
|
4
25
|
|
|
5
26
|
function AuthDemoAuthenticated(): React.ReactElement {
|
|
6
27
|
const app = useApp();
|
|
@@ -13,29 +34,21 @@ function AuthDemoAuthenticated(): React.ReactElement {
|
|
|
13
34
|
return (
|
|
14
35
|
<PageLayout
|
|
15
36
|
header={
|
|
16
|
-
<div className="px-6 py-4">
|
|
17
|
-
<a href="/" className="text-
|
|
37
|
+
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
38
|
+
<a href="/" className="text-sm text-gray-500 hover:text-gray-900 dark:hover:text-gray-100 transition-colors">
|
|
18
39
|
← Home
|
|
19
40
|
</a>
|
|
20
41
|
</div>
|
|
21
42
|
}
|
|
22
43
|
>
|
|
23
|
-
<div className="max-w-md mx-auto">
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<pre className="
|
|
30
|
-
{JSON.stringify(
|
|
31
|
-
{
|
|
32
|
-
userId: app.userId,
|
|
33
|
-
email: app.user.email,
|
|
34
|
-
phone: app.user.phone,
|
|
35
|
-
},
|
|
36
|
-
null,
|
|
37
|
-
2,
|
|
38
|
-
)}
|
|
44
|
+
<div className="max-w-md mx-auto px-4 py-6">
|
|
45
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4">Auth Demo</h1>
|
|
46
|
+
<Card>
|
|
47
|
+
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
|
|
48
|
+
Logged in
|
|
49
|
+
</p>
|
|
50
|
+
<pre className="text-sm bg-gray-50 dark:bg-gray-900 p-3 rounded-lg overflow-auto text-gray-800 dark:text-gray-200">
|
|
51
|
+
{JSON.stringify({ userId: app.userId, email: app.user.email, phone: app.user.phone }, null, 2)}
|
|
39
52
|
</pre>
|
|
40
53
|
<Button variant="secondary" onClick={handleLogout} className="mt-4">
|
|
41
54
|
Logout
|
|
@@ -47,36 +60,21 @@ function AuthDemoAuthenticated(): React.ReactElement {
|
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
function AuthDemoUnauthenticated(): React.ReactElement {
|
|
50
|
-
const { openPopup } = useRouter();
|
|
51
|
-
|
|
52
|
-
function handleLogin(): void {
|
|
53
|
-
openPopup(
|
|
54
|
-
<LoginPopup
|
|
55
|
-
inline
|
|
56
|
-
|
|
57
|
-
onSuccess={() => { window.location.reload(); }}
|
|
58
|
-
/>,
|
|
59
|
-
{ mode: 'transient' },
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
63
|
return (
|
|
64
64
|
<PageLayout
|
|
65
65
|
header={
|
|
66
|
-
<div className="px-6 py-4">
|
|
67
|
-
<a href="/" className="text-
|
|
66
|
+
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
67
|
+
<a href="/" className="text-sm text-gray-500 hover:text-gray-900 dark:hover:text-gray-100 transition-colors">
|
|
68
68
|
← Home
|
|
69
69
|
</a>
|
|
70
70
|
</div>
|
|
71
71
|
}
|
|
72
72
|
>
|
|
73
|
-
<div className="max-w-md mx-auto">
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<Text>You are not logged in.</Text>
|
|
79
|
-
<Button onClick={handleLogin} className="mt-4">
|
|
73
|
+
<div className="max-w-md mx-auto px-4 py-6">
|
|
74
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4">Auth Demo</h1>
|
|
75
|
+
<Card>
|
|
76
|
+
<Text className="text-gray-600 dark:text-gray-400">You are not logged in.</Text>
|
|
77
|
+
<Button onClick={openLogin} className="mt-4">
|
|
80
78
|
Login with ugly.bot
|
|
81
79
|
</Button>
|
|
82
80
|
</Card>
|
|
@@ -86,8 +84,7 @@ function AuthDemoUnauthenticated(): React.ReactElement {
|
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
export default function AuthDemoPage(): React.ReactElement {
|
|
89
|
-
const isLoggedIn = !!(window as unknown as { __AUTH_TOKEN__?: string })
|
|
90
|
-
.__AUTH_TOKEN__;
|
|
87
|
+
const isLoggedIn = !!(window as unknown as { __AUTH_TOKEN__?: string }).__AUTH_TOKEN__;
|
|
91
88
|
if (isLoggedIn) return <AuthDemoAuthenticated />;
|
|
92
89
|
return <AuthDemoUnauthenticated />;
|
|
93
90
|
}
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Button, Card,
|
|
3
|
-
|
|
2
|
+
import { Button, Card, PageLayout, Text, useApp } from 'ugly-app/client';
|
|
3
|
+
|
|
4
|
+
function openLogin(): void {
|
|
5
|
+
window.open(
|
|
6
|
+
`https://ugly.bot/oauth?origin=${encodeURIComponent(window.location.origin)}`,
|
|
7
|
+
'ugly-bot-login',
|
|
8
|
+
`width=480,height=640,left=${Math.round(window.screenX + (window.outerWidth - 480) / 2)},top=${Math.round(window.screenY + (window.outerHeight - 640) / 2)}`,
|
|
9
|
+
);
|
|
10
|
+
function onMessage(event: MessageEvent): void {
|
|
11
|
+
if (event.origin !== 'https://ugly.bot') return;
|
|
12
|
+
const data = event.data as { type?: string; code?: string } | null;
|
|
13
|
+
if (!data || data.type !== 'ugly-bot-oauth' || !data.code) return;
|
|
14
|
+
window.removeEventListener('message', onMessage);
|
|
15
|
+
void fetch('/auth/verify', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify({ code: data.code }),
|
|
19
|
+
}).then((res) => {
|
|
20
|
+
if (res.ok) window.location.reload();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
window.addEventListener('message', onMessage);
|
|
24
|
+
}
|
|
4
25
|
|
|
5
26
|
function HomePageAuthenticated(): React.ReactElement {
|
|
6
27
|
const app = useApp();
|
|
@@ -13,10 +34,8 @@ function HomePageAuthenticated(): React.ReactElement {
|
|
|
13
34
|
return (
|
|
14
35
|
<PageLayout
|
|
15
36
|
header={
|
|
16
|
-
<div className="flex items-center justify-between px-6 py-4">
|
|
17
|
-
<
|
|
18
|
-
My App
|
|
19
|
-
</Text>
|
|
37
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
38
|
+
<span className="text-base font-semibold text-gray-900 dark:text-gray-100">My App</span>
|
|
20
39
|
<Button variant="secondary" onClick={handleLogout}>
|
|
21
40
|
Logout
|
|
22
41
|
</Button>
|
|
@@ -29,27 +48,12 @@ function HomePageAuthenticated(): React.ReactElement {
|
|
|
29
48
|
}
|
|
30
49
|
|
|
31
50
|
function HomePageUnauthenticated(): React.ReactElement {
|
|
32
|
-
const { openPopup } = useRouter();
|
|
33
|
-
|
|
34
|
-
function handleLogin(): void {
|
|
35
|
-
openPopup(
|
|
36
|
-
<LoginPopup
|
|
37
|
-
inline
|
|
38
|
-
|
|
39
|
-
onSuccess={() => { window.location.reload(); }}
|
|
40
|
-
/>,
|
|
41
|
-
{ mode: 'transient' },
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
51
|
return (
|
|
46
52
|
<PageLayout
|
|
47
53
|
header={
|
|
48
|
-
<div className="flex items-center justify-between px-6 py-4">
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
</Text>
|
|
52
|
-
<Button variant="primary" onClick={handleLogin}>
|
|
54
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
55
|
+
<span className="text-base font-semibold text-gray-900 dark:text-gray-100">My App</span>
|
|
56
|
+
<Button variant="primary" onClick={openLogin}>
|
|
53
57
|
Login
|
|
54
58
|
</Button>
|
|
55
59
|
</div>
|
|
@@ -60,69 +64,42 @@ function HomePageUnauthenticated(): React.ReactElement {
|
|
|
60
64
|
);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
function HomePageBody({
|
|
64
|
-
userId,
|
|
65
|
-
}: {
|
|
66
|
-
userId: string | null;
|
|
67
|
-
}): React.ReactElement {
|
|
67
|
+
function HomePageBody({ userId }: { userId: string | null }): React.ReactElement {
|
|
68
68
|
return (
|
|
69
|
-
<div className="max-w-2xl mx-auto">
|
|
70
|
-
<Card>
|
|
71
|
-
<Text size="xl" weight="bold">
|
|
72
|
-
|
|
69
|
+
<div className="max-w-2xl mx-auto px-4 py-6">
|
|
70
|
+
<Card className="mb-6">
|
|
71
|
+
<Text size="xl" weight="bold">Welcome</Text>
|
|
72
|
+
<Text className="mt-1 text-gray-500 dark:text-gray-400">
|
|
73
|
+
{userId ? `Logged in as: ${userId}` : 'This app was built with ugly-app.'}
|
|
73
74
|
</Text>
|
|
74
|
-
{userId ? (
|
|
75
|
-
<Text className="mt-2 text-gray-600 dark:text-gray-400">
|
|
76
|
-
Logged in as: {userId}
|
|
77
|
-
</Text>
|
|
78
|
-
) : (
|
|
79
|
-
<Text className="mt-2 text-gray-600 dark:text-gray-400">
|
|
80
|
-
This app was built with ugly-app.
|
|
81
|
-
</Text>
|
|
82
|
-
)}
|
|
83
75
|
</Card>
|
|
84
76
|
|
|
85
|
-
<div className="
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<Text weight="medium">Query Params Demo →</Text>
|
|
105
|
-
<Text size="sm" className="text-gray-500 mt-1">
|
|
106
|
-
URL: /search?q=...
|
|
107
|
-
</Text>
|
|
108
|
-
</Card>
|
|
109
|
-
</a>
|
|
110
|
-
<a href="/ai-test" className="block">
|
|
111
|
-
<Card className="hover:shadow-md transition-shadow cursor-pointer">
|
|
112
|
-
<Text weight="medium">AI Model Test →</Text>
|
|
113
|
-
<Text size="sm" className="text-gray-500 mt-1">
|
|
114
|
-
Test text & image generation models
|
|
115
|
-
</Text>
|
|
116
|
-
</Card>
|
|
117
|
-
</a>
|
|
77
|
+
<div className="grid gap-3">
|
|
78
|
+
{[
|
|
79
|
+
{ href: '/auth-demo', label: 'Auth Demo', desc: 'Login, logout, and user info' },
|
|
80
|
+
{ href: '/user/example-user-id', label: 'Path Params Demo', desc: 'URL: /user/:userId' },
|
|
81
|
+
{ href: '/search?q=hello', label: 'Query Params Demo', desc: 'URL: /search?q=...' },
|
|
82
|
+
{ href: '/ai-test', label: 'AI Test', desc: 'Text & image generation models' },
|
|
83
|
+
].map(({ href, label, desc }) => (
|
|
84
|
+
<a key={href} href={href} className="block group">
|
|
85
|
+
<Card className="transition-shadow group-hover:shadow-md">
|
|
86
|
+
<div className="flex items-center justify-between">
|
|
87
|
+
<div>
|
|
88
|
+
<Text weight="medium">{label}</Text>
|
|
89
|
+
<Text size="sm" className="text-gray-500 mt-0.5">{desc}</Text>
|
|
90
|
+
</div>
|
|
91
|
+
<span className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 transition-colors">→</span>
|
|
92
|
+
</div>
|
|
93
|
+
</Card>
|
|
94
|
+
</a>
|
|
95
|
+
))}
|
|
118
96
|
</div>
|
|
119
97
|
</div>
|
|
120
98
|
);
|
|
121
99
|
}
|
|
122
100
|
|
|
123
101
|
export default function HomePage(): React.ReactElement {
|
|
124
|
-
const isLoggedIn = !!(window as unknown as { __AUTH_TOKEN__?: string })
|
|
125
|
-
.__AUTH_TOKEN__;
|
|
102
|
+
const isLoggedIn = !!(window as unknown as { __AUTH_TOKEN__?: string }).__AUTH_TOKEN__;
|
|
126
103
|
if (isLoggedIn) return <HomePageAuthenticated />;
|
|
127
104
|
return <HomePageUnauthenticated />;
|
|
128
105
|
}
|
package/templates/gitignore
CHANGED