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
package/templates/client/App.tsx
CHANGED
|
@@ -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
|
-
|
|
20
|
-
case '
|
|
21
|
-
|
|
22
|
-
|
|
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);
|
package/templates/shared/api.ts
CHANGED
|
@@ -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({
|