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.
Files changed (197) hide show
  1. package/README.md +3 -3
  2. package/lib/ai/approve.d.ts +99 -0
  3. package/lib/ai/approve.js +76 -0
  4. package/lib/ai/astro-file.d.ts +137 -0
  5. package/lib/ai/astro-file.js +156 -0
  6. package/lib/ai/client.d.ts +7 -8
  7. package/lib/ai/client.js +48 -19
  8. package/lib/ai/css-file.d.ts +131 -0
  9. package/lib/ai/css-file.js +141 -0
  10. package/lib/ai/data.d.ts +51 -6
  11. package/lib/ai/data.js +58 -12
  12. package/lib/ai/document.d.ts +108 -24
  13. package/lib/ai/document.js +166 -52
  14. package/lib/ai/html-file.d.ts +128 -0
  15. package/lib/ai/html-file.js +138 -0
  16. package/lib/ai/index.d.ts +9 -0
  17. package/lib/ai/index.js +9 -0
  18. package/lib/ai/json-file.d.ts +160 -0
  19. package/lib/ai/json-file.js +187 -0
  20. package/lib/ai/review.d.ts +122 -0
  21. package/lib/ai/review.js +101 -0
  22. package/lib/ai/typescript-file.d.ts +139 -0
  23. package/lib/ai/typescript-file.js +145 -0
  24. package/lib/ai/vue-file.d.ts +126 -0
  25. package/lib/ai/vue-file.js +136 -0
  26. package/lib/ai/yaml-file.d.ts +169 -0
  27. package/lib/ai/yaml-file.js +200 -0
  28. package/lib/alchemy.js +17 -2
  29. package/lib/apply.js +1 -1
  30. package/lib/cloudflare/dns.d.ts +88 -0
  31. package/lib/cloudflare/dns.js +192 -0
  32. package/lib/cloudflare/generate-asset-manifest.js +1 -1
  33. package/lib/cloudflare/index.d.ts +2 -1
  34. package/lib/cloudflare/index.js +2 -1
  35. package/lib/cloudflare/r2-rest-state-store.d.ts +125 -0
  36. package/lib/cloudflare/r2-rest-state-store.js +254 -0
  37. package/lib/cloudflare/response.d.ts +12 -0
  38. package/lib/cloudflare/response.js +0 -0
  39. package/lib/cloudflare/static-site-router.js +16 -7
  40. package/lib/cloudflare/static-site.js +3 -0
  41. package/lib/cloudflare/upload-asset-manifest.js +4 -2
  42. package/lib/cloudflare/zone.d.ts +18 -0
  43. package/lib/cloudflare/zone.js +17 -4
  44. package/lib/dns/godaddy.d.ts +7 -0
  45. package/lib/dns/godaddy.js +21 -0
  46. package/lib/dns/import-dns.d.ts +82 -0
  47. package/lib/dns/import-dns.js +114 -0
  48. package/lib/dns/index.d.ts +2 -0
  49. package/lib/dns/index.js +2 -0
  50. package/lib/dns/record.d.ts +88 -0
  51. package/lib/dns/record.js +13 -0
  52. package/lib/fs/copy-file.d.ts +54 -0
  53. package/lib/fs/copy-file.js +63 -0
  54. package/lib/fs/file-system-state-store.d.ts +17 -0
  55. package/lib/fs/file-system-state-store.js +87 -0
  56. package/lib/fs/file.d.ts +13 -0
  57. package/lib/fs/file.js +24 -5
  58. package/lib/fs/folder.d.ts +10 -0
  59. package/lib/fs/folder.js +2 -2
  60. package/lib/fs/index.d.ts +10 -4
  61. package/lib/fs/index.js +10 -4
  62. package/lib/fs/static-astro-file.d.ts +34 -0
  63. package/lib/fs/static-astro-file.js +39 -0
  64. package/lib/fs/static-css-file.d.ts +24 -0
  65. package/lib/fs/static-css-file.js +29 -0
  66. package/lib/fs/static-html-file.d.ts +31 -0
  67. package/lib/fs/static-html-file.js +36 -0
  68. package/lib/fs/{json-file.d.ts → static-json-file.d.ts} +3 -3
  69. package/lib/fs/static-json-file.js +15 -0
  70. package/lib/fs/{text-file.d.ts → static-text-file.d.ts} +2 -2
  71. package/lib/fs/static-text-file.js +8 -0
  72. package/lib/fs/{typescript-file.d.ts → static-typescript-file.d.ts} +3 -3
  73. package/lib/fs/{typescript-file.js → static-typescript-file.js} +3 -2
  74. package/lib/fs/static-vue-file.d.ts +28 -0
  75. package/lib/fs/static-vue-file.js +33 -0
  76. package/lib/fs/static-yaml-file.d.ts +22 -0
  77. package/lib/fs/{yaml-file.js → static-yaml-file.js} +3 -2
  78. package/lib/internal/getting-started.d.ts +21 -0
  79. package/lib/internal/getting-started.js +87 -0
  80. package/lib/internal/index.d.ts +3 -0
  81. package/lib/internal/index.js +3 -0
  82. package/lib/internal/providers.d.ts +27 -0
  83. package/lib/internal/providers.js +172 -0
  84. package/lib/internal/tutorial.d.ts +104 -0
  85. package/lib/internal/tutorial.js +251 -0
  86. package/lib/resource.js +8 -1
  87. package/lib/scope.d.ts +1 -1
  88. package/lib/scope.js +4 -2
  89. package/lib/state.d.ts +1 -16
  90. package/lib/state.js +0 -87
  91. package/lib/test/bun.d.ts +11 -0
  92. package/lib/test/bun.js +36 -8
  93. package/lib/web/astro.d.ts +147 -0
  94. package/lib/web/astro.js +414 -0
  95. package/lib/{shadcn/component.js → web/shadcn-component.js} +2 -9
  96. package/lib/web/shadcn.d.ts +97 -0
  97. package/lib/web/shadcn.js +99 -0
  98. package/lib/web/tailwind.d.ts +64 -0
  99. package/lib/web/tailwind.js +97 -0
  100. package/lib/{vite → web}/vite.d.ts +5 -0
  101. package/lib/{vite → web}/vite.js +44 -44
  102. package/lib/web/vitepress/config.d.ts +10 -0
  103. package/lib/web/vitepress/config.js +31 -0
  104. package/lib/web/vitepress/custom-theme.d.ts +173 -0
  105. package/lib/web/vitepress/custom-theme.js +336 -0
  106. package/lib/{vitepress → web/vitepress}/dependencies.d.ts +2 -2
  107. package/lib/{vitepress → web/vitepress}/dependencies.js +1 -1
  108. package/lib/web/vitepress/home-page.d.ts +246 -0
  109. package/lib/web/vitepress/home-page.js +147 -0
  110. package/lib/{vitepress → web/vitepress}/index.d.ts +2 -0
  111. package/lib/{vitepress → web/vitepress}/index.js +2 -0
  112. package/lib/{vitepress → web/vitepress}/vitepress.d.ts +15 -12
  113. package/lib/{vitepress → web/vitepress}/vitepress.js +26 -44
  114. package/package.json +6 -2
  115. package/src/ai/approve.ts +163 -0
  116. package/src/ai/astro-file.ts +269 -0
  117. package/src/ai/client.ts +60 -21
  118. package/src/ai/css-file.ts +247 -0
  119. package/src/ai/data.ts +81 -19
  120. package/src/ai/document.ts +240 -62
  121. package/src/ai/html-file.ts +244 -0
  122. package/src/ai/index.ts +9 -0
  123. package/src/ai/json-file.ts +325 -0
  124. package/src/ai/review.ts +213 -0
  125. package/src/ai/typescript-file.ts +259 -0
  126. package/src/ai/vue-file.ts +242 -0
  127. package/src/ai/yaml-file.ts +328 -0
  128. package/src/alchemy.ts +18 -7
  129. package/src/apply.ts +8 -9
  130. package/src/cloudflare/dns.ts +344 -0
  131. package/src/cloudflare/generate-asset-manifest.ts +4 -4
  132. package/src/cloudflare/index.ts +2 -1
  133. package/src/cloudflare/r2-rest-state-store.ts +353 -0
  134. package/src/cloudflare/response.ts +9 -0
  135. package/src/cloudflare/static-site-router.ts +18 -8
  136. package/src/cloudflare/static-site.ts +11 -7
  137. package/src/cloudflare/upload-asset-manifest.ts +6 -4
  138. package/src/cloudflare/worker.ts +44 -43
  139. package/src/cloudflare/zone.ts +39 -23
  140. package/src/dns/godaddy.ts +29 -0
  141. package/src/dns/import-dns.ts +213 -0
  142. package/src/dns/index.ts +2 -0
  143. package/src/dns/record.ts +121 -0
  144. package/src/fs/copy-file.ts +115 -0
  145. package/src/fs/file-system-state-store.ts +109 -0
  146. package/src/fs/file.ts +35 -9
  147. package/src/fs/folder.ts +14 -2
  148. package/src/fs/index.ts +10 -4
  149. package/src/fs/static-astro-file.ts +45 -0
  150. package/src/fs/static-css-file.ts +35 -0
  151. package/src/fs/static-html-file.ts +42 -0
  152. package/src/fs/static-json-file.ts +34 -0
  153. package/src/fs/{text-file.ts → static-text-file.ts} +9 -3
  154. package/src/fs/{typescript-file.ts → static-typescript-file.ts} +7 -6
  155. package/src/fs/static-vue-file.ts +39 -0
  156. package/src/fs/static-yaml-file.ts +33 -0
  157. package/src/internal/getting-started.ts +115 -0
  158. package/src/internal/index.ts +3 -0
  159. package/src/internal/providers.ts +241 -0
  160. package/src/internal/tutorial.ts +392 -0
  161. package/src/resource.ts +10 -1
  162. package/src/scope.ts +5 -6
  163. package/src/state.ts +1 -111
  164. package/src/test/bun.ts +60 -10
  165. package/src/web/astro.ts +644 -0
  166. package/src/{shadcn/component.ts → web/shadcn-component.ts} +5 -13
  167. package/src/web/shadcn.ts +219 -0
  168. package/src/web/tailwind.ts +167 -0
  169. package/src/{vite → web}/vite.ts +54 -56
  170. package/src/web/vitepress/config.ts +48 -0
  171. package/src/web/vitepress/custom-theme.ts +514 -0
  172. package/src/{vitepress → web/vitepress}/dependencies.ts +2 -2
  173. package/src/web/vitepress/home-page.ts +375 -0
  174. package/src/{vitepress → web/vitepress}/index.ts +2 -0
  175. package/src/{vitepress → web/vitepress}/vitepress.ts +62 -72
  176. package/lib/cloudflare/state.d.ts +0 -82
  177. package/lib/cloudflare/state.js +0 -108
  178. package/lib/fs/json-file.js +0 -7
  179. package/lib/fs/text-file.js +0 -7
  180. package/lib/fs/yaml-file.d.ts +0 -19
  181. package/lib/internal/docs.d.ts +0 -5
  182. package/lib/internal/docs.js +0 -287
  183. package/lib/shadcn/index.d.ts +0 -1
  184. package/lib/shadcn/index.js +0 -1
  185. package/lib/vite/index.d.ts +0 -1
  186. package/lib/vite/index.js +0 -1
  187. package/lib/vitepress/home-page.d.ts +0 -133
  188. package/lib/vitepress/home-page.js +0 -11
  189. package/src/cloudflare/state.ts +0 -138
  190. package/src/fs/json-file.ts +0 -23
  191. package/src/fs/yaml-file.ts +0 -26
  192. package/src/internal/docs.ts +0 -338
  193. package/src/shadcn/index.ts +0 -1
  194. package/src/vite/index.ts +0 -1
  195. package/src/vitepress/home-page.ts +0 -166
  196. /package/lib/{shadcn/component.d.ts → web/shadcn-component.d.ts} +0 -0
  197. /package/src/{vitepress → web/vitepress}/index.md +0 -0
@@ -1,82 +1,196 @@
1
1
  import { generateText } from "ai";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
2
+ import { StaticTextFile } from "../fs/static-text-file";
4
3
  import { Resource } from "../resource";
5
- import { createClient, getModelId, getModelOptions, } from "./client";
4
+ import { createModel, withRateLimitRetry } from "./client";
5
+ /**
6
+ * Default system prompt for markdown document generation
7
+ */
8
+ const DEFAULT_MD_SYSTEM_PROMPT = "You are a technical documentation writer. Create a single markdown document based on the user's requirements. Your response MUST include only a single markdown document inside ```md fences. Do not include any other text, explanations, or multiple code blocks.";
6
9
  /**
7
10
  * Resource for managing AI-generated markdown documents using the Vercel AI SDK.
8
11
  * Supports powerful context handling through the alchemy template literal tag.
9
12
  *
10
13
  * @example
11
- * // Create a document using alchemy template literals for context
14
+ * // Create an in-memory document (no file created)
12
15
  * const apiDocs = await Document("api-docs", {
16
+ * title: "API Documentation",
17
+ * prompt: await alchemy`
18
+ * Generate API documentation based on these source files:
19
+ * ${alchemy.file("src/api.ts")}
20
+ * ${alchemy.file("src/types.ts")}
21
+ * `,
22
+ * model: {
23
+ * id: "gpt-4o",
24
+ * provider: "openai"
25
+ * }
26
+ * });
27
+ *
28
+ * @example
29
+ * // Create a document and write it to disk
30
+ * const apiDocs = await Document("api-docs", {
31
+ * title: "API Documentation",
13
32
  * path: "./docs/api.md",
14
33
  * prompt: await alchemy`
15
34
  * Generate API documentation based on these source files:
16
35
  * ${alchemy.file("src/api.ts")}
17
36
  * ${alchemy.file("src/types.ts")}
18
- * `
19
- * // The above will automatically append the file contents as code blocks:
20
- * //
21
- * // Generate API documentation based on these source files:
22
- * // [api.ts](src/api.ts)
23
- * // [types.ts](src/types.ts)
24
- * //
25
- * // // src/api.ts
26
- * // ```ts
27
- * // ... contents of api.ts ...
28
- * // ```
29
- * //
30
- * // // src/types.ts
31
- * // ```ts
32
- * // ... contents of types.ts ...
33
- * // ```
37
+ * `,
38
+ * model: {
39
+ * id: "gpt-4o",
40
+ * provider: "openai"
41
+ * }
42
+ * });
43
+ *
44
+ * @example
45
+ * // Use message history for iterative document generation
46
+ * const apiDocs = await Document("api-docs", {
47
+ * title: "API Documentation",
48
+ * path: "./docs/api.md",
49
+ * messages: [
50
+ * { role: "user", content: "Create API documentation for these files" },
51
+ * { role: "assistant", content: "I'll help you create API documentation. Please provide the files." },
52
+ * { role: "user", content: "Here are the files: [file contents]" }
53
+ * ],
54
+ * system: "You are a technical documentation writer. Generate clear and concise API documentation.",
55
+ * model: {
56
+ * id: "gpt-4o",
57
+ * provider: "openai"
58
+ * }
34
59
  * });
35
60
  *
36
61
  * @example
37
- * // Use alchemy template literals with file collections
62
+ * // Use alchemy template literals with file collections and temperature control
38
63
  * const modelDocs = await Document("models", {
64
+ * title: "Data Models",
39
65
  * path: "./docs/models.md",
40
66
  * prompt: await alchemy`
41
67
  * Write documentation for these data models:
42
68
  * ${alchemy.files("src/models/user.ts", "src/models/post.ts")}
43
- * `
44
- * // This creates a prompt with all files appended as code blocks,
45
- * // automatically handling syntax highlighting based on file extensions
69
+ * `,
70
+ * temperature: 0.2 // Lower temperature for more deterministic output
71
+ * });
72
+ *
73
+ * @example
74
+ * // Advanced model configuration with custom provider options and custom system prompt
75
+ * const techDocs = await Document("tech-specs", {
76
+ * title: "Technical Specifications",
77
+ * path: "./docs/tech-specs.md",
78
+ * prompt: await alchemy`
79
+ * Create detailed technical specifications based on these requirements:
80
+ * ${alchemy.file("requirements/system.md")}
81
+ * `,
82
+ * system: "You are an expert technical writer specializing in system specifications. Create a single markdown document inside ```md fences with no additional text.",
83
+ * model: {
84
+ * id: "o3-mini",
85
+ * provider: "openai",
86
+ * options: {
87
+ * reasoningEffort: "high"
88
+ * }
89
+ * },
90
+ * temperature: 0.1
46
91
  * });
47
92
  */
48
93
  export const Document = Resource("docs::Document", async function (id, props) {
49
- // Ensure directory exists
50
- await fs.mkdir(path.dirname(props.path), { recursive: true });
94
+ // Validate that either prompt or messages are provided
95
+ if (!props.prompt && !props.messages) {
96
+ throw new Error("Either prompt or messages must be provided");
97
+ }
98
+ // Handle deletion phase
51
99
  if (this.phase === "delete") {
52
- try {
53
- await fs.unlink(props.path);
54
- }
55
- catch (error) {
56
- // Ignore if file doesn't exist
57
- if (error.code !== "ENOENT") {
58
- throw error;
59
- }
60
- }
61
100
  return this.destroy();
62
101
  }
63
- // Initialize OpenAI compatible provider using shared client
64
- const provider = createClient(props);
65
- // Generate content
66
- const { text } = await generateText({
67
- model: provider(getModelId(props)),
68
- prompt: props.prompt,
69
- ...getModelOptions(props),
102
+ // Use provided system prompt or default
103
+ const system = props.system || DEFAULT_MD_SYSTEM_PROMPT;
104
+ // Generate initial content with rate limit retry
105
+ const { text } = await withRateLimitRetry(async () => {
106
+ return generateText({
107
+ model: createModel(props),
108
+ ...(props.messages
109
+ ? { messages: props.messages }
110
+ : { prompt: props.prompt }),
111
+ system,
112
+ maxTokens: props.maxTokens || 8192,
113
+ providerOptions: props.model?.options,
114
+ ...(props.temperature === undefined
115
+ ? {}
116
+ : // some models error if you provide it (rather than ignoring it)
117
+ { temperature: props.temperature }),
118
+ });
70
119
  });
71
- // Write content to file
72
- await fs.writeFile(props.path, text);
73
- // Get file stats for timestamps
74
- const stats = await fs.stat(props.path);
75
- // Return the resource
76
- return this({
120
+ // Extract and validate markdown content
121
+ let { content, error } = extractMarkdownContent(text);
122
+ // Re-prompt if there are validation errors
123
+ if (error) {
124
+ const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one markdown document inside \`\`\`md fences.`;
125
+ const { text: retryText } = await withRateLimitRetry(async () => {
126
+ return generateText({
127
+ model: createModel(props),
128
+ ...(props.messages
129
+ ? { messages: props.messages }
130
+ : { prompt: props.prompt }),
131
+ system: errorSystem,
132
+ providerOptions: props.model?.options,
133
+ ...(props.temperature === undefined
134
+ ? {}
135
+ : { temperature: props.temperature }),
136
+ });
137
+ });
138
+ const retryResult = extractMarkdownContent(retryText);
139
+ if (retryResult.error) {
140
+ throw new Error(`Failed to generate valid markdown content: ${retryResult.error}`);
141
+ }
142
+ content = retryResult.content;
143
+ }
144
+ // Create result object
145
+ const result = {
77
146
  ...props,
78
- content: text,
79
- createdAt: stats.birthtimeMs,
80
- updatedAt: stats.mtimeMs,
81
- });
147
+ content,
148
+ messages: [
149
+ ...(props.messages || [{ role: "user", content: props.prompt }]),
150
+ { role: "assistant", content },
151
+ ],
152
+ createdAt: this.output?.createdAt || Date.now(),
153
+ updatedAt: Date.now(),
154
+ };
155
+ // Write file if path is provided
156
+ if (props.path) {
157
+ const filePath = props.path;
158
+ const fileId = `${id}-file`;
159
+ result.file = await StaticTextFile(fileId, filePath, content);
160
+ }
161
+ // Return the resource
162
+ return this(result);
82
163
  });
164
+ /**
165
+ * Extracts markdown content from between ```md fences
166
+ * Validates that exactly one markdown code block exists
167
+ *
168
+ * @param text The text to extract markdown content from
169
+ * @returns The extracted markdown content or error message
170
+ */
171
+ function extractMarkdownContent(text) {
172
+ const lines = text.split("\n");
173
+ const startIdx = lines.findIndex((line) => line.trim() === "```md");
174
+ if (startIdx === -1) {
175
+ return {
176
+ content: "",
177
+ error: "No markdown code block found in the response. Please include your markdown content within ```md fences.",
178
+ };
179
+ }
180
+ const rest = lines.slice(startIdx + 1);
181
+ const endRelativeIdx = rest
182
+ .map((line) => line.trim() === "```")
183
+ .lastIndexOf(true);
184
+ if (endRelativeIdx === -1) {
185
+ return {
186
+ content: "",
187
+ error: "Markdown block was not closed properly.",
188
+ };
189
+ }
190
+ const endIdx = startIdx + 1 + endRelativeIdx;
191
+ const content = lines
192
+ .slice(startIdx + 1, endIdx)
193
+ .join("\n")
194
+ .trim();
195
+ return { content };
196
+ }
@@ -0,0 +1,128 @@
1
+ import type { Context } from "../context";
2
+ import { Resource } from "../resource";
3
+ import type { Secret } from "../secret";
4
+ import { type ModelConfig } from "./client";
5
+ /**
6
+ * Properties for creating or updating an HTMLFile
7
+ */
8
+ export interface HTMLFileProps {
9
+ /**
10
+ * Path to the HTML file
11
+ */
12
+ path: string;
13
+ /**
14
+ * Base URL for the OpenAI API
15
+ * @default 'https://api.openai.com/v1'
16
+ */
17
+ baseURL?: string;
18
+ /**
19
+ * Prompt for generating content
20
+ * Use alchemy template literals to include file context:
21
+ * @example
22
+ * prompt: await alchemy`
23
+ * Generate an HTML page using:
24
+ * ${alchemy.file("src/templates/base.html")}
25
+ * `
26
+ */
27
+ prompt: string;
28
+ /**
29
+ * System prompt for the model
30
+ * This is used to provide instructions to the model about how to format the response
31
+ * The default system prompt instructs the model to return HTML code inside ```html fences
32
+ * @default "You are an HTML code generator. Create HTML code based on the user's requirements. Your response MUST include only HTML code inside ```html fences. Do not include any other text, explanations, or multiple code blocks."
33
+ */
34
+ system?: string;
35
+ /**
36
+ * OpenAI API key to use for generating content
37
+ * If not provided, will use OPENAI_API_KEY environment variable
38
+ */
39
+ apiKey?: Secret;
40
+ /**
41
+ * Model configuration
42
+ */
43
+ model?: ModelConfig;
44
+ /**
45
+ * Temperature for controlling randomness in generation.
46
+ * Higher values (e.g., 0.8) make output more random,
47
+ * lower values (e.g., 0.2) make it more deterministic.
48
+ * @default 0.7
49
+ */
50
+ temperature?: number;
51
+ }
52
+ /**
53
+ * An HTML file that can be created, updated, and deleted
54
+ */
55
+ export interface HTMLFile extends HTMLFileProps, Resource<"ai::HTMLFile"> {
56
+ /**
57
+ * Content of the HTML file
58
+ */
59
+ content: string;
60
+ /**
61
+ * Time at which the file was created
62
+ */
63
+ createdAt: number;
64
+ /**
65
+ * Time at which the file was last updated
66
+ */
67
+ updatedAt: number;
68
+ }
69
+ /**
70
+ * Resource for generating HTML files using AI models.
71
+ * Extracts HTML code from between ```html fences and validates the response.
72
+ *
73
+ * @example
74
+ * // Create a simple landing page
75
+ * const landingPage = await HTMLFile("landing-page", {
76
+ * path: "./public/index.html",
77
+ * prompt: await alchemy`
78
+ * Generate a modern landing page for a SaaS product with:
79
+ * - Hero section with headline and call-to-action
80
+ * - Features section with 3 key features
81
+ * - Pricing section with 3 tiers
82
+ * - Testimonials section with 2 customer quotes
83
+ * - Contact form and footer
84
+ * `,
85
+ * model: {
86
+ * id: "gpt-4o",
87
+ * provider: "openai"
88
+ * }
89
+ * });
90
+ *
91
+ * @example
92
+ * // Generate an HTML email template
93
+ * const emailTemplate = await HTMLFile("welcome-email", {
94
+ * path: "./emails/welcome.html",
95
+ * prompt: await alchemy`
96
+ * Create an HTML email template for welcoming new users to our platform.
97
+ * The email should include:
98
+ * - Company logo and branding
99
+ * - Personalized welcome message (use {{name}} placeholder)
100
+ * - Three steps to get started
101
+ * - Support contact information
102
+ * - Unsubscribe footer
103
+ *
104
+ * Make sure it's responsive and works in all major email clients.
105
+ * `,
106
+ * temperature: 0.2
107
+ * });
108
+ *
109
+ * @example
110
+ * // Generate an HTML component with custom system prompt
111
+ * const navComponent = await HTMLFile("navigation", {
112
+ * path: "./components/nav.html",
113
+ * prompt: await alchemy`
114
+ * Create a responsive navigation component with:
115
+ * - Logo in the left corner
116
+ * - Navigation links: Home, Products, Services, About, Contact
117
+ * - Mobile hamburger menu that expands/collapses
118
+ * - Login/signup buttons on the right side
119
+ * - Dark/light mode toggle
120
+ * `,
121
+ * system: "You are an expert HTML/CSS developer specializing in responsive components. Create a single HTML file inside ```html fences with no additional text. Use modern HTML5 semantic elements and inline CSS if needed.",
122
+ * model: {
123
+ * id: "claude-3-opus-20240229",
124
+ * provider: "anthropic"
125
+ * }
126
+ * });
127
+ */
128
+ export declare const HTMLFile: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<HTMLFile>, id: string, props: HTMLFileProps) => Promise<HTMLFile>);
@@ -0,0 +1,138 @@
1
+ import { generateText } from "ai";
2
+ import { StaticHTMLFile } from "../fs/static-html-file";
3
+ import { Resource } from "../resource";
4
+ import { createModel } from "./client";
5
+ /**
6
+ * Default system prompt for HTML file generation
7
+ */
8
+ const DEFAULT_HTML_SYSTEM_PROMPT = "You are an HTML code generator. Create HTML code based on the user's requirements. Your response MUST include only HTML code inside ```html fences. Do not include any other text, explanations, or multiple code blocks.";
9
+ /**
10
+ * Resource for generating HTML files using AI models.
11
+ * Extracts HTML code from between ```html fences and validates the response.
12
+ *
13
+ * @example
14
+ * // Create a simple landing page
15
+ * const landingPage = await HTMLFile("landing-page", {
16
+ * path: "./public/index.html",
17
+ * prompt: await alchemy`
18
+ * Generate a modern landing page for a SaaS product with:
19
+ * - Hero section with headline and call-to-action
20
+ * - Features section with 3 key features
21
+ * - Pricing section with 3 tiers
22
+ * - Testimonials section with 2 customer quotes
23
+ * - Contact form and footer
24
+ * `,
25
+ * model: {
26
+ * id: "gpt-4o",
27
+ * provider: "openai"
28
+ * }
29
+ * });
30
+ *
31
+ * @example
32
+ * // Generate an HTML email template
33
+ * const emailTemplate = await HTMLFile("welcome-email", {
34
+ * path: "./emails/welcome.html",
35
+ * prompt: await alchemy`
36
+ * Create an HTML email template for welcoming new users to our platform.
37
+ * The email should include:
38
+ * - Company logo and branding
39
+ * - Personalized welcome message (use {{name}} placeholder)
40
+ * - Three steps to get started
41
+ * - Support contact information
42
+ * - Unsubscribe footer
43
+ *
44
+ * Make sure it's responsive and works in all major email clients.
45
+ * `,
46
+ * temperature: 0.2
47
+ * });
48
+ *
49
+ * @example
50
+ * // Generate an HTML component with custom system prompt
51
+ * const navComponent = await HTMLFile("navigation", {
52
+ * path: "./components/nav.html",
53
+ * prompt: await alchemy`
54
+ * Create a responsive navigation component with:
55
+ * - Logo in the left corner
56
+ * - Navigation links: Home, Products, Services, About, Contact
57
+ * - Mobile hamburger menu that expands/collapses
58
+ * - Login/signup buttons on the right side
59
+ * - Dark/light mode toggle
60
+ * `,
61
+ * system: "You are an expert HTML/CSS developer specializing in responsive components. Create a single HTML file inside ```html fences with no additional text. Use modern HTML5 semantic elements and inline CSS if needed.",
62
+ * model: {
63
+ * id: "claude-3-opus-20240229",
64
+ * provider: "anthropic"
65
+ * }
66
+ * });
67
+ */
68
+ export const HTMLFile = Resource("ai::HTMLFile", async function (id, props) {
69
+ // Handle deletion phase
70
+ if (this.phase === "delete") {
71
+ return this.destroy();
72
+ }
73
+ // Use provided system prompt or default
74
+ const system = props.system || DEFAULT_HTML_SYSTEM_PROMPT;
75
+ // Generate initial content
76
+ const { text } = await generateText({
77
+ model: createModel(props),
78
+ prompt: props.prompt,
79
+ system,
80
+ providerOptions: props.model?.options,
81
+ ...(props.temperature === undefined
82
+ ? {}
83
+ : { temperature: props.temperature }),
84
+ });
85
+ // Extract and validate HTML code
86
+ let { code, error } = await extractHTMLCode(text);
87
+ // Re-prompt if there are validation errors
88
+ if (error) {
89
+ const errorSystem = `${system}\n\nERROR: ${error}\n\nPlease try again and ensure your response contains exactly one HTML code block inside \`\`\`html fences.`;
90
+ const { text: retryText } = await generateText({
91
+ model: createModel(props),
92
+ prompt: props.prompt,
93
+ system: errorSystem,
94
+ providerOptions: props.model?.options,
95
+ ...(props.temperature === undefined
96
+ ? {}
97
+ : { temperature: props.temperature }),
98
+ });
99
+ const retryResult = await extractHTMLCode(retryText);
100
+ if (retryResult.error) {
101
+ throw new Error(`Failed to generate valid HTML code: ${retryResult.error}`);
102
+ }
103
+ code = retryResult.code;
104
+ }
105
+ // Use StaticHTMLFile to create/update the file
106
+ const file = await StaticHTMLFile("file", props.path, code);
107
+ // Return the resource
108
+ return this({
109
+ ...props,
110
+ content: file.content,
111
+ createdAt: Date.now(),
112
+ updatedAt: Date.now(),
113
+ });
114
+ });
115
+ /**
116
+ * Extracts HTML code from between ```html fences
117
+ * Validates that exactly one HTML code block exists
118
+ *
119
+ * @param text The text to extract HTML code from
120
+ * @returns The extracted HTML code or error message
121
+ */
122
+ async function extractHTMLCode(text) {
123
+ const htmlCodeRegex = /```html\s*([\s\S]*?)```/g;
124
+ const matches = Array.from(text.matchAll(htmlCodeRegex));
125
+ if (matches.length === 0) {
126
+ return {
127
+ code: "",
128
+ error: "No HTML code block found in the response. Please include your code within ```html fences.",
129
+ };
130
+ }
131
+ if (matches.length > 1) {
132
+ return {
133
+ code: "",
134
+ error: "Multiple HTML code blocks found in the response. Please provide exactly one code block within ```html fences.",
135
+ };
136
+ }
137
+ return { code: matches[0][1].trim() };
138
+ }
package/lib/ai/index.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ export * from "./approve";
1
2
  export * from "./ark";
3
+ export * from "./astro-file";
4
+ export * from "./css-file";
2
5
  export * from "./data";
3
6
  export * from "./document";
7
+ export * from "./html-file";
8
+ export * from "./json-file";
9
+ export * from "./review";
10
+ export * from "./typescript-file";
11
+ export * from "./vue-file";
12
+ export * from "./yaml-file";
package/lib/ai/index.js CHANGED
@@ -1,3 +1,12 @@
1
+ export * from "./approve";
1
2
  export * from "./ark";
3
+ export * from "./astro-file";
4
+ export * from "./css-file";
2
5
  export * from "./data";
3
6
  export * from "./document";
7
+ export * from "./html-file";
8
+ export * from "./json-file";
9
+ export * from "./review";
10
+ export * from "./typescript-file";
11
+ export * from "./vue-file";
12
+ export * from "./yaml-file";