ugly-app 0.1.19 → 0.1.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-app",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "main": "./dist/server/index.js",
6
6
  "exports": {
@@ -3,6 +3,7 @@ import type { AppSocket } from 'ugly-app/client';
3
3
  import { FeedbackButton } from 'ugly-app/client';
4
4
  import type { RouterState } from 'ugly-app/shared';
5
5
  import type { AppRegistry } from '../shared/api';
6
+ import { AITestPage } from './pages/AITestPage';
6
7
  import { AuthDemoPage } from './pages/AuthDemoPage';
7
8
  import { HomePage } from './pages/HomePage';
8
9
  import { SearchPage } from './pages/SearchPage';
@@ -15,14 +16,11 @@ interface AppProps {
15
16
 
16
17
  function renderPage(state: RouterState): React.ReactNode {
17
18
  switch (state.routeName) {
18
- case 'auth-demo':
19
- return <AuthDemoPage />;
20
- case 'user/:userId':
21
- return <UserPage userId={state.params.userId ?? ''} />;
22
- case 'search':
23
- return <SearchPage />;
24
- default:
25
- return <HomePage />;
19
+ case 'auth-demo': return <AuthDemoPage />;
20
+ case 'user/:userId': return <UserPage userId={state.params.userId ?? ''} />;
21
+ case 'search': return <SearchPage />;
22
+ case 'ai-test': return <AITestPage />;
23
+ default: return <HomePage />;
26
24
  }
27
25
  }
28
26
 
@@ -0,0 +1,145 @@
1
+ import React, { useState } from 'react';
2
+ import { Button, Card, Input, PageLayout, Text } from 'ugly-app/client';
3
+ import { imageGenProviderModels, textGenProviderModels } from 'ugly-app/shared';
4
+
5
+ type Mode = 'text' | 'image';
6
+
7
+ const UGLY_BOT_URL = import.meta.env.VITE_UGLY_BOT_URL ?? 'https://ugly.bot';
8
+
9
+ async function callUglyBot(op: string, input: unknown): Promise<unknown> {
10
+ const token = (window as unknown as { __AUTH_TOKEN__?: string }).__AUTH_TOKEN__;
11
+ const res = await fetch(`${UGLY_BOT_URL}/request`, {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Authorization': `Bearer ${token ?? ''}`,
15
+ 'Content-Type': 'application/json',
16
+ },
17
+ body: JSON.stringify({ op, input, sessionId: 'ai-test' }),
18
+ });
19
+ if (!res.ok) {
20
+ const body = await res.text().catch(() => '');
21
+ throw new Error(`${res.status}: ${body}`);
22
+ }
23
+ const data = (await res.json()) as { result: unknown };
24
+ return data.result;
25
+ }
26
+
27
+ export function AITestPage(): React.ReactElement {
28
+ const [mode, setMode] = useState<Mode>('text');
29
+ const [prompt, setPrompt] = useState('');
30
+ const [textModel, setTextModel] = useState(textGenProviderModels[0] ?? '');
31
+ const [imageModel, setImageModel] = useState(imageGenProviderModels[0] ?? '');
32
+ const [result, setResult] = useState('');
33
+ const [imageUrl, setImageUrl] = useState('');
34
+ const [loading, setLoading] = useState(false);
35
+ const [error, setError] = useState('');
36
+
37
+ async function handleRun(): Promise<void> {
38
+ if (!prompt.trim()) return;
39
+ setLoading(true);
40
+ setError('');
41
+ setResult('');
42
+ setImageUrl('');
43
+ try {
44
+ if (mode === 'text') {
45
+ const text = await callUglyBot('textGen', {
46
+ messages: [{ role: 'user', content: prompt }],
47
+ options: { provider: textModel },
48
+ });
49
+ setResult(String(text));
50
+ } else {
51
+ const url = await callUglyBot('imageGen', {
52
+ prompt,
53
+ options: { provider: imageModel },
54
+ });
55
+ setImageUrl(String(url));
56
+ }
57
+ } catch (err) {
58
+ setError(err instanceof Error ? err.message : String(err));
59
+ } finally {
60
+ setLoading(false);
61
+ }
62
+ }
63
+
64
+ const model = mode === 'text' ? textModel : imageModel;
65
+ const models = mode === 'text' ? textGenProviderModels : imageGenProviderModels;
66
+
67
+ return (
68
+ <PageLayout
69
+ header={
70
+ <div className="px-6 py-4">
71
+ <a href="/" className="text-blue-600">← Home</a>
72
+ </div>
73
+ }
74
+ >
75
+ <div className="max-w-2xl mx-auto">
76
+ <Text size="xl" weight="bold">AI Model Test</Text>
77
+
78
+ <div className="mt-4 flex gap-2">
79
+ <Button
80
+ variant={mode === 'text' ? 'primary' : 'secondary'}
81
+ onClick={() => setMode('text')}
82
+ >
83
+ Text
84
+ </Button>
85
+ <Button
86
+ variant={mode === 'image' ? 'primary' : 'secondary'}
87
+ onClick={() => setMode('image')}
88
+ >
89
+ Image
90
+ </Button>
91
+ </div>
92
+
93
+ <Card className="mt-4">
94
+ <div className="space-y-4">
95
+ <div>
96
+ <Text size="sm" weight="medium" className="mb-1">Model</Text>
97
+ <select
98
+ value={model}
99
+ onChange={(e) => mode === 'text' ? setTextModel(e.target.value) : setImageModel(e.target.value)}
100
+ className="w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 text-sm bg-white dark:bg-gray-800"
101
+ >
102
+ {models.map((m) => (
103
+ <option key={m} value={m}>{m}</option>
104
+ ))}
105
+ </select>
106
+ </div>
107
+
108
+ <Input
109
+ label="Prompt"
110
+ value={prompt}
111
+ onChange={setPrompt}
112
+ placeholder={mode === 'text' ? 'Ask something...' : 'Describe an image...'}
113
+ />
114
+
115
+ <Button onClick={handleRun} disabled={loading || !prompt.trim()}>
116
+ {loading ? 'Running...' : 'Run'}
117
+ </Button>
118
+ </div>
119
+ </Card>
120
+
121
+ {error && (
122
+ <Card className="mt-4 border-red-300">
123
+ <Text size="sm" className="text-red-600">{error}</Text>
124
+ </Card>
125
+ )}
126
+
127
+ {result && (
128
+ <Card className="mt-4">
129
+ <Text size="sm" weight="medium" className="mb-2">Result</Text>
130
+ <pre className="text-sm whitespace-pre-wrap break-words bg-gray-50 dark:bg-gray-900 p-3 rounded">
131
+ {result}
132
+ </pre>
133
+ </Card>
134
+ )}
135
+
136
+ {imageUrl && (
137
+ <Card className="mt-4">
138
+ <Text size="sm" weight="medium" className="mb-2">Result</Text>
139
+ <img src={imageUrl} alt="Generated" className="w-full rounded" />
140
+ </Card>
141
+ )}
142
+ </div>
143
+ </PageLayout>
144
+ );
145
+ }
@@ -98,6 +98,14 @@ function HomePageBody({
98
98
  </Text>
99
99
  </Card>
100
100
  </a>
101
+ <a href="/ai-test" className="block">
102
+ <Card className="hover:shadow-md transition-shadow cursor-pointer">
103
+ <Text weight="medium">AI Model Test →</Text>
104
+ <Text size="sm" className="text-gray-500 mt-1">
105
+ Test text & image generation models
106
+ </Text>
107
+ </Card>
108
+ </a>
101
109
  </div>
102
110
  </div>
103
111
  );
@@ -1,12 +1,5 @@
1
- import {
2
- createApp,
3
- createUserHelper,
4
- imageGenCreate,
5
- registerFeedbackHandlers,
6
- textGenCreate,
7
- } from 'ugly-app';
1
+ import { createApp, createUserHelper, registerFeedbackHandlers } from 'ugly-app';
8
2
  import { dbDefaults } from 'ugly-app/shared';
9
- import type { ImageGenModelProvider, TextGenModelProvider } from 'ugly-app/shared';
10
3
  import { functions, requests } from '../shared/api';
11
4
  import { collections } from '../shared/collections';
12
5
  import { pages } from '../shared/pages';
@@ -25,22 +18,6 @@ const app = createApp({ functions, requests }, collections, (configurator) => {
25
18
  const maintainBotUserId = process.env.MAINTAIN_BOT_USER_ID ?? '';
26
19
  registerFeedbackHandlers(app, maintainBotUserId);
27
20
 
28
- // Register function handlers
29
- app.registerFunction('testTextGen', async (_ctx, input) => {
30
- const { prompt, model } = input as { prompt: string; model: string };
31
- const textGen = textGenCreate(model as TextGenModelProvider);
32
- const response = await textGen.complete({ prompt }, null);
33
- return { result: response?.text ?? '' };
34
- });
35
-
36
- app.registerFunction('testImageGen', async (_ctx, input) => {
37
- const { prompt, model } = input as { prompt: string; model: string };
38
- const imageGen = imageGenCreate(model as ImageGenModelProvider);
39
- const result = await imageGen.generate({ prompt, negativePrompt: '', size: 'square', allowSexual: false, images: [] });
40
- const imageUrl = result ? `data:${result.mime ?? 'image/jpeg'};base64,${result.base64}` : '';
41
- return { imageUrl };
42
- });
43
-
44
21
  // Register request handlers
45
22
  app.registerRequest('getMe', async (ctx) => {
46
23
  const user = await userHelper.get(ctx.db, ctx.userId);
@@ -10,20 +10,6 @@ export const functions = defineFunctions({
10
10
  }),
11
11
  output: z.object({ ok: z.boolean() }),
12
12
  }),
13
- testTextGen: fn({
14
- input: z.object({
15
- prompt: z.string().min(1).max(2000),
16
- model: z.string(),
17
- }),
18
- output: z.object({ result: z.string() }),
19
- }),
20
- testImageGen: fn({
21
- input: z.object({
22
- prompt: z.string().min(1).max(500),
23
- model: z.string(),
24
- }),
25
- output: z.object({ imageUrl: z.string() }),
26
- }),
27
13
  });
28
14
 
29
15
  export const requests = defineRequests({