@agentuity/cli 3.0.0-alpha.1 → 3.0.0-alpha.2
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/cmd/project/frameworks.d.ts +13 -13
- package/dist/cmd/project/frameworks.d.ts.map +1 -1
- package/dist/cmd/project/frameworks.js +26 -21
- package/dist/cmd/project/frameworks.js.map +1 -1
- package/dist/cmd/project/scaffold.d.ts.map +1 -1
- package/dist/cmd/project/scaffold.js +13 -23
- package/dist/cmd/project/scaffold.js.map +1 -1
- package/dist/cmd/project/template-flow.d.ts.map +1 -1
- package/dist/cmd/project/template-flow.js +4 -1
- package/dist/cmd/project/template-flow.js.map +1 -1
- package/package.json +7 -7
- package/src/cmd/project/frameworks.ts +31 -47
- package/src/cmd/project/scaffold.ts +14 -26
- package/src/cmd/project/template-flow.ts +3 -1
- package/src/cmd/project/templates/astro/src/pages/api/translate.ts +22 -0
- package/src/cmd/project/templates/astro/src/pages/index.astro +160 -0
- package/src/cmd/project/templates/hono/src/index.ts +103 -0
- package/src/cmd/project/templates/nextjs/src/app/api/translate/route.ts +19 -0
- package/src/cmd/project/templates/nextjs/src/app/globals.css +74 -0
- package/src/cmd/project/templates/nextjs/src/app/page.tsx +234 -0
- package/src/cmd/project/templates/nuxt/app.vue +191 -0
- package/src/cmd/project/templates/nuxt/server/api/translate.post.ts +18 -0
- package/src/cmd/project/templates/remix/app/routes/api.translate.ts +24 -0
- package/src/cmd/project/templates/remix/app/routes/home.tsx +241 -0
- package/src/cmd/project/templates/sveltekit/src/routes/+page.server.ts +24 -0
- package/src/cmd/project/templates/sveltekit/src/routes/+page.svelte +204 -0
- package/src/cmd/project/templates/vite-react/server.ts +39 -0
- package/src/cmd/project/templates/vite-react/src/App.tsx +241 -0
- package/src/cmd/project/templates/vite-react/src/index.css +31 -0
- package/src/cmd/project/templates/vite-react/src/main.tsx +15 -0
- package/dist/cmd/project/frameworks-ai-examples.d.ts +0 -15
- package/dist/cmd/project/frameworks-ai-examples.d.ts.map +0 -1
- package/dist/cmd/project/frameworks-ai-examples.js +0 -160
- package/dist/cmd/project/frameworks-ai-examples.js.map +0 -1
- package/dist/cmd/project/frameworks-landing-pages.d.ts +0 -17
- package/dist/cmd/project/frameworks-landing-pages.d.ts.map +0 -1
- package/dist/cmd/project/frameworks-landing-pages.js +0 -242
- package/dist/cmd/project/frameworks-landing-pages.js.map +0 -1
- package/src/cmd/project/frameworks-ai-examples.ts +0 -166
- package/src/cmd/project/frameworks-landing-pages.ts +0 -267
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, type ChangeEvent } from 'react';
|
|
4
|
+
import useSWRMutation from 'swr/mutation';
|
|
5
|
+
|
|
6
|
+
const LANGUAGES = ['Spanish', 'French', 'German', 'Chinese'] as const;
|
|
7
|
+
const MODELS = ['gpt-4o-mini', 'gpt-4o', 'gpt-4.1-nano'] as const;
|
|
8
|
+
const DEFAULT_TEXT =
|
|
9
|
+
'Welcome to Agentuity! This translation demo shows what you can build with the platform. It connects to AI models through our gateway — no separate API keys needed. Try translating this text into different languages to see it in action.';
|
|
10
|
+
|
|
11
|
+
async function translateRequest(
|
|
12
|
+
url: string,
|
|
13
|
+
{ arg }: { arg: { text: string; toLanguage: string; model: string } },
|
|
14
|
+
) {
|
|
15
|
+
const res = await fetch(url, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify(arg),
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
throw new Error(`API error ${res.status}: ${await res.text()}`);
|
|
22
|
+
}
|
|
23
|
+
return res.json();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function Home() {
|
|
27
|
+
const [text, setText] = useState(DEFAULT_TEXT);
|
|
28
|
+
const [toLanguage, setToLanguage] = useState<(typeof LANGUAGES)[number]>('Spanish');
|
|
29
|
+
const [model, setModel] = useState<(typeof MODELS)[number]>('gpt-4o-mini');
|
|
30
|
+
|
|
31
|
+
const { trigger, data, error, isMutating } = useSWRMutation(
|
|
32
|
+
'/api/translate',
|
|
33
|
+
translateRequest,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const handleTranslate = () => {
|
|
37
|
+
trigger({ text, toLanguage, model });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="flex min-h-screen justify-center font-sans text-white">
|
|
42
|
+
<div className="flex w-full max-w-3xl flex-col gap-4 p-16">
|
|
43
|
+
{/* Header */}
|
|
44
|
+
<div className="relative mb-8 flex flex-col items-center justify-center gap-2 text-center">
|
|
45
|
+
<svg
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
className="mb-4 h-auto w-12"
|
|
48
|
+
fill="none"
|
|
49
|
+
height="191"
|
|
50
|
+
viewBox="0 0 220 191"
|
|
51
|
+
width="220"
|
|
52
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
>
|
|
54
|
+
<path
|
|
55
|
+
clipRule="evenodd"
|
|
56
|
+
d="M220 191H0L31.427 136.5H0L8 122.5H180.5L220 191ZM47.5879 136.5L24.2339 177H195.766L172.412 136.5H47.5879Z"
|
|
57
|
+
fill="var(--color-cyan-500)"
|
|
58
|
+
fillRule="evenodd"
|
|
59
|
+
/>
|
|
60
|
+
<path
|
|
61
|
+
clipRule="evenodd"
|
|
62
|
+
d="M110 0L157.448 82.5H189L197 96.5H54.5L110 0ZM78.7021 82.5L110 28.0811L141.298 82.5H78.7021Z"
|
|
63
|
+
fill="var(--color-cyan-500)"
|
|
64
|
+
fillRule="evenodd"
|
|
65
|
+
/>
|
|
66
|
+
</svg>
|
|
67
|
+
<h1 className="text-5xl font-thin">Welcome to Agentuity</h1>
|
|
68
|
+
<p className="text-lg text-gray-400">
|
|
69
|
+
<span className="font-serif italic">Next.js</span> + AI Gateway
|
|
70
|
+
</p>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{/* Translate Form */}
|
|
74
|
+
<div className="flex flex-col gap-6 rounded-lg border border-gray-900 bg-black p-8 text-gray-400 shadow-2xl">
|
|
75
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
76
|
+
Translate to
|
|
77
|
+
<select
|
|
78
|
+
className="-mb-0.5 cursor-pointer appearance-none border-0 border-b border-dashed border-gray-700 bg-transparent font-normal text-white outline-none hover:border-b-cyan-400 focus:border-b-cyan-400"
|
|
79
|
+
disabled={isMutating}
|
|
80
|
+
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
|
|
81
|
+
setToLanguage(e.currentTarget.value as (typeof LANGUAGES)[number])
|
|
82
|
+
}
|
|
83
|
+
value={toLanguage}
|
|
84
|
+
>
|
|
85
|
+
{LANGUAGES.map((lang) => (
|
|
86
|
+
<option key={lang} value={lang}>
|
|
87
|
+
{lang}
|
|
88
|
+
</option>
|
|
89
|
+
))}
|
|
90
|
+
</select>
|
|
91
|
+
using
|
|
92
|
+
<select
|
|
93
|
+
className="-mb-0.5 cursor-pointer appearance-none border-0 border-b border-dashed border-gray-700 bg-transparent font-normal text-white outline-none hover:border-b-cyan-400 focus:border-b-cyan-400"
|
|
94
|
+
disabled={isMutating}
|
|
95
|
+
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
|
|
96
|
+
setModel(e.currentTarget.value as (typeof MODELS)[number])
|
|
97
|
+
}
|
|
98
|
+
value={model}
|
|
99
|
+
>
|
|
100
|
+
{MODELS.map((m) => (
|
|
101
|
+
<option key={m} value={m}>
|
|
102
|
+
{m}
|
|
103
|
+
</option>
|
|
104
|
+
))}
|
|
105
|
+
</select>
|
|
106
|
+
<div className="group relative z-0 ml-auto">
|
|
107
|
+
<div className="absolute inset-0 rounded-lg bg-linear-to-r from-cyan-700 via-blue-500 to-purple-600 opacity-75 blur-xl transition-all duration-700 group-hover:opacity-100 group-hover:blur-2xl" />
|
|
108
|
+
<div className="absolute inset-0 rounded-lg bg-cyan-500/50 opacity-50 blur-3xl" />
|
|
109
|
+
<button
|
|
110
|
+
className="relative cursor-pointer rounded-lg bg-gray-950 px-4 py-2 font-semibold text-white shadow-2xl disabled:cursor-not-allowed disabled:opacity-50"
|
|
111
|
+
disabled={isMutating || !text.trim()}
|
|
112
|
+
onClick={handleTranslate}
|
|
113
|
+
type="button"
|
|
114
|
+
data-loading={isMutating}
|
|
115
|
+
>
|
|
116
|
+
{isMutating ? 'Translating' : 'Translate'}
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<textarea
|
|
122
|
+
className="z-10 min-h-28 resize-y rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-white focus:outline-2 focus:outline-offset-2 focus:outline-cyan-500"
|
|
123
|
+
disabled={isMutating}
|
|
124
|
+
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setText(e.currentTarget.value)}
|
|
125
|
+
placeholder="Enter text to translate..."
|
|
126
|
+
rows={4}
|
|
127
|
+
value={text}
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
{/* Translation Result */}
|
|
131
|
+
{error ? (
|
|
132
|
+
<div className="rounded-md border border-red-800 bg-red-950 px-4 py-3 text-sm text-red-400">
|
|
133
|
+
{error.message}
|
|
134
|
+
</div>
|
|
135
|
+
) : isMutating ? (
|
|
136
|
+
<div
|
|
137
|
+
className="rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-gray-600"
|
|
138
|
+
data-loading="true"
|
|
139
|
+
/>
|
|
140
|
+
) : !data?.translation ? (
|
|
141
|
+
<div className="output rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-gray-600">
|
|
142
|
+
Translation will appear here
|
|
143
|
+
</div>
|
|
144
|
+
) : (
|
|
145
|
+
<div className="flex flex-col gap-3">
|
|
146
|
+
<div className="output rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-cyan-500">
|
|
147
|
+
{data.translation}
|
|
148
|
+
</div>
|
|
149
|
+
<div className="flex gap-4 text-xs text-gray-500">
|
|
150
|
+
{data.tokens > 0 && (
|
|
151
|
+
<span>
|
|
152
|
+
Tokens <strong className="text-gray-400">{data.tokens}</strong>
|
|
153
|
+
</span>
|
|
154
|
+
)}
|
|
155
|
+
<span>
|
|
156
|
+
Model <strong className="text-gray-400">{data.model}</strong>
|
|
157
|
+
</span>
|
|
158
|
+
<span>
|
|
159
|
+
Language <strong className="text-gray-400">{data.toLanguage}</strong>
|
|
160
|
+
</span>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
{/* How it works */}
|
|
167
|
+
<div className="rounded-lg border border-gray-900 bg-black p-8">
|
|
168
|
+
<h3 className="m-0 mb-6 text-xl font-normal leading-none text-white">
|
|
169
|
+
How it works
|
|
170
|
+
</h3>
|
|
171
|
+
<div className="flex flex-col gap-6">
|
|
172
|
+
{[
|
|
173
|
+
{
|
|
174
|
+
title: 'AI Gateway routing',
|
|
175
|
+
text: (
|
|
176
|
+
<>
|
|
177
|
+
<code className="text-white">agentuity dev</code> automatically sets
|
|
178
|
+
OPENAI_API_KEY and OPENAI_BASE_URL so the AI SDK routes through the
|
|
179
|
+
Agentuity gateway.
|
|
180
|
+
</>
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
title: 'API routes',
|
|
185
|
+
text: (
|
|
186
|
+
<>
|
|
187
|
+
Edit{' '}
|
|
188
|
+
<code className="text-white">src/app/api/translate/route.ts</code> to
|
|
189
|
+
change the AI model, prompt, or add new routes.
|
|
190
|
+
</>
|
|
191
|
+
),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
title: 'Next.js App Router',
|
|
195
|
+
text: (
|
|
196
|
+
<>
|
|
197
|
+
Add routes in <code className="text-white">src/app/</code> — file-based
|
|
198
|
+
routing with React Server Components.
|
|
199
|
+
</>
|
|
200
|
+
),
|
|
201
|
+
},
|
|
202
|
+
].map((step) => (
|
|
203
|
+
<div key={step.title} className="flex items-start gap-3">
|
|
204
|
+
<div className="flex size-4 shrink-0 items-center justify-center rounded border border-green-500 bg-green-950">
|
|
205
|
+
<svg
|
|
206
|
+
aria-hidden="true"
|
|
207
|
+
className="size-2.5"
|
|
208
|
+
fill="none"
|
|
209
|
+
height="24"
|
|
210
|
+
stroke="var(--color-green-500)"
|
|
211
|
+
strokeLinecap="round"
|
|
212
|
+
strokeLinejoin="round"
|
|
213
|
+
strokeWidth="2"
|
|
214
|
+
viewBox="0 0 24 24"
|
|
215
|
+
width="24"
|
|
216
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
217
|
+
>
|
|
218
|
+
<path d="M20 6 9 17l-5-5" />
|
|
219
|
+
</svg>
|
|
220
|
+
</div>
|
|
221
|
+
<div>
|
|
222
|
+
<h4 className="-mt-0.5 mb-0.5 text-sm font-normal text-white">
|
|
223
|
+
{step.title}
|
|
224
|
+
</h4>
|
|
225
|
+
<p className="text-xs text-gray-400">{step.text}</p>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
))}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const LANGUAGES = ['Spanish', 'French', 'German', 'Chinese'] as const;
|
|
3
|
+
const MODELS = ['gpt-4o-mini', 'gpt-4o', 'gpt-4.1-nano'] as const;
|
|
4
|
+
const DEFAULT_TEXT =
|
|
5
|
+
'Welcome to Agentuity! This translation demo shows what you can build with the platform. It connects to AI models through our gateway — no separate API keys needed. Try translating this text into different languages to see it in action.';
|
|
6
|
+
|
|
7
|
+
const text = ref(DEFAULT_TEXT);
|
|
8
|
+
const toLanguage = ref<(typeof LANGUAGES)[number]>('Spanish');
|
|
9
|
+
const model = ref<(typeof MODELS)[number]>('gpt-4o-mini');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
data: result,
|
|
13
|
+
error,
|
|
14
|
+
status,
|
|
15
|
+
execute: handleTranslate,
|
|
16
|
+
} = useFetch('/api/translate', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
body: computed(() => ({ text: text.value, toLanguage: toLanguage.value, model: model.value })),
|
|
19
|
+
immediate: false,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const isLoading = computed(() => status.value === 'pending');
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div class="flex min-h-screen justify-center font-sans text-white">
|
|
27
|
+
<div class="flex w-full max-w-3xl flex-col gap-4 p-16">
|
|
28
|
+
<!-- Header -->
|
|
29
|
+
<div class="relative mb-8 flex flex-col items-center justify-center gap-2 text-center">
|
|
30
|
+
<svg
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
class="mb-4 h-auto w-12"
|
|
33
|
+
fill="none"
|
|
34
|
+
height="191"
|
|
35
|
+
viewBox="0 0 220 191"
|
|
36
|
+
width="220"
|
|
37
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
38
|
+
>
|
|
39
|
+
<path
|
|
40
|
+
clip-rule="evenodd"
|
|
41
|
+
d="M220 191H0L31.427 136.5H0L8 122.5H180.5L220 191ZM47.5879 136.5L24.2339 177H195.766L172.412 136.5H47.5879Z"
|
|
42
|
+
fill="var(--color-cyan-500)"
|
|
43
|
+
fill-rule="evenodd"
|
|
44
|
+
/>
|
|
45
|
+
<path
|
|
46
|
+
clip-rule="evenodd"
|
|
47
|
+
d="M110 0L157.448 82.5H189L197 96.5H54.5L110 0ZM78.7021 82.5L110 28.0811L141.298 82.5H78.7021Z"
|
|
48
|
+
fill="var(--color-cyan-500)"
|
|
49
|
+
fill-rule="evenodd"
|
|
50
|
+
/>
|
|
51
|
+
</svg>
|
|
52
|
+
<h1 class="text-5xl font-thin">Welcome to Agentuity</h1>
|
|
53
|
+
<p class="text-lg text-gray-400">
|
|
54
|
+
<span class="font-serif italic">Nuxt</span> + AI Gateway
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- Translate Form -->
|
|
59
|
+
<div
|
|
60
|
+
class="flex flex-col gap-6 rounded-lg border border-gray-900 bg-black p-8 text-gray-400 shadow-2xl"
|
|
61
|
+
>
|
|
62
|
+
<div class="flex flex-wrap items-center gap-1.5">
|
|
63
|
+
Translate to
|
|
64
|
+
<select
|
|
65
|
+
class="-mb-0.5 cursor-pointer appearance-none border-0 border-b border-dashed border-gray-700 bg-transparent font-normal text-white outline-none hover:border-b-cyan-400 focus:border-b-cyan-400"
|
|
66
|
+
:disabled="isLoading"
|
|
67
|
+
v-model="toLanguage"
|
|
68
|
+
>
|
|
69
|
+
<option v-for="lang in LANGUAGES" :key="lang" :value="lang">{{ lang }}</option>
|
|
70
|
+
</select>
|
|
71
|
+
using
|
|
72
|
+
<select
|
|
73
|
+
class="-mb-0.5 cursor-pointer appearance-none border-0 border-b border-dashed border-gray-700 bg-transparent font-normal text-white outline-none hover:border-b-cyan-400 focus:border-b-cyan-400"
|
|
74
|
+
:disabled="isLoading"
|
|
75
|
+
v-model="model"
|
|
76
|
+
>
|
|
77
|
+
<option v-for="m in MODELS" :key="m" :value="m">{{ m }}</option>
|
|
78
|
+
</select>
|
|
79
|
+
<div class="group relative z-0 ml-auto">
|
|
80
|
+
<div
|
|
81
|
+
class="absolute inset-0 rounded-lg bg-linear-to-r from-cyan-700 via-blue-500 to-purple-600 opacity-75 blur-xl transition-all duration-700 group-hover:opacity-100 group-hover:blur-2xl"
|
|
82
|
+
/>
|
|
83
|
+
<div class="absolute inset-0 rounded-lg bg-cyan-500/50 opacity-50 blur-3xl" />
|
|
84
|
+
<button
|
|
85
|
+
class="relative cursor-pointer rounded-lg bg-gray-950 px-4 py-2 font-semibold text-white shadow-2xl disabled:cursor-not-allowed disabled:opacity-50"
|
|
86
|
+
:disabled="isLoading || !text.trim()"
|
|
87
|
+
@click="handleTranslate()"
|
|
88
|
+
type="button"
|
|
89
|
+
:data-loading="isLoading"
|
|
90
|
+
>
|
|
91
|
+
{{ isLoading ? 'Translating' : 'Translate' }}
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<textarea
|
|
97
|
+
class="z-10 min-h-28 resize-y rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-white focus:outline-2 focus:outline-offset-2 focus:outline-cyan-500"
|
|
98
|
+
:disabled="isLoading"
|
|
99
|
+
v-model="text"
|
|
100
|
+
placeholder="Enter text to translate..."
|
|
101
|
+
rows="4"
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
<!-- Translation Result -->
|
|
105
|
+
<div
|
|
106
|
+
v-if="error"
|
|
107
|
+
class="rounded-md border border-red-800 bg-red-950 px-4 py-3 text-sm text-red-400"
|
|
108
|
+
>
|
|
109
|
+
{{ error.message }}
|
|
110
|
+
</div>
|
|
111
|
+
<div
|
|
112
|
+
v-else-if="isLoading"
|
|
113
|
+
class="rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-gray-600"
|
|
114
|
+
data-loading="true"
|
|
115
|
+
/>
|
|
116
|
+
<div
|
|
117
|
+
v-else-if="!result?.translation"
|
|
118
|
+
class="output rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-gray-600"
|
|
119
|
+
>
|
|
120
|
+
Translation will appear here
|
|
121
|
+
</div>
|
|
122
|
+
<template v-else>
|
|
123
|
+
<div class="flex flex-col gap-3">
|
|
124
|
+
<div
|
|
125
|
+
class="output rounded-md border border-gray-800 bg-gray-950 px-4 py-3 text-sm text-cyan-500"
|
|
126
|
+
>
|
|
127
|
+
{{ result.translation }}
|
|
128
|
+
</div>
|
|
129
|
+
<div class="flex gap-4 text-xs text-gray-500">
|
|
130
|
+
<span v-if="(result as any).tokens > 0">
|
|
131
|
+
Tokens <strong class="text-gray-400">{{ (result as any).tokens }}</strong>
|
|
132
|
+
</span>
|
|
133
|
+
<span>
|
|
134
|
+
Model <strong class="text-gray-400">{{ (result as any).model }}</strong>
|
|
135
|
+
</span>
|
|
136
|
+
<span>
|
|
137
|
+
Language
|
|
138
|
+
<strong class="text-gray-400">{{ (result as any).toLanguage }}</strong>
|
|
139
|
+
</span>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</template>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<!-- How it works -->
|
|
146
|
+
<div class="rounded-lg border border-gray-900 bg-black p-8">
|
|
147
|
+
<h3 class="m-0 mb-6 text-xl font-normal leading-none text-white">How it works</h3>
|
|
148
|
+
<div class="flex flex-col gap-6">
|
|
149
|
+
<div v-for="step in [
|
|
150
|
+
{
|
|
151
|
+
title: 'AI Gateway routing',
|
|
152
|
+
text: '`agentuity dev` automatically sets OPENAI_API_KEY and OPENAI_BASE_URL so the AI SDK routes through the Agentuity gateway.',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
title: 'Server routes',
|
|
156
|
+
text: 'Edit `server/api/translate.post.ts` to change the AI model, prompt, or add new server routes.',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
title: 'Nuxt',
|
|
160
|
+
text: 'Add pages in `pages/` and server routes in `server/api/` — full-stack Vue framework with SSR.',
|
|
161
|
+
},
|
|
162
|
+
]" :key="step.title" class="flex items-start gap-3">
|
|
163
|
+
<div
|
|
164
|
+
class="flex size-4 shrink-0 items-center justify-center rounded border border-green-500 bg-green-950"
|
|
165
|
+
>
|
|
166
|
+
<svg
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
class="size-2.5"
|
|
169
|
+
fill="none"
|
|
170
|
+
height="24"
|
|
171
|
+
stroke="var(--color-green-500)"
|
|
172
|
+
stroke-linecap="round"
|
|
173
|
+
stroke-linejoin="round"
|
|
174
|
+
stroke-width="2"
|
|
175
|
+
viewBox="0 0 24 24"
|
|
176
|
+
width="24"
|
|
177
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
178
|
+
>
|
|
179
|
+
<path d="M20 6 9 17l-5-5" />
|
|
180
|
+
</svg>
|
|
181
|
+
</div>
|
|
182
|
+
<div>
|
|
183
|
+
<h4 class="-mt-0.5 mb-0.5 text-sm font-normal text-white">{{ step.title }}</h4>
|
|
184
|
+
<p class="text-xs text-gray-400">{{ step.text }}</p>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
import { openai } from '@ai-sdk/openai';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const { text, toLanguage, model = 'gpt-4o-mini' } = await readBody(event);
|
|
6
|
+
|
|
7
|
+
const { text: translation, usage } = await generateText({
|
|
8
|
+
model: openai(model),
|
|
9
|
+
prompt: `Translate the following text to ${toLanguage}. Return only the translation, nothing else.\n\n${text}`,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
translation,
|
|
14
|
+
tokens: usage?.totalTokens ?? 0,
|
|
15
|
+
model,
|
|
16
|
+
toLanguage,
|
|
17
|
+
};
|
|
18
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { generateText } from 'ai';
|
|
2
|
+
import { openai } from '@ai-sdk/openai';
|
|
3
|
+
import { data } from 'react-router';
|
|
4
|
+
import type { Route } from './+types/api.translate';
|
|
5
|
+
|
|
6
|
+
export async function action({ request }: Route.ActionArgs) {
|
|
7
|
+
const { text, toLanguage, model = 'gpt-4o-mini' } = await request.json();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const { text: translation, usage } = await generateText({
|
|
11
|
+
model: openai(model),
|
|
12
|
+
prompt: `Translate the following text to ${toLanguage}. Return only the translation, nothing else.\n\n${text}`,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return data({
|
|
16
|
+
translation,
|
|
17
|
+
tokens: usage?.totalTokens ?? 0,
|
|
18
|
+
model,
|
|
19
|
+
toLanguage,
|
|
20
|
+
});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw data({ message: error instanceof Error ? error.message : 'Translation failed' }, { status: 500 });
|
|
23
|
+
}
|
|
24
|
+
}
|