alchemy 0.2.5 → 0.3.1
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 +3 -3
- package/lib/ai/approve.d.ts +99 -0
- package/lib/ai/approve.js +76 -0
- package/lib/ai/astro-file.d.ts +137 -0
- package/lib/ai/astro-file.js +156 -0
- package/lib/ai/client.d.ts +7 -8
- package/lib/ai/client.js +48 -19
- package/lib/ai/css-file.d.ts +131 -0
- package/lib/ai/css-file.js +141 -0
- package/lib/ai/data.d.ts +51 -6
- package/lib/ai/data.js +58 -12
- package/lib/ai/document.d.ts +108 -24
- package/lib/ai/document.js +166 -52
- package/lib/ai/html-file.d.ts +128 -0
- package/lib/ai/html-file.js +138 -0
- package/lib/ai/index.d.ts +9 -0
- package/lib/ai/index.js +9 -0
- package/lib/ai/json-file.d.ts +160 -0
- package/lib/ai/json-file.js +187 -0
- package/lib/ai/review.d.ts +122 -0
- package/lib/ai/review.js +101 -0
- package/lib/ai/typescript-file.d.ts +139 -0
- package/lib/ai/typescript-file.js +145 -0
- package/lib/ai/vue-file.d.ts +126 -0
- package/lib/ai/vue-file.js +136 -0
- package/lib/ai/yaml-file.d.ts +169 -0
- package/lib/ai/yaml-file.js +200 -0
- package/lib/alchemy.js +17 -2
- package/lib/apply.js +1 -1
- package/lib/cloudflare/dns.d.ts +88 -0
- package/lib/cloudflare/dns.js +192 -0
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/index.d.ts +2 -1
- package/lib/cloudflare/index.js +2 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
- package/lib/cloudflare/r2-rest-state-store.js +254 -0
- package/lib/cloudflare/response.d.ts +12 -0
- package/lib/cloudflare/response.js +0 -0
- package/lib/cloudflare/static-site-router.js +16 -7
- package/lib/cloudflare/static-site.js +3 -0
- package/lib/cloudflare/upload-asset-manifest.js +4 -2
- package/lib/cloudflare/zone.d.ts +18 -0
- package/lib/cloudflare/zone.js +17 -4
- package/lib/dns/godaddy.d.ts +7 -0
- package/lib/dns/godaddy.js +21 -0
- package/lib/dns/import-dns.d.ts +82 -0
- package/lib/dns/import-dns.js +114 -0
- package/lib/dns/index.d.ts +2 -0
- package/lib/dns/index.js +2 -0
- package/lib/dns/record.d.ts +88 -0
- package/lib/dns/record.js +13 -0
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file-system-state-store.d.ts +17 -0
- package/lib/fs/file-system-state-store.js +87 -0
- package/lib/fs/file.d.ts +13 -0
- package/lib/fs/file.js +24 -5
- package/lib/fs/folder.d.ts +10 -0
- package/lib/fs/folder.js +2 -2
- package/lib/fs/index.d.ts +10 -4
- package/lib/fs/index.js +10 -4
- package/lib/fs/static-astro-file.d.ts +34 -0
- package/lib/fs/static-astro-file.js +39 -0
- package/lib/fs/static-css-file.d.ts +24 -0
- package/lib/fs/static-css-file.js +29 -0
- package/lib/fs/static-html-file.d.ts +31 -0
- package/lib/fs/static-html-file.js +36 -0
- package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +3 -3
- package/lib/fs/static-json-file.js +15 -0
- package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
- package/lib/fs/static-text-file.js +8 -0
- package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
- package/lib/fs/{typescript-file.js → static-typescript-file.js} +3 -2
- package/lib/fs/static-vue-file.d.ts +28 -0
- package/lib/fs/static-vue-file.js +33 -0
- package/lib/fs/static-yaml-file.d.ts +22 -0
- package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
- package/lib/internal/getting-started.d.ts +21 -0
- package/lib/internal/getting-started.js +87 -0
- package/lib/internal/index.d.ts +3 -0
- package/lib/internal/index.js +3 -0
- package/lib/internal/providers.d.ts +27 -0
- package/lib/internal/providers.js +172 -0
- package/lib/internal/tutorial.d.ts +104 -0
- package/lib/internal/tutorial.js +251 -0
- package/lib/resource.js +8 -1
- package/lib/scope.d.ts +1 -1
- package/lib/scope.js +4 -2
- package/lib/state.d.ts +1 -16
- package/lib/state.js +0 -87
- package/lib/test/bun.d.ts +11 -0
- package/lib/test/bun.js +36 -8
- package/lib/web/astro.d.ts +147 -0
- package/lib/web/astro.js +414 -0
- package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
- package/lib/web/shadcn.d.ts +97 -0
- package/lib/web/shadcn.js +99 -0
- package/lib/web/tailwind.d.ts +64 -0
- package/lib/web/tailwind.js +97 -0
- package/lib/{vite → web}/vite.d.ts +5 -0
- package/lib/{vite → web}/vite.js +44 -44
- package/lib/web/vitepress/config.d.ts +10 -0
- package/lib/web/vitepress/config.js +31 -0
- package/lib/web/vitepress/custom-theme.d.ts +173 -0
- package/lib/web/vitepress/custom-theme.js +336 -0
- package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
- package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
- package/lib/web/vitepress/home-page.d.ts +246 -0
- package/lib/web/vitepress/home-page.js +147 -0
- package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
- package/lib/{vitepress → web/vitepress}/index.js +2 -0
- package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
- package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
- package/package.json +6 -2
- package/src/ai/approve.ts +163 -0
- package/src/ai/astro-file.ts +269 -0
- package/src/ai/client.ts +60 -21
- package/src/ai/css-file.ts +247 -0
- package/src/ai/data.ts +81 -19
- package/src/ai/document.ts +240 -62
- package/src/ai/html-file.ts +244 -0
- package/src/ai/index.ts +9 -0
- package/src/ai/json-file.ts +325 -0
- package/src/ai/review.ts +213 -0
- package/src/ai/typescript-file.ts +259 -0
- package/src/ai/vue-file.ts +242 -0
- package/src/ai/yaml-file.ts +328 -0
- package/src/alchemy.ts +18 -7
- package/src/apply.ts +8 -9
- package/src/cloudflare/dns.ts +344 -0
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/index.ts +2 -1
- package/src/cloudflare/r2-rest-state-store.ts +353 -0
- package/src/cloudflare/response.ts +9 -0
- package/src/cloudflare/static-site-router.ts +18 -8
- package/src/cloudflare/static-site.ts +11 -7
- package/src/cloudflare/upload-asset-manifest.ts +6 -4
- package/src/cloudflare/worker.ts +44 -43
- package/src/cloudflare/zone.ts +39 -23
- package/src/dns/godaddy.ts +29 -0
- package/src/dns/import-dns.ts +213 -0
- package/src/dns/index.ts +2 -0
- package/src/dns/record.ts +121 -0
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file-system-state-store.ts +109 -0
- package/src/fs/file.ts +35 -9
- package/src/fs/folder.ts +14 -2
- package/src/fs/index.ts +10 -4
- package/src/fs/static-astro-file.ts +45 -0
- package/src/fs/static-css-file.ts +35 -0
- package/src/fs/static-html-file.ts +42 -0
- package/src/fs/static-json-file.ts +34 -0
- package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
- package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
- package/src/fs/static-vue-file.ts +39 -0
- package/src/fs/static-yaml-file.ts +33 -0
- package/src/internal/getting-started.ts +115 -0
- package/src/internal/index.ts +3 -0
- package/src/internal/providers.ts +241 -0
- package/src/internal/tutorial.ts +392 -0
- package/src/resource.ts +10 -1
- package/src/scope.ts +5 -6
- package/src/state.ts +1 -111
- package/src/test/bun.ts +60 -10
- package/src/web/astro.ts +644 -0
- package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
- package/src/web/shadcn.ts +219 -0
- package/src/web/tailwind.ts +167 -0
- package/src/{vite → web}/vite.ts +54 -56
- package/src/web/vitepress/config.ts +48 -0
- package/src/web/vitepress/custom-theme.ts +514 -0
- package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
- package/src/web/vitepress/home-page.ts +375 -0
- package/src/{vitepress → web/vitepress}/index.ts +2 -0
- package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
- package/lib/cloudflare/state.d.ts +0 -82
- package/lib/cloudflare/state.js +0 -108
- package/lib/fs/json-file.js +0 -7
- package/lib/fs/text-file.js +0 -7
- package/lib/fs/yaml-file.d.ts +0 -19
- package/lib/internal/docs.d.ts +0 -5
- package/lib/internal/docs.js +0 -287
- package/lib/shadcn/index.d.ts +0 -1
- package/lib/shadcn/index.js +0 -1
- package/lib/vite/index.d.ts +0 -1
- package/lib/vite/index.js +0 -1
- package/lib/vitepress/home-page.d.ts +0 -133
- package/lib/vitepress/home-page.js +0 -11
- package/src/cloudflare/state.ts +0 -138
- package/src/fs/json-file.ts +0 -23
- package/src/fs/yaml-file.ts +0 -26
- package/src/internal/docs.ts +0 -338
- package/src/shadcn/index.ts +0 -1
- package/src/vite/index.ts +0 -1
- package/src/vitepress/home-page.ts +0 -166
- /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
- /package/src/{vitepress → web/vitepress}/index.md +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { generateText } from "ai";
|
|
2
|
+
import type { Context } from "../context";
|
|
3
|
+
import { StaticTypeScriptFile } from "../fs/static-typescript-file";
|
|
4
|
+
import { Resource } from "../resource";
|
|
5
|
+
import type { Secret } from "../secret";
|
|
6
|
+
import { createModel, type ModelConfig } from "./client";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Properties for creating or updating a TypeScriptFile
|
|
10
|
+
*/
|
|
11
|
+
export interface TypeScriptFileProps {
|
|
12
|
+
/**
|
|
13
|
+
* Path to the TypeScript file
|
|
14
|
+
*/
|
|
15
|
+
path: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base URL for the OpenAI API
|
|
19
|
+
* @default 'https://api.openai.com/v1'
|
|
20
|
+
*/
|
|
21
|
+
baseURL?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Prompt for generating content
|
|
25
|
+
* Use alchemy template literals to include file context:
|
|
26
|
+
* @example
|
|
27
|
+
* prompt: await alchemy`
|
|
28
|
+
* Generate a TypeScript utility function using:
|
|
29
|
+
* ${alchemy.file("src/types.ts")}
|
|
30
|
+
* `
|
|
31
|
+
*/
|
|
32
|
+
prompt: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* System prompt for the model
|
|
36
|
+
* This is used to provide instructions to the model about how to format the response
|
|
37
|
+
* The default system prompt instructs the model to return TypeScript code inside ```ts fences
|
|
38
|
+
* @default "You are a TypeScript code generator. Create TypeScript code based on the user's requirements. Your response MUST include only TypeScript code inside ```ts fences. Do not include any other text, explanations, or multiple code blocks."
|
|
39
|
+
*/
|
|
40
|
+
system?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* OpenAI API key to use for generating content
|
|
44
|
+
* If not provided, will use OPENAI_API_KEY environment variable
|
|
45
|
+
*/
|
|
46
|
+
apiKey?: Secret;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Model configuration
|
|
50
|
+
*/
|
|
51
|
+
model?: ModelConfig;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Temperature for controlling randomness in generation.
|
|
55
|
+
* Higher values (e.g., 0.8) make output more random,
|
|
56
|
+
* lower values (e.g., 0.2) make it more deterministic.
|
|
57
|
+
* @default 0.7
|
|
58
|
+
*/
|
|
59
|
+
temperature?: number;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Prettier configuration to use for formatting the TypeScript code
|
|
63
|
+
* If not provided, will use the default Prettier configuration
|
|
64
|
+
*/
|
|
65
|
+
prettierConfig?: object;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A TypeScript file that can be created, updated, and deleted
|
|
70
|
+
*/
|
|
71
|
+
export interface TypeScriptFile
|
|
72
|
+
extends TypeScriptFileProps,
|
|
73
|
+
Resource<"ai::TypeScriptFile"> {
|
|
74
|
+
/**
|
|
75
|
+
* Content of the TypeScript file
|
|
76
|
+
*/
|
|
77
|
+
content: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Time at which the file was created
|
|
81
|
+
*/
|
|
82
|
+
createdAt: number;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Time at which the file was last updated
|
|
86
|
+
*/
|
|
87
|
+
updatedAt: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Default system prompt for TypeScript file generation
|
|
92
|
+
*/
|
|
93
|
+
const DEFAULT_TS_SYSTEM_PROMPT =
|
|
94
|
+
"You are a TypeScript code generator. Create TypeScript code based on the user's requirements. Your response MUST include only TypeScript code inside ```ts fences. Do not include any other text, explanations, or multiple code blocks.";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resource for generating TypeScript files using AI models.
|
|
98
|
+
* Extracts TypeScript code from between ```ts fences, validates the response,
|
|
99
|
+
* and formats the code with Prettier.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // Create a utility function
|
|
103
|
+
* const utils = await TypeScriptFile("string-utils", {
|
|
104
|
+
* path: "./src/utils/string-utils.ts",
|
|
105
|
+
* prompt: await alchemy`
|
|
106
|
+
* Generate TypeScript utility functions for string manipulation:
|
|
107
|
+
* - Capitalize first letter
|
|
108
|
+
* - Truncate with ellipsis
|
|
109
|
+
* - Convert to camelCase and kebab-case
|
|
110
|
+
* - Remove special characters
|
|
111
|
+
* `,
|
|
112
|
+
* model: {
|
|
113
|
+
* id: "gpt-4o",
|
|
114
|
+
* provider: "openai"
|
|
115
|
+
* }
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* // Generate a TypeScript class with custom formatting
|
|
120
|
+
* const userService = await TypeScriptFile("user-service", {
|
|
121
|
+
* path: "./src/services/UserService.ts",
|
|
122
|
+
* prompt: await alchemy`
|
|
123
|
+
* Create a UserService class that handles user authentication and profile management.
|
|
124
|
+
* The service should use the User type from:
|
|
125
|
+
* ${alchemy.file("src/types/User.ts")}
|
|
126
|
+
*
|
|
127
|
+
* Include methods for:
|
|
128
|
+
* - login(email, password)
|
|
129
|
+
* - register(user)
|
|
130
|
+
* - updateProfile(userId, profileData)
|
|
131
|
+
* - deleteAccount(userId)
|
|
132
|
+
* `,
|
|
133
|
+
* temperature: 0.2,
|
|
134
|
+
* prettierConfig: {
|
|
135
|
+
* semi: false,
|
|
136
|
+
* singleQuote: true,
|
|
137
|
+
* printWidth: 120
|
|
138
|
+
* }
|
|
139
|
+
* });
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* // Generate a React hook with custom system prompt
|
|
143
|
+
* const useFormHook = await TypeScriptFile("use-form", {
|
|
144
|
+
* path: "./src/hooks/useForm.ts",
|
|
145
|
+
* prompt: await alchemy`
|
|
146
|
+
* Create a custom React hook called useForm that handles form state, validation, and submission.
|
|
147
|
+
* It should support:
|
|
148
|
+
* - Initial values
|
|
149
|
+
* - Validation rules
|
|
150
|
+
* - Field errors
|
|
151
|
+
* - Form submission with loading state
|
|
152
|
+
* - Reset functionality
|
|
153
|
+
* `,
|
|
154
|
+
* system: "You are an expert React developer specializing in TypeScript hooks. Create a single TypeScript file inside ```ts fences with no additional text. Follow React best practices and include proper typing.",
|
|
155
|
+
* model: {
|
|
156
|
+
* id: "claude-3-opus-20240229",
|
|
157
|
+
* provider: "anthropic"
|
|
158
|
+
* }
|
|
159
|
+
* });
|
|
160
|
+
*/
|
|
161
|
+
export const TypeScriptFile = Resource(
|
|
162
|
+
"ai::TypeScriptFile",
|
|
163
|
+
async function (
|
|
164
|
+
this: Context<TypeScriptFile>,
|
|
165
|
+
id: string,
|
|
166
|
+
props: TypeScriptFileProps
|
|
167
|
+
): Promise<TypeScriptFile> {
|
|
168
|
+
// Handle delete phase
|
|
169
|
+
if (this.phase === "delete") {
|
|
170
|
+
// StaticTypeScriptFile will handle the deletion
|
|
171
|
+
return this.destroy();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Use provided system prompt or default
|
|
175
|
+
const system = props.system || DEFAULT_TS_SYSTEM_PROMPT;
|
|
176
|
+
|
|
177
|
+
// Generate initial content
|
|
178
|
+
const { text } = await generateText({
|
|
179
|
+
model: createModel(props),
|
|
180
|
+
prompt: props.prompt,
|
|
181
|
+
system,
|
|
182
|
+
providerOptions: props.model?.options,
|
|
183
|
+
...(props.temperature === undefined
|
|
184
|
+
? {}
|
|
185
|
+
: { temperature: props.temperature }),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Extract and validate TypeScript code
|
|
189
|
+
let { code, error } = await extractTypeScriptCode(text);
|
|
190
|
+
|
|
191
|
+
// Re-prompt if there are validation errors
|
|
192
|
+
if (error) {
|
|
193
|
+
const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one TypeScript code block inside \`\`\`ts fences.`;
|
|
194
|
+
|
|
195
|
+
const { text: retryText } = await generateText({
|
|
196
|
+
model: createModel(props),
|
|
197
|
+
prompt: props.prompt,
|
|
198
|
+
system: errorSystem,
|
|
199
|
+
providerOptions: props.model?.options,
|
|
200
|
+
...(props.temperature === undefined
|
|
201
|
+
? {}
|
|
202
|
+
: { temperature: props.temperature }),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const retryResult = await extractTypeScriptCode(retryText);
|
|
206
|
+
|
|
207
|
+
if (retryResult.error) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Failed to generate valid TypeScript code: ${retryResult.error}`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
code = retryResult.code;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Use StaticTypeScriptFile to create/update the file
|
|
217
|
+
const file = await StaticTypeScriptFile("file", props.path, code);
|
|
218
|
+
|
|
219
|
+
// Return the resource
|
|
220
|
+
return this({
|
|
221
|
+
...props,
|
|
222
|
+
content: file.content,
|
|
223
|
+
createdAt: Date.now(),
|
|
224
|
+
updatedAt: Date.now(),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Extracts TypeScript code from between ```ts fences
|
|
231
|
+
* Validates that exactly one TypeScript code block exists
|
|
232
|
+
*
|
|
233
|
+
* @param text The text to extract TypeScript code from
|
|
234
|
+
* @returns The extracted TypeScript code or error message
|
|
235
|
+
*/
|
|
236
|
+
async function extractTypeScriptCode(
|
|
237
|
+
text: string
|
|
238
|
+
): Promise<{ code: string; error?: string }> {
|
|
239
|
+
const tsCodeRegex = /```ts\s*([\s\S]*?)```/g;
|
|
240
|
+
const matches = Array.from(text.matchAll(tsCodeRegex));
|
|
241
|
+
|
|
242
|
+
if (matches.length === 0) {
|
|
243
|
+
return {
|
|
244
|
+
code: "",
|
|
245
|
+
error:
|
|
246
|
+
"No TypeScript code block found in the response. Please include your code within ```ts fences.",
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (matches.length > 1) {
|
|
251
|
+
return {
|
|
252
|
+
code: "",
|
|
253
|
+
error:
|
|
254
|
+
"Multiple TypeScript code blocks found in the response. Please provide exactly one code block within ```ts fences.",
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { code: matches[0][1].trim() };
|
|
259
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { generateText } from "ai";
|
|
2
|
+
import type { Context } from "../context";
|
|
3
|
+
import { StaticVueFile } from "../fs/static-vue-file";
|
|
4
|
+
import { Resource } from "../resource";
|
|
5
|
+
import type { Secret } from "../secret";
|
|
6
|
+
import { type ModelConfig, createModel } from "./client";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Properties for creating or updating a VueFile
|
|
10
|
+
*/
|
|
11
|
+
export interface VueFileProps {
|
|
12
|
+
/**
|
|
13
|
+
* Path to the Vue file
|
|
14
|
+
*/
|
|
15
|
+
path: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base URL for the OpenAI API
|
|
19
|
+
* @default 'https://api.openai.com/v1'
|
|
20
|
+
*/
|
|
21
|
+
baseURL?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Prompt for generating content
|
|
25
|
+
* Use alchemy template literals to include file context:
|
|
26
|
+
* @example
|
|
27
|
+
* prompt: await alchemy`
|
|
28
|
+
* Generate a Vue component using:
|
|
29
|
+
* ${alchemy.file("src/api.ts")}
|
|
30
|
+
* `
|
|
31
|
+
*/
|
|
32
|
+
prompt: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* System prompt for the model
|
|
36
|
+
* This is used to provide instructions to the model about how to format the response
|
|
37
|
+
* The default system prompt instructs the model to return a single Vue component inside ```vue fences
|
|
38
|
+
* @default "You are a Vue component generator. Create a single Vue component based on the user's requirements. Your response MUST include only a single Vue component inside ```vue fences. Do not include any other text, explanations, or multiple code blocks."
|
|
39
|
+
*/
|
|
40
|
+
system?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* OpenAI API key to use for generating content
|
|
44
|
+
* If not provided, will use OPENAI_API_KEY environment variable
|
|
45
|
+
*/
|
|
46
|
+
apiKey?: Secret;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Model configuration
|
|
50
|
+
*/
|
|
51
|
+
model?: ModelConfig;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Temperature for controlling randomness in generation.
|
|
55
|
+
* Higher values (e.g., 0.8) make output more random,
|
|
56
|
+
* lower values (e.g., 0.2) make it more deterministic.
|
|
57
|
+
* @default 0.7
|
|
58
|
+
*/
|
|
59
|
+
temperature?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A Vue file that can be created, updated, and deleted
|
|
64
|
+
*/
|
|
65
|
+
export interface VueFile extends VueFileProps, Resource<"ai::VueFile"> {
|
|
66
|
+
/**
|
|
67
|
+
* Content of the Vue file
|
|
68
|
+
*/
|
|
69
|
+
content: string;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Time at which the file was created
|
|
73
|
+
*/
|
|
74
|
+
createdAt: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Time at which the file was last updated
|
|
78
|
+
*/
|
|
79
|
+
updatedAt: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Default system prompt for Vue file generation
|
|
84
|
+
*/
|
|
85
|
+
const DEFAULT_VUE_SYSTEM_PROMPT =
|
|
86
|
+
"You are a Vue component generator. Create a single Vue component based on the user's requirements. Your response MUST include only a single Vue component inside ```vue fences. Do not include any other text, explanations, or multiple code blocks.";
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resource for generating Vue files using AI models.
|
|
90
|
+
* Extracts Vue code from between ```vue fences and validates the response.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // Create a simple Vue component
|
|
94
|
+
* const button = await VueFile("button-component", {
|
|
95
|
+
* path: "./src/components/Button.vue",
|
|
96
|
+
* prompt: await alchemy`
|
|
97
|
+
* Generate a customizable button Vue component with:
|
|
98
|
+
* - Primary, secondary, and outline variants
|
|
99
|
+
* - Small, medium, and large sizes
|
|
100
|
+
* - Loading state with spinner
|
|
101
|
+
* - Disabled state
|
|
102
|
+
* `,
|
|
103
|
+
* model: {
|
|
104
|
+
* id: "gpt-4o",
|
|
105
|
+
* provider: "openai"
|
|
106
|
+
* }
|
|
107
|
+
* });
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* // Generate a Vue component using existing files as reference
|
|
111
|
+
* const userCard = await VueFile("user-card", {
|
|
112
|
+
* path: "./src/components/UserCard.vue",
|
|
113
|
+
* prompt: await alchemy`
|
|
114
|
+
* Create a UserCard Vue component that displays user information.
|
|
115
|
+
* Follow the styling patterns from:
|
|
116
|
+
* ${alchemy.file("src/components/Card.vue")}
|
|
117
|
+
*
|
|
118
|
+
* Use the user type from:
|
|
119
|
+
* ${alchemy.file("src/types/User.ts")}
|
|
120
|
+
* `,
|
|
121
|
+
* temperature: 0.2
|
|
122
|
+
* });
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* // Generate a complex form component with validation and custom system prompt
|
|
126
|
+
* const form = await VueFile("registration-form", {
|
|
127
|
+
* path: "./src/components/RegistrationForm.vue",
|
|
128
|
+
* prompt: await alchemy`
|
|
129
|
+
* Generate a registration form Vue component with:
|
|
130
|
+
* - Email, password, and confirm password fields
|
|
131
|
+
* - Form validation using Vuelidate or similar
|
|
132
|
+
* - Error messages for each field
|
|
133
|
+
* - Submit handler that emits form data
|
|
134
|
+
*
|
|
135
|
+
* Follow these style guidelines:
|
|
136
|
+
* ${alchemy.file("src/styles/guidelines.md")}
|
|
137
|
+
* `,
|
|
138
|
+
* system: "You are an expert Vue component creator specializing in form components with validation. Create a single Vue component inside ```vue fences with no additional text.",
|
|
139
|
+
* model: {
|
|
140
|
+
* id: "claude-3-opus-20240229",
|
|
141
|
+
* provider: "anthropic"
|
|
142
|
+
* }
|
|
143
|
+
* });
|
|
144
|
+
*/
|
|
145
|
+
export const VueFile = Resource(
|
|
146
|
+
"ai::VueFile",
|
|
147
|
+
async function (
|
|
148
|
+
this: Context<VueFile>,
|
|
149
|
+
id: string,
|
|
150
|
+
props: VueFileProps
|
|
151
|
+
): Promise<VueFile> {
|
|
152
|
+
// Handle deletion phase
|
|
153
|
+
if (this.phase === "delete") {
|
|
154
|
+
return this.destroy();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Use provided system prompt or default
|
|
158
|
+
const system = props.system || DEFAULT_VUE_SYSTEM_PROMPT;
|
|
159
|
+
|
|
160
|
+
// Generate initial content
|
|
161
|
+
const { text } = await generateText({
|
|
162
|
+
model: createModel(props),
|
|
163
|
+
prompt: props.prompt,
|
|
164
|
+
system,
|
|
165
|
+
providerOptions: props.model?.options,
|
|
166
|
+
...(props.temperature === undefined
|
|
167
|
+
? {}
|
|
168
|
+
: { temperature: props.temperature }),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Extract and validate Vue code
|
|
172
|
+
let { code, error } = await extractVueCode(text);
|
|
173
|
+
|
|
174
|
+
// Re-prompt if there are validation errors
|
|
175
|
+
if (error) {
|
|
176
|
+
const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one Vue component inside \`\`\`vue fences.`;
|
|
177
|
+
|
|
178
|
+
const { text: retryText } = await generateText({
|
|
179
|
+
model: createModel(props),
|
|
180
|
+
prompt: props.prompt,
|
|
181
|
+
system: errorSystem,
|
|
182
|
+
providerOptions: props.model?.options,
|
|
183
|
+
...(props.temperature === undefined
|
|
184
|
+
? {}
|
|
185
|
+
: { temperature: props.temperature }),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const retryResult = await extractVueCode(retryText);
|
|
189
|
+
|
|
190
|
+
if (retryResult.error) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Failed to generate valid Vue code: ${retryResult.error}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
code = retryResult.code;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Use StaticVueFile to create/update the file
|
|
200
|
+
const file = await StaticVueFile("file", props.path, code);
|
|
201
|
+
|
|
202
|
+
// Return the resource
|
|
203
|
+
return this({
|
|
204
|
+
...props,
|
|
205
|
+
content: file.content,
|
|
206
|
+
createdAt: Date.now(),
|
|
207
|
+
updatedAt: Date.now(),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Extracts Vue code from between ```vue fences
|
|
214
|
+
* Validates that exactly one Vue code block exists
|
|
215
|
+
*
|
|
216
|
+
* @param text The text to extract Vue code from
|
|
217
|
+
* @returns The extracted Vue code or error message
|
|
218
|
+
*/
|
|
219
|
+
async function extractVueCode(
|
|
220
|
+
text: string
|
|
221
|
+
): Promise<{ code: string; error?: string }> {
|
|
222
|
+
const vueCodeRegex = /```vue\s*([\s\S]*?)```/g;
|
|
223
|
+
const matches = Array.from(text.matchAll(vueCodeRegex));
|
|
224
|
+
|
|
225
|
+
if (matches.length === 0) {
|
|
226
|
+
return {
|
|
227
|
+
code: "",
|
|
228
|
+
error:
|
|
229
|
+
"No Vue code block found in the response. Please include your code within ```vue fences.",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (matches.length > 1) {
|
|
234
|
+
return {
|
|
235
|
+
code: "",
|
|
236
|
+
error:
|
|
237
|
+
"Multiple Vue code blocks found in the response. Please provide exactly one code block within ```vue fences.",
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { code: matches[0][1].trim() };
|
|
242
|
+
}
|