2 * This is a Townie clone for Val Town.
3 * It creates a chat interface that helps users generate Val Town applications.
4 * Uses OpenAI's API to generate code responses in the proper format.
5 */
6/** @jsxImportSource https://esm.sh/react@18.2.0 */
91If you are changing a SQLite table's schema, you should also change the table's name so it creates a fresh table, ie by adding _2 or _3 after it everywhere. Ensure that tables are created before they are used.
92
93OPENAI
94
95Val Town includes a free, proxied OpenAI:
96
97import { OpenAI } from "https://esm.town/v/std/openai";
98const openai = new OpenAI();
99const completion = await openai.chat.completions.create({
100 messages: [
101 { role: "user", content: "Say hello in a creative way" },
176): Promise<string> {
177 try {
178 // Try using the proxied OpenAI first, if that fails, fall back to direct API call
179 try {
180 return await processWithValTownOpenAI(messages, systemPrompt, stream);
181 } catch (error) {
182 console.error("Error with Val Town OpenAI:", error);
183 console.log("Falling back to direct OpenAI API call");
184 return await processWithDirectOpenAI(messages, systemPrompt, stream);
185 }
186 } catch (error) {
191
192/**
193 * Process chat using Val Town's proxied OpenAI
194 */
195async function processWithValTownOpenAI(
196 messages: Message[],
197 systemPrompt: string,
199): Promise<string> {
200 try {
201 const { OpenAI } = await import("https://esm.town/v/std/openai");
202 const openai = new OpenAI();
203
204 // Prepare the messages array with system prompt
205 const openaiMessages = [
206 { role: "system", content: systemPrompt },
207 ...messages.map(m => ({ role: m.role, content: m.content }))
208 ];
209
210 console.log("Sending to Val Town OpenAI:", {
211 messageCount: openaiMessages.length,
212 firstUserMessage: messages[0]?.content?.substring(0, 20) + "..."
213 });
215 // If streaming is not required, get a complete response
216 if (!stream) {
217 const completion = await openai.chat.completions.create({
218 messages: openaiMessages,
219 model: "gpt-4o-mini", // Using Val Town's available model
220 temperature: 0.7,
223 return completion.choices[0]?.message?.content || "Sorry, I couldn't generate a response.";
224 } else {
225 // Streaming is not directly supported by Val Town OpenAI wrapper
226 // Falling back to direct API
227 throw new Error("Streaming not supported by Val Town OpenAI wrapper");
228 }
229 } catch (error) {
230 console.error("Error in processWithValTownOpenAI:", error);
231 throw error;
232 }
234
235/**
236 * Process chat using direct OpenAI API
237 */
238async function processWithDirectOpenAI(
239 messages: Message[],
240 systemPrompt: string,
242): Promise<string> {
243 // Get API key from environment
244 const apiKey = Deno.env.get("OPENAI_API_KEY");
245
246 if (!apiKey) {
247 throw new Error("OpenAI API Key not found. Please set the OPENAI_API_KEY environment variable.");
248 }
249
250 // Format messages for OpenAI API
251 const openaiMessages = [
252 { role: "system", content: systemPrompt },
253 ...messages.map(m => ({ role: m.role, content: m.content }))
254 ];
255
256 console.log("Sending to Direct OpenAI:", {
257 messageCount: openaiMessages.length,
258 firstUserMessage: messages[0]?.content?.substring(0, 20) + "..."
259 });
261 if (stream) {
262 // Stream the response if a stream is provided
263 return await streamChatResponse(openaiMessages, apiKey, stream);
264 } else {
265 // Otherwise, return the complete response
266 return await fetchChatResponse(openaiMessages, apiKey);
267 }
268}
269
270/**
271 * Fetch a complete chat response from OpenAI
272 */
273async function fetchChatResponse(messages: any[], apiKey: string): Promise<string> {
274 try {
275 const response = await fetch("https://api.openai.com/v1/chat/completions", {
276 method: "POST",
277 headers: {
288 if (!response.ok) {
289 const errorText = await response.text();
290 console.error("OpenAI API error response:", errorText);
291 try {
292 const errorData = JSON.parse(errorText);
293 throw new Error(`OpenAI API error: ${response.status} ${errorData.error?.message || errorText}`);
294 } catch (e) {
295 throw new Error(`OpenAI API error: ${response.status} ${errorText}`);
296 }
297 }
306
307/**
308 * Stream a chat response from OpenAI
309 */
310async function streamChatResponse(messages: any[], apiKey: string, stream: any): Promise<string> {
311 try {
312 const response = await fetch("https://api.openai.com/v1/chat/completions", {
313 method: "POST",
314 headers: {
326 if (!response.ok) {
327 const errorText = await response.text();
328 console.error("OpenAI API streaming error:", errorText);
329 throw new Error(`OpenAI API error: ${response.status} ${errorText}`);
330 }
331
374 status: "ok",
375 message: "Backend is working",
376 hasOpenAiKey: Boolean(Deno.env.get("OPENAI_API_KEY")),
377 hasValTownOpenAI: true
378 };
379});