2* Multi-Agent Policy Document Analysis (Single Val Version with PDF Upload & Dashboard Style)
3* Demonstrates document ingestion (URL, Text, PDF Upload), content analysis,
4* citation extraction, and basic reference traversal using collaborative AI agents via OpenAI.
5* Uses 'npm:pdf.js-extract' for direct PDF text extraction within the Val (experimental).
6* Serves an HTML UI with a dashboard grid background and handles API requests within the same Val.
7* OpenAI import is dynamically done inside the main server function.
8*
9* Based on structure from multi-agent support simulation example.
10* Assumes 'openai' secret is set in Val Town environment variables.
11*
12* Last Updated: 2025-05-01 (Added PDF upload, dashboard style)
171<p class="description">
172Enter a document URL, paste text, or upload a PDF below. AI agents will analyze content, extract citations, and attempt reference traversal.<br>
173Uses OpenAI via Val Town & <code>npm:pdf.js-extract</code> for PDFs. Current Date: ${
174new Date().toLocaleDateString()
175}
339export default async function(req: Request) {
340// --- Dynamic Imports (Inside Handler) ---
341const { OpenAI } = await import("https://esm.town/v/std/openai");
342const { z } = await import("npm:zod");
343const { fetch } = await import("https://esm.town/v/std/fetch");
397}
398399// --- Helper Function: Call OpenAI API (Unchanged) ---
400async function callOpenAI(
401openai: OpenAI,
402systemPrompt: string,
403userMessage: string,
406): Promise<{ role: "assistant" | "system"; content: string | object }> {
407try {
408const response = await openai.chat.completions.create({
409model: model,
410messages: [{ role: "system", content: systemPrompt }, { role: "user", content: userMessage }],
418return { role: "assistant", content: JSON.parse(content) };
419} catch (parseError) {
420console.error("OpenAI JSON Parse Error:", parseError, "Raw Content:", content);
421throw new Error(`AI response was not valid JSON. Raw: ${content.substring(0, 150)}...`);
422}
423} else { return { role: "assistant", content: content }; }
424} catch (error) {
425console.error(`OpenAI API call failed. ExpectJSON: ${expectJson}. Error:`, error);
426let errorMessage = "Error communicating with AI model.";
427if (error.message) { errorMessage += ` Details: ${error.message}`; }
486log: LogEntry[],
487): Promise<LogEntry[]> {
488const openai = new OpenAI();
489490log.push({ agent: "System", type: "step", message: "Starting analysis workflow." });
554555// Limit text length
556const MAX_TEXT_LENGTH = 20000; // Consider limits for OpenAI context window
557const truncatedText = documentText.substring(0, MAX_TEXT_LENGTH);
558if (documentText.length > MAX_TEXT_LENGTH) {
566// --- Steps 2, 3, 4, 5 (Analysis, Extraction, Traversal, Final Output) ---
567log.push({ agent: "System", type: "step", message: "Starting Content Analysis..." });
568const analysisResponse = await callOpenAI(openai, contentAnalysisSystemPrompt, truncatedText, "gpt-4o", true);
569let analysisResult: AnalysisResult | null = null;
570if (analysisResponse.role === "assistant" && typeof analysisResponse.content === "object") {
575576log.push({ agent: "System", type: "step", message: "Starting Citation Extraction..." });
577const citationResponse = await callOpenAI(openai, citationExtractionSystemPrompt, truncatedText, "gpt-4o", true);
578let extractedCitations: Citation[] = [];
579if (
5// =============================================================================
67import { OpenAI } from "https://esm.town/v/std/openai";
8// import { Request, Response } from "https://esm.town/v/std/fetch"; // Usually global
916}
1718interface OpenAIResponse {
19races: RaceInfo[];
20}
4950// --- ***** THIS FUNCTION WAS MISSING ***** ---
51// --- OpenAI Generation Function ---
52// Asynchronously fetches race data from OpenAI's Chat Completion API.
53async function generateRaceDataWithOpenAI(): Promise<RaceInfo[]> {
54let openai;
55try {
56// IMPORTANT: Ensure the 'openai' secret is configured in your Val Town settings.
57openai = new OpenAI();
58} catch (error) {
59console.error("Failed to initialize OpenAI client. Check 'openai' secret in Val Town.", error);
60console.warn("Using fallback race data due to OpenAI initialization error.");
61return fallbackRaceData;
62}
8687try {
88console.info("Requesting race data generation from OpenAI...");
89const completion = await openai.chat.completions.create({
90model: "gpt-4o", // Or "gpt-3.5-turbo"
91messages: [{ role: "user", content: prompt }],
96const rawContent = completion.choices[0]?.message?.content;
97if (!rawContent) {
98throw new Error("OpenAI returned an empty response message.");
99}
100console.info("Received response from OpenAI. Parsing and validating JSON...");
101let parsedJson;
102try {
103parsedJson = JSON.parse(rawContent);
104} catch (parseError) {
105console.error("Failed to parse OpenAI response as JSON:", rawContent);
106throw new Error("Invalid JSON received from OpenAI.");
107}
108122)
123) {
124console.warn("OpenAI response JSON failed validation:", parsedJson);
125throw new Error("OpenAI response JSON does not match expected structure/data.");
126}
127128const generatedData = (parsedJson as OpenAIResponse).races;
129console.info(`Successfully generated and validated ${generatedData.length} races from OpenAI.`);
130// Trim whitespace just in case
131return generatedData.map(race => ({
136}));
137} catch (error) {
138console.error("Error fetching or processing data from OpenAI:", error);
139if (error.constructor.name === "AuthenticationError") {
140console.error("OpenAI Authentication Error: Check 'openai' secret.");
141} // Add more specific error checks if needed
142console.warn("Using fallback race data due to the error.");
154// 1. Fetch Race Data - Ensure this line is calling the function defined above
155console.log("Attempting to fetch race data...");
156const activeRaceData = await generateRaceDataWithOpenAI();
157console.log(`Workspaceed ${activeRaceData.length} races. First race: ${activeRaceData[0]?.name || "N/A"}`);
158
5// =============================================================================
67import { OpenAI } from "https://esm.town/v/std/openai";
8// import { Request, Response } from "https://esm.town/v/std/fetch"; // Usually global
915}
1617interface OpenAIResponse {
18races: RaceInfo[];
19}
31];
3233async function generateRaceDataWithOpenAI(): Promise<RaceInfo[]> {
34let openai;
35try {
36openai = new OpenAI(); // Ensure 'openai' secret is set in Val Town
37} catch (error) {
38console.error("Failed to initialize OpenAI client.", error);
39return fallbackRaceData;
40}
41const prompt = `{Your OpenAI prompt from previous version - unchanged}`; // Keep the prompt as before
4243try {
44console.info("Requesting race data from OpenAI...");
45const completion = await openai.chat.completions.create({
46model: "gpt-4o",
47messages: [{ role: "user", content: prompt }],
50});
51const rawContent = completion.choices[0]?.message?.content;
52if (!rawContent) throw new Error("OpenAI returned empty content.");
53console.info("Parsing and validating OpenAI response...");
54let parsedJson = JSON.parse(rawContent);
5559|| parsedJson.races.some(/*... detailed item checks ...*/)
60) {
61throw new Error("OpenAI response JSON failed validation.");
62}
63const generatedData = (parsedJson as OpenAIResponse).races;
64console.info(`Successfully validated ${generatedData.length} races from OpenAI.`);
65return generatedData.map(race => ({ ...race, name: race.name.trim() /* etc */
66}));
67} catch (error) {
68console.error("Error fetching/processing from OpenAI:", error);
69return fallbackRaceData;
70}
75// =============================================================================
76export default async function server(request: Request): Promise<Response> {
77const activeRaceData = await generateRaceDataWithOpenAI();
7879const css = `
5// =============================================================================
67// Import OpenAI library from Val Town's standard modules
8// Ensure you have the 'openai' secret set in your Val Town account.
9import { OpenAI } from "https://esm.town/v/std/openai";
10// Import Request and Response types if needed (usually available globally in Val Town)
11// Example: import { Request, Response } from "https://esm.town/v/std/fetch";
21}
2223// Defines the specific JSON structure expected back from the OpenAI API
24interface OpenAIResponse {
25races: RaceInfo[]; // An array containing the race information
26}
2728// --- Fallback Data ---
29// Provides default race data if the OpenAI API call fails or returns invalid data.
30const fallbackRaceData: RaceInfo[] = [
31{
55];
5657// --- OpenAI Generation Function ---
58// Asynchronously fetches race data from OpenAI's Chat Completion API.
59async function generateRaceDataWithOpenAI(): Promise<RaceInfo[]> {
60// Initialize the OpenAI client (using API key from Val Town secrets)
61// Ensure the 'openai' secret is configured in your Val Town settings.
62let openai;
63try {
64openai = new OpenAI();
65} catch (error) {
66console.error("Failed to initialize OpenAI client. Check 'openai' secret in Val Town.", error);
67console.warn("Using fallback race data due to OpenAI initialization error.");
68return fallbackRaceData;
69}
7071// Detailed prompt instructing OpenAI to generate race data in a specific JSON format.
72const prompt = `
73Generate a list of exactly 4 distinct fantasy character races suitable for an RPG character creator carousel.
9495try {
96console.info("Requesting race data generation from OpenAI...");
97const completion = await openai.chat.completions.create({
98model: "gpt-4o", // Recommended model
99messages: [{ role: "user", content: prompt }],
105const rawContent = completion.choices[0]?.message?.content;
106if (!rawContent) {
107throw new Error("OpenAI returned an empty response message.");
108}
109// console.debug("Raw OpenAI Response:", rawContent); // Uncomment for debugging
110111console.info("Received response from OpenAI. Parsing and validating JSON...");
112let parsedJson;
113try {
114parsedJson = JSON.parse(rawContent);
115} catch (parseError) {
116console.error("Failed to parse OpenAI response as JSON:", rawContent);
117throw new Error("Invalid JSON received from OpenAI.");
118}
119133)
134) {
135console.warn("OpenAI response JSON failed validation:", parsedJson);
136throw new Error(
137"OpenAI response JSON does not match the expected structure or contains invalid data.",
138);
139}
140141// Type assertion after successful validation
142const generatedData = (parsedJson as OpenAIResponse).races;
143144console.info(`Successfully generated and validated ${generatedData.length} races from OpenAI.`);
145// Sanitize potentially empty strings just in case validation missed something edge-casey (unlikely with checks above)
146return generatedData.map(race => ({
151}));
152} catch (error) {
153// Log the specific error encountered during the OpenAI call or processing
154console.error("Error fetching or processing data from OpenAI:", error);
155// Check if the error is specifically an AuthenticationError
156if (error.constructor.name === "AuthenticationError") {
157console.error(
158"OpenAI Authentication Error: Please ensure your 'openai' secret is correctly configured in Val Town settings.",
159);
160} else if (error instanceof SyntaxError) {
161console.error("JSON Parsing Error: The response from OpenAI was not valid JSON.");
162} else {
163// General error
164console.error("An unexpected error occurred while communicating with OpenAI.");
165}
166console.warn("Using fallback race data due to the error.");
176// This function handles incoming HTTP requests and returns the HTML for the game.
177export default async function server(request: Request): Promise<Response> {
178// 1. Fetch Race Data: Attempt to get dynamic data from OpenAI, use fallback on error.
179const activeRaceData = await generateRaceDataWithOpenAI();
180181// 2. Define CSS Styles: Includes base carousel, new game UI, and enhanced effects.
1// Val Town Script: Dynamic Character Race Carousel with OpenAI
23// =============================================================================
5// =============================================================================
67// Import OpenAI library from Val Town's standard modules
8// Ensure you have the 'openai' secret set in your Val Town account.
9// Alternatives: import { OpenAI } from "npm:openai";
10import { OpenAI } from "https://esm.town/v/std/openai";
1112// --- Define Expected Data Structures ---
20}
2122// Defines the specific JSON structure expected back from the OpenAI API
23interface OpenAIResponse {
24races: RaceInfo[]; // An array containing the race information
25}
2627// --- Fallback Data ---
28// Provides default race data if the OpenAI API call fails or returns invalid data.
29// This ensures the carousel always has content to display.
30const fallbackRaceData: RaceInfo[] = [
58];
5960// --- OpenAI Generation Function ---
61// Asynchronously fetches race data from OpenAI's Chat Completion API.
62async function generateRaceDataWithOpenAI(): Promise<RaceInfo[]> {
63// Initialize the OpenAI client (using API key from Val Town secrets)
64const openai = new OpenAI();
6566// Detailed prompt instructing OpenAI to generate race data in a specific JSON format.
67const prompt = `
68Generate a list of exactly 4 distinct fantasy character races suitable for an RPG character creator carousel.
8990try {
91console.info("Requesting race data generation from OpenAI...");
92const completion = await openai.chat.completions.create({
93model: "gpt-4o", // Recommended model, can use "gpt-3.5-turbo" as a fallback
94messages: [{ role: "user", content: prompt }],
95// Force OpenAI to return JSON matching the structure described in the prompt
96response_format: { type: "json_object" },
97temperature: 0.8, // Balances creativity and consistency
101const rawContent = completion.choices[0]?.message?.content;
102if (!rawContent) {
103throw new Error("OpenAI returned an empty response message.");
104}
105// console.debug("Raw OpenAI Response:", rawContent); // Uncomment for deep debugging
106107console.info("Received response from OpenAI. Parsing and validating JSON...");
108const parsedJson = JSON.parse(rawContent);
109110// --- Rigorous Validation ---
111// Check if the parsed response conforms to the expected OpenAIResponse structure.
112if (
113typeof parsedJson !== "object" // Must be an object
125)
126) {
127console.warn("OpenAI response JSON failed validation:", parsedJson);
128throw new Error(
129"OpenAI response JSON does not match the expected structure or contains invalid data types/formats.",
130);
131}
132133// Type assertion after successful validation
134const generatedData = (parsedJson as OpenAIResponse).races;
135136console.info(`Successfully generated and validated ${generatedData.length} races from OpenAI.`);
137return generatedData;
138} catch (error) {
139console.error("Error fetching or processing data from OpenAI:", error);
140console.warn("Using fallback race data due to the error.");
141return fallbackRaceData; // Return default data on any failure
146// This function handles incoming HTTP requests and returns an HTML response.
147export default async function server(request: Request): Promise<Response> {
148// 1. Fetch Race Data: Attempt to get dynamic data from OpenAI, use fallback on error.
149const activeRaceData = await generateRaceDataWithOpenAI();
150151// 2. Define CSS Styles: Static CSS for the carousel appearance and animations.
379380// --- Injected Data ---
381// This 'raceData' variable receives the array generated by the server (OpenAI or fallback).
382// It's crucial that this data is valid JSON when the script runs.
383const raceData = ${JSON.stringify(activeRaceData, null, 2)}; // Pretty-print for readability in source
4* and their relationships (citations, references), visualizing them as a network graph.
5* Runs entirely within a single Val Town endpoint.
6* Uses React Flow for visualization and OpenAI for analysis.
7*
8* Structure adapted from the Multi-Agent AI Support Simulation example.
934// --- Main Server Request Handler (Val Town Entry Point) ---
935export default async function(req: Request): Promise<Response> {
936// --- Dynamic Import of OpenAI Library ---
937const { OpenAI } = await import("https://esm.town/v/std/openai");
938939// Server-side logging utility
943}
944945// --- OpenAI Initialization ---
946let openai: OpenAI;
947try {
948// Assumes OPENAI_API_KEY is set as a Val Town secret named 'openai'
949openai = new OpenAI();
950// Perform a simple test call or validation if needed, but often just instantiating is enough.
951} catch (e: any) {
952logServer("ERROR", "Failed to initialize OpenAI Client", { error: e.message });
953// If OpenAI fails to init, we can still potentially serve the HTML,
954// but API calls will fail later. For API calls, we return an immediate error.
955// We'll handle API call errors within the analysis function.
970}
971972// Check if OpenAI client initialized successfully *before* making the call
973if (!openai) {
974return {
975success: false,
976error: "OpenAI client failed to initialize. Check server logs and API key configuration.",
977};
978}
10181019try {
1020const completion = await openai.chat.completions.create({
1021model: "gpt-4o", // Recommend powerful model for this task
1022response_format: { type: "json_object" },
1079logServer("ERROR", "Policy Analysis tool execution failed", { errorMessage: error.message, stack: error.stack });
1080let specificError = error.message;
1081// Check for common OpenAI API errors
1082if (error.status === 401) specificError = "OpenAI Authentication failed. Check API key secret.";
1083if (error.status === 429) specificError = "OpenAI Rate limit or quota exceeded.";
1084if (error.status >= 500) specificError = "OpenAI server error. Please try again later.";
10851086return { success: false, error: `Analysis failed: \${specificError}` };
11001101try {
1102// --- Check OpenAI init status again specifically for API calls ---
1103if (!openai) {
1104throw new Error("OpenAI client is not available. Check server configuration/secrets.");
1105}
1106
faithfulTanEelmain.tsx20 matches
1// Val Town Script: Dynamic Character Race Carousel with OpenAI
23// Import the OpenAI library (ensure you have the 'openai' secret set in Val Town)
4import { OpenAI } from "https://esm.town/v/std/openai"; // Or use 'npm:openai' or 'std/openai'
56// --- Define Expected Data Structures ---
13}
1415// Define the specific JSON structure we expect OpenAI to return
16interface OpenAIResponse {
17races: RaceInfo[];
18}
1920// --- Fallback Data ---
21// Used if OpenAI call fails or returns invalid data
22const fallbackRaceData: RaceInfo[] = [
23{
47];
4849// --- OpenAI Generation Function ---
50async function generateRaceDataWithOpenAI(): Promise<RaceInfo[]> {
51// Use 'new OpenAI()' if using std/openai
52const openai = new OpenAI();
5354// Updated prompt requesting a specific JSON object structure
7071try {
72console.info("Calling OpenAI to generate race data...");
73const completion = await openai.chat.completions.create({
74model: "gpt-4o", // Or "gpt-3.5-turbo"
75messages: [{ role: "user", content: prompt }],
81const content = completion.choices[0]?.message?.content;
82if (!content) {
83throw new Error("OpenAI returned an empty response content.");
84}
8586console.info("Received response from OpenAI. Parsing JSON...");
87const parsedJson = JSON.parse(content);
88101)
102) {
103throw new Error("OpenAI response JSON does not match the expected structure or contains invalid data.");
104}
105106const generatedData = (parsedJson as OpenAIResponse).races;
107108console.info(`Successfully generated and validated ${generatedData.length} races.`);
109return generatedData;
110} catch (error) {
111console.error("Error fetching or processing data from OpenAI:", error);
112console.warn("Using fallback race data due to error.");
113return fallbackRaceData;
117// --- Main HTTP Handler (Val Town Entry Point) ---
118export default async function server(request: Request): Promise<Response> {
119// 1. Generate race data using OpenAI (or use fallback on error)
120const activeRaceData = await generateRaceDataWithOpenAI();
121122// 2. Define CSS (remains static)
165166// Inject the dynamically generated data from the server
167// This 'raceData' variable will hold the array from OpenAI or the fallback
168const raceData = ${JSON.stringify(activeRaceData, null, 2)};
169
stevensDemo.cursorrules4 matches
100Note: When changing a SQLite table's schema, change the table's name (e.g., add _2 or _3) to create a fresh table.
101102### OpenAI
103```ts
104import { OpenAI } from "https://esm.town/v/std/openai";
105const openai = new OpenAI();
106const completion = await openai.chat.completions.create({
107messages: [
108{ role: "user", content: "Say hello in a creative way" },
stevensDemo.cursorrules4 matches
100Note: When changing a SQLite table's schema, change the table's name (e.g., add _2 or _3) to create a fresh table.
101102### OpenAI
103```ts
104import { OpenAI } from "https://esm.town/v/std/openai";
105const openai = new OpenAI();
106const completion = await openai.chat.completions.create({
107messages: [
108{ role: "user", content: "Say hello in a creative way" },
strategistmain.tsx3 matches
276export default async function server(request: Request): Promise<Response> {
277if (request.method === "POST" && new URL(request.url).pathname === "/generate-plan") {
278const { OpenAI } = await import("https://esm.town/v/std/openai");
279const openai = new OpenAI();
280
281const { context } = await request.json();
282283const completion = await openai.chat.completions.create({
284model: "gpt-4o-mini",
285messages: [