@@ -353,11 +352,11 @@ class Agent extends DashboardView {
const { messages } = this.state;
const { agentConfig } = this.props;
const models = agentConfig?.models || [];
-
+
// Check if agent configuration is missing or no models are configured
const hasNoAgentConfig = !agentConfig;
const hasNoModels = models.length === 0;
-
+
return (
{this.renderToolbar()}
diff --git a/src/lib/AgentService.js b/src/lib/AgentService.js
index 3ac49c0f4e..d47be52290 100644
--- a/src/lib/AgentService.js
+++ b/src/lib/AgentService.js
@@ -15,12 +15,11 @@ export default class AgentService {
* Send a message to the configured AI model and get a response
* @param {string} message - The user's message
* @param {Object} modelConfig - The model configuration object
- * @param {string|null} instructions - Optional system instructions for the AI (currently ignored, handled server-side)
* @param {string} appSlug - The app slug to scope the request to
* @param {string|null} conversationId - Optional conversation ID to maintain context
* @returns {Promise<{response: string, conversationId: string}>} The AI's response and conversation ID
*/
- static async sendMessage(message, modelConfig, instructions = null, appSlug, conversationId = null) {
+ static async sendMessage(message, modelConfig, appSlug, conversationId = null) {
if (!modelConfig) {
throw new Error('Model configuration is required');
}
@@ -40,18 +39,44 @@ export default class AgentService {
message: message,
modelName: name
};
-
+
// Include conversation ID if provided
if (conversationId) {
requestBody.conversationId = conversationId;
}
-
+
const response = await post(`/apps/${appSlug}/agent`, requestBody);
+ // Log the request and response for debugging
+ console.log('Agent API Request:', {
+ message,
+ modelName: name,
+ appSlug,
+ conversationId,
+ timestamp: new Date().toISOString()
+ });
+
if (response.error) {
+ console.error('Agent API Error Response:', response.error);
throw new Error(response.error);
}
+ console.log('Agent API Success Response:', {
+ responseLength: response.response?.length || 0,
+ conversationId: response.conversationId,
+ debug: response.debug,
+ timestamp: new Date().toISOString()
+ });
+
+ // Log Parse Server operations if available
+ if (response.debug?.operations?.length > 0) {
+ console.group('Parse Server Operations:');
+ response.debug.operations.forEach((op, index) => {
+ console.log(`${index + 1}. ${op.operation}:`, op);
+ });
+ console.groupEnd();
+ }
+
return {
response: response.response,
conversationId: response.conversationId
@@ -61,16 +86,16 @@ export default class AgentService {
if (error.message && error.message.includes('Permission Denied')) {
throw new Error('Permission denied. Please refresh the page and try again.');
}
-
+
if (error.message && error.message.includes('CSRF')) {
throw new Error('Security token expired. Please refresh the page and try again.');
}
-
+
// Handle network errors and other fetch-related errors
if (error.message && error.message.includes('fetch')) {
throw new Error('Network error: Unable to connect to agent service. Please check your internet connection.');
}
-
+
// Re-throw the original error if it's not a recognized type
throw error;
}
From 7b99cb461ab75eb5b721aaeb3c6252b28c1d0474 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 15:15:35 +0200
Subject: [PATCH 24/42] debug
---
Parse-Dashboard/app.js | 229 ----------------------------------------
src/lib/AgentService.js | 26 -----
2 files changed, 255 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index c4b8980200..5ee9eb7bc2 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -491,15 +491,6 @@ module.exports = function(config, options) {
async function executeDatabaseFunction(functionName, args, appContext, operationLog = []) {
const Parse = require('parse/node');
- // Debug logging for all Parse Server requests
- console.log('Parse Server Request:', {
- operation: functionName,
- args: args,
- appId: appContext.appId,
- serverURL: appContext.serverURL,
- timestamp: new Date().toISOString()
- });
-
// Initialize Parse for this app context
Parse.initialize(appContext.appId, undefined, appContext.masterKey);
Parse.serverURL = appContext.serverURL;
@@ -558,21 +549,6 @@ module.exports = function(config, options) {
timestamp: new Date().toISOString()
};
- // Log Parse SDK operation details
- console.log('Parse SDK Operation:', {
- method: 'Query.find()',
- className,
- constraints: where,
- limit,
- skip,
- order,
- include,
- select,
- resultCount: results.length,
- useMasterKey: true
- });
-
- console.log('Parse Server Response:', operationSummary);
operationLog.push(operationSummary);
return resultData;
}
@@ -597,30 +573,9 @@ module.exports = function(config, options) {
object.set(key, objectData[key]);
});
- // Log Parse SDK operation details
- console.log('Parse SDK Operation:', {
- method: 'ParseObject.save()',
- className,
- data: objectData,
- useMasterKey: true
- });
-
const result = await object.save(null, { useMasterKey: true });
const resultData = result.toJSON();
- console.log('Parse SDK Result:', {
- method: 'ParseObject.save()',
- className,
- objectId: resultData.objectId,
- createdAt: resultData.createdAt
- });
-
- console.log('Parse Server Response:', {
- operation: 'createObject',
- className,
- objectId: resultData.objectId,
- timestamp: new Date().toISOString()
- });
return resultData;
}
@@ -632,53 +587,16 @@ module.exports = function(config, options) {
throw new Error(`Updating objects requires user confirmation. The AI should ask for permission before updating object ${objectId} in the ${className} class.`);
}
- // Log Parse SDK operation details for getting the object
- console.log('Parse SDK Operation:', {
- method: 'Query.get()',
- className,
- objectId,
- useMasterKey: true
- });
-
const query = new Parse.Query(className);
const object = await query.get(objectId, { useMasterKey: true });
- console.log('Parse SDK Result:', {
- method: 'Query.get()',
- className,
- objectId,
- found: true
- });
-
Object.keys(objectData).forEach(key => {
object.set(key, objectData[key]);
});
- // Log Parse SDK operation details for saving the updated object
- console.log('Parse SDK Operation:', {
- method: 'ParseObject.save() [update]',
- className,
- objectId,
- updateData: objectData,
- useMasterKey: true
- });
-
const result = await object.save(null, { useMasterKey: true });
const resultData = result.toJSON();
- console.log('Parse SDK Result:', {
- method: 'ParseObject.save() [update]',
- className,
- objectId,
- updatedAt: resultData.updatedAt
- });
-
- console.log('Parse Server Response:', {
- operation: 'updateObject',
- className,
- objectId,
- timestamp: new Date().toISOString()
- });
return resultData;
}
@@ -690,48 +608,12 @@ module.exports = function(config, options) {
throw new Error(`Deleting objects requires user confirmation. The AI should ask for permission before permanently deleting object ${objectId} from the ${className} class.`);
}
- // Log Parse SDK operation details for getting the object
- console.log('Parse SDK Operation:', {
- method: 'Query.get()',
- className,
- objectId,
- useMasterKey: true
- });
-
const query = new Parse.Query(className);
const object = await query.get(objectId, { useMasterKey: true });
- console.log('Parse SDK Result:', {
- method: 'Query.get()',
- className,
- objectId,
- found: true
- });
-
- // Log Parse SDK operation details for deleting the object
- console.log('Parse SDK Operation:', {
- method: 'ParseObject.destroy()',
- className,
- objectId,
- useMasterKey: true
- });
-
await object.destroy({ useMasterKey: true });
- console.log('Parse SDK Result:', {
- method: 'ParseObject.destroy()',
- className,
- objectId,
- deleted: true
- });
-
const result = { success: true, objectId };
- console.log('Parse Server Response:', {
- operation: 'deleteObject',
- className,
- objectId,
- timestamp: new Date().toISOString()
- });
return result;
}
@@ -739,45 +621,9 @@ module.exports = function(config, options) {
const { className } = args;
let result;
if (className) {
- // Log Parse SDK operation details for getting a specific schema
- console.log('Parse SDK Operation:', {
- method: 'Parse.Schema.get()',
- className
- });
-
result = await new Parse.Schema(className).get();
-
- console.log('Parse SDK Result:', {
- method: 'Parse.Schema.get()',
- className,
- fields: Object.keys(result.fields || {}).length
- });
-
- console.log('Parse Server Response:', {
- operation: 'getSchema',
- className,
- timestamp: new Date().toISOString()
- });
} else {
- // Log Parse SDK operation details for getting all schemas
- console.log('Parse SDK Operation:', {
- method: 'Parse.Schema.all()'
- });
-
result = await Parse.Schema.all();
-
- console.log('Parse SDK Result:', {
- method: 'Parse.Schema.all()',
- schemaCount: result.length,
- classNames: result.map(schema => schema.className)
- });
-
- console.log('Parse Server Response:', {
- operation: 'getSchema',
- action: 'getAllSchemas',
- schemaCount: result.length,
- timestamp: new Date().toISOString()
- });
}
return result;
}
@@ -809,29 +655,9 @@ module.exports = function(config, options) {
}
});
- // Log Parse SDK operation details
- console.log('Parse SDK Operation:', {
- method: 'Query.count()',
- className,
- constraints: where,
- useMasterKey: true
- });
-
const count = await query.count({ useMasterKey: true });
- console.log('Parse SDK Result:', {
- method: 'Query.count()',
- className,
- count
- });
-
const result = { count };
- console.log('Parse Server Response:', {
- operation: 'countObjects',
- className,
- count,
- timestamp: new Date().toISOString()
- });
return result;
}
@@ -843,14 +669,6 @@ module.exports = function(config, options) {
throw new Error(`Creating classes requires user confirmation. The AI should ask for permission before creating the ${className} class.`);
}
- // Log Parse SDK operation details
- console.log('Parse SDK Operation:', {
- method: 'new Parse.Schema().save()',
- className,
- fields,
- useMasterKey: true
- });
-
const schema = new Parse.Schema(className);
// Add fields to the schema
@@ -890,19 +708,7 @@ module.exports = function(config, options) {
const result = await schema.save();
- console.log('Parse SDK Result:', {
- method: 'new Parse.Schema().save()',
- className,
- savedSchema: result.toJSON()
- });
-
const resultData = { success: true, className, schema: result };
- console.log('Parse Server Response:', {
- operation: 'createClass',
- className,
- fieldsCount: Object.keys(fields).length,
- timestamp: new Date().toISOString()
- });
return resultData;
}
@@ -1075,22 +881,6 @@ You have direct access to the Parse database through function calls, so you can
const functionName = toolCall.function.name;
const functionArgs = JSON.parse(toolCall.function.arguments);
- // Detailed logging to debug function call parameters
- console.log('=== FUNCTION CALL DEBUG ===');
- console.log('Function Name:', functionName);
- console.log('Raw Arguments String:', toolCall.function.arguments);
- console.log('Parsed Arguments:', functionArgs);
- console.log('Arguments Keys:', Object.keys(functionArgs));
- if (functionName === 'createObject') {
- console.log('createObject Analysis:');
- console.log('- className:', functionArgs.className);
- console.log('- objectData:', functionArgs.objectData);
- console.log('- objectData type:', typeof functionArgs.objectData);
- console.log('- objectData keys:', functionArgs.objectData ? Object.keys(functionArgs.objectData) : 'OBJECTDATA IS MISSING');
- console.log('- confirmed:', functionArgs.confirmed);
- }
- console.log('=== END DEBUG ===');
-
console.log('Executing database function:', {
functionName,
args: functionArgs,
@@ -1099,19 +889,6 @@ You have direct access to the Parse database through function calls, so you can
timestamp: new Date().toISOString()
});
- // Special validation for createObject function calls
- if (functionName === 'createObject' && (!functionArgs.objectData || typeof functionArgs.objectData !== 'object' || Object.keys(functionArgs.objectData).length === 0)) {
- const missingDataError = {
- error: 'CRITICAL ERROR: createObject function call is missing the required objectData parameter. You must provide the objectData parameter with actual field values. Example: {"className": "TestCars", "objectData": {"model": "Honda Civic", "year": 2023, "brand": "Honda"}, "confirmed": true}. Please retry the function call with the objectData parameter containing the object fields and values you want to create.'
- };
- toolResponses.push({
- tool_call_id: toolCall.id,
- role: 'tool',
- content: JSON.stringify(missingDataError)
- });
- continue; // Skip to next tool call
- }
-
// Execute the database function
const result = await executeDatabaseFunction(functionName, functionArgs, appContext, operationLog);
@@ -1121,12 +898,6 @@ You have direct access to the Parse database through function calls, so you can
content: result ? JSON.stringify(result) : JSON.stringify({ success: true })
});
} catch (error) {
- console.error('Parse operation error:', {
- functionName: toolCall.function.name,
- args: toolCall.function.arguments,
- error: error.message,
- stack: error.stack
- });
toolResponses.push({
tool_call_id: toolCall.id,
role: 'tool',
diff --git a/src/lib/AgentService.js b/src/lib/AgentService.js
index d47be52290..bca88fcc7c 100644
--- a/src/lib/AgentService.js
+++ b/src/lib/AgentService.js
@@ -47,36 +47,10 @@ export default class AgentService {
const response = await post(`/apps/${appSlug}/agent`, requestBody);
- // Log the request and response for debugging
- console.log('Agent API Request:', {
- message,
- modelName: name,
- appSlug,
- conversationId,
- timestamp: new Date().toISOString()
- });
-
if (response.error) {
- console.error('Agent API Error Response:', response.error);
throw new Error(response.error);
}
- console.log('Agent API Success Response:', {
- responseLength: response.response?.length || 0,
- conversationId: response.conversationId,
- debug: response.debug,
- timestamp: new Date().toISOString()
- });
-
- // Log Parse Server operations if available
- if (response.debug?.operations?.length > 0) {
- console.group('Parse Server Operations:');
- response.debug.operations.forEach((op, index) => {
- console.log(`${index + 1}. ${op.operation}:`, op);
- });
- console.groupEnd();
- }
-
return {
response: response.response,
conversationId: response.conversationId
From f87dc21bdf0e7cf7135854b32026dcd878636f88 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 15:15:41 +0200
Subject: [PATCH 25/42] Revert "debug"
This reverts commit 7b99cb461ab75eb5b721aaeb3c6252b28c1d0474.
---
Parse-Dashboard/app.js | 229 ++++++++++++++++++++++++++++++++++++++++
src/lib/AgentService.js | 26 +++++
2 files changed, 255 insertions(+)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index 5ee9eb7bc2..c4b8980200 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -491,6 +491,15 @@ module.exports = function(config, options) {
async function executeDatabaseFunction(functionName, args, appContext, operationLog = []) {
const Parse = require('parse/node');
+ // Debug logging for all Parse Server requests
+ console.log('Parse Server Request:', {
+ operation: functionName,
+ args: args,
+ appId: appContext.appId,
+ serverURL: appContext.serverURL,
+ timestamp: new Date().toISOString()
+ });
+
// Initialize Parse for this app context
Parse.initialize(appContext.appId, undefined, appContext.masterKey);
Parse.serverURL = appContext.serverURL;
@@ -549,6 +558,21 @@ module.exports = function(config, options) {
timestamp: new Date().toISOString()
};
+ // Log Parse SDK operation details
+ console.log('Parse SDK Operation:', {
+ method: 'Query.find()',
+ className,
+ constraints: where,
+ limit,
+ skip,
+ order,
+ include,
+ select,
+ resultCount: results.length,
+ useMasterKey: true
+ });
+
+ console.log('Parse Server Response:', operationSummary);
operationLog.push(operationSummary);
return resultData;
}
@@ -573,9 +597,30 @@ module.exports = function(config, options) {
object.set(key, objectData[key]);
});
+ // Log Parse SDK operation details
+ console.log('Parse SDK Operation:', {
+ method: 'ParseObject.save()',
+ className,
+ data: objectData,
+ useMasterKey: true
+ });
+
const result = await object.save(null, { useMasterKey: true });
const resultData = result.toJSON();
+ console.log('Parse SDK Result:', {
+ method: 'ParseObject.save()',
+ className,
+ objectId: resultData.objectId,
+ createdAt: resultData.createdAt
+ });
+
+ console.log('Parse Server Response:', {
+ operation: 'createObject',
+ className,
+ objectId: resultData.objectId,
+ timestamp: new Date().toISOString()
+ });
return resultData;
}
@@ -587,16 +632,53 @@ module.exports = function(config, options) {
throw new Error(`Updating objects requires user confirmation. The AI should ask for permission before updating object ${objectId} in the ${className} class.`);
}
+ // Log Parse SDK operation details for getting the object
+ console.log('Parse SDK Operation:', {
+ method: 'Query.get()',
+ className,
+ objectId,
+ useMasterKey: true
+ });
+
const query = new Parse.Query(className);
const object = await query.get(objectId, { useMasterKey: true });
+ console.log('Parse SDK Result:', {
+ method: 'Query.get()',
+ className,
+ objectId,
+ found: true
+ });
+
Object.keys(objectData).forEach(key => {
object.set(key, objectData[key]);
});
+ // Log Parse SDK operation details for saving the updated object
+ console.log('Parse SDK Operation:', {
+ method: 'ParseObject.save() [update]',
+ className,
+ objectId,
+ updateData: objectData,
+ useMasterKey: true
+ });
+
const result = await object.save(null, { useMasterKey: true });
const resultData = result.toJSON();
+ console.log('Parse SDK Result:', {
+ method: 'ParseObject.save() [update]',
+ className,
+ objectId,
+ updatedAt: resultData.updatedAt
+ });
+
+ console.log('Parse Server Response:', {
+ operation: 'updateObject',
+ className,
+ objectId,
+ timestamp: new Date().toISOString()
+ });
return resultData;
}
@@ -608,12 +690,48 @@ module.exports = function(config, options) {
throw new Error(`Deleting objects requires user confirmation. The AI should ask for permission before permanently deleting object ${objectId} from the ${className} class.`);
}
+ // Log Parse SDK operation details for getting the object
+ console.log('Parse SDK Operation:', {
+ method: 'Query.get()',
+ className,
+ objectId,
+ useMasterKey: true
+ });
+
const query = new Parse.Query(className);
const object = await query.get(objectId, { useMasterKey: true });
+ console.log('Parse SDK Result:', {
+ method: 'Query.get()',
+ className,
+ objectId,
+ found: true
+ });
+
+ // Log Parse SDK operation details for deleting the object
+ console.log('Parse SDK Operation:', {
+ method: 'ParseObject.destroy()',
+ className,
+ objectId,
+ useMasterKey: true
+ });
+
await object.destroy({ useMasterKey: true });
+ console.log('Parse SDK Result:', {
+ method: 'ParseObject.destroy()',
+ className,
+ objectId,
+ deleted: true
+ });
+
const result = { success: true, objectId };
+ console.log('Parse Server Response:', {
+ operation: 'deleteObject',
+ className,
+ objectId,
+ timestamp: new Date().toISOString()
+ });
return result;
}
@@ -621,9 +739,45 @@ module.exports = function(config, options) {
const { className } = args;
let result;
if (className) {
+ // Log Parse SDK operation details for getting a specific schema
+ console.log('Parse SDK Operation:', {
+ method: 'Parse.Schema.get()',
+ className
+ });
+
result = await new Parse.Schema(className).get();
+
+ console.log('Parse SDK Result:', {
+ method: 'Parse.Schema.get()',
+ className,
+ fields: Object.keys(result.fields || {}).length
+ });
+
+ console.log('Parse Server Response:', {
+ operation: 'getSchema',
+ className,
+ timestamp: new Date().toISOString()
+ });
} else {
+ // Log Parse SDK operation details for getting all schemas
+ console.log('Parse SDK Operation:', {
+ method: 'Parse.Schema.all()'
+ });
+
result = await Parse.Schema.all();
+
+ console.log('Parse SDK Result:', {
+ method: 'Parse.Schema.all()',
+ schemaCount: result.length,
+ classNames: result.map(schema => schema.className)
+ });
+
+ console.log('Parse Server Response:', {
+ operation: 'getSchema',
+ action: 'getAllSchemas',
+ schemaCount: result.length,
+ timestamp: new Date().toISOString()
+ });
}
return result;
}
@@ -655,9 +809,29 @@ module.exports = function(config, options) {
}
});
+ // Log Parse SDK operation details
+ console.log('Parse SDK Operation:', {
+ method: 'Query.count()',
+ className,
+ constraints: where,
+ useMasterKey: true
+ });
+
const count = await query.count({ useMasterKey: true });
+ console.log('Parse SDK Result:', {
+ method: 'Query.count()',
+ className,
+ count
+ });
+
const result = { count };
+ console.log('Parse Server Response:', {
+ operation: 'countObjects',
+ className,
+ count,
+ timestamp: new Date().toISOString()
+ });
return result;
}
@@ -669,6 +843,14 @@ module.exports = function(config, options) {
throw new Error(`Creating classes requires user confirmation. The AI should ask for permission before creating the ${className} class.`);
}
+ // Log Parse SDK operation details
+ console.log('Parse SDK Operation:', {
+ method: 'new Parse.Schema().save()',
+ className,
+ fields,
+ useMasterKey: true
+ });
+
const schema = new Parse.Schema(className);
// Add fields to the schema
@@ -708,7 +890,19 @@ module.exports = function(config, options) {
const result = await schema.save();
+ console.log('Parse SDK Result:', {
+ method: 'new Parse.Schema().save()',
+ className,
+ savedSchema: result.toJSON()
+ });
+
const resultData = { success: true, className, schema: result };
+ console.log('Parse Server Response:', {
+ operation: 'createClass',
+ className,
+ fieldsCount: Object.keys(fields).length,
+ timestamp: new Date().toISOString()
+ });
return resultData;
}
@@ -881,6 +1075,22 @@ You have direct access to the Parse database through function calls, so you can
const functionName = toolCall.function.name;
const functionArgs = JSON.parse(toolCall.function.arguments);
+ // Detailed logging to debug function call parameters
+ console.log('=== FUNCTION CALL DEBUG ===');
+ console.log('Function Name:', functionName);
+ console.log('Raw Arguments String:', toolCall.function.arguments);
+ console.log('Parsed Arguments:', functionArgs);
+ console.log('Arguments Keys:', Object.keys(functionArgs));
+ if (functionName === 'createObject') {
+ console.log('createObject Analysis:');
+ console.log('- className:', functionArgs.className);
+ console.log('- objectData:', functionArgs.objectData);
+ console.log('- objectData type:', typeof functionArgs.objectData);
+ console.log('- objectData keys:', functionArgs.objectData ? Object.keys(functionArgs.objectData) : 'OBJECTDATA IS MISSING');
+ console.log('- confirmed:', functionArgs.confirmed);
+ }
+ console.log('=== END DEBUG ===');
+
console.log('Executing database function:', {
functionName,
args: functionArgs,
@@ -889,6 +1099,19 @@ You have direct access to the Parse database through function calls, so you can
timestamp: new Date().toISOString()
});
+ // Special validation for createObject function calls
+ if (functionName === 'createObject' && (!functionArgs.objectData || typeof functionArgs.objectData !== 'object' || Object.keys(functionArgs.objectData).length === 0)) {
+ const missingDataError = {
+ error: 'CRITICAL ERROR: createObject function call is missing the required objectData parameter. You must provide the objectData parameter with actual field values. Example: {"className": "TestCars", "objectData": {"model": "Honda Civic", "year": 2023, "brand": "Honda"}, "confirmed": true}. Please retry the function call with the objectData parameter containing the object fields and values you want to create.'
+ };
+ toolResponses.push({
+ tool_call_id: toolCall.id,
+ role: 'tool',
+ content: JSON.stringify(missingDataError)
+ });
+ continue; // Skip to next tool call
+ }
+
// Execute the database function
const result = await executeDatabaseFunction(functionName, functionArgs, appContext, operationLog);
@@ -898,6 +1121,12 @@ You have direct access to the Parse database through function calls, so you can
content: result ? JSON.stringify(result) : JSON.stringify({ success: true })
});
} catch (error) {
+ console.error('Parse operation error:', {
+ functionName: toolCall.function.name,
+ args: toolCall.function.arguments,
+ error: error.message,
+ stack: error.stack
+ });
toolResponses.push({
tool_call_id: toolCall.id,
role: 'tool',
diff --git a/src/lib/AgentService.js b/src/lib/AgentService.js
index bca88fcc7c..d47be52290 100644
--- a/src/lib/AgentService.js
+++ b/src/lib/AgentService.js
@@ -47,10 +47,36 @@ export default class AgentService {
const response = await post(`/apps/${appSlug}/agent`, requestBody);
+ // Log the request and response for debugging
+ console.log('Agent API Request:', {
+ message,
+ modelName: name,
+ appSlug,
+ conversationId,
+ timestamp: new Date().toISOString()
+ });
+
if (response.error) {
+ console.error('Agent API Error Response:', response.error);
throw new Error(response.error);
}
+ console.log('Agent API Success Response:', {
+ responseLength: response.response?.length || 0,
+ conversationId: response.conversationId,
+ debug: response.debug,
+ timestamp: new Date().toISOString()
+ });
+
+ // Log Parse Server operations if available
+ if (response.debug?.operations?.length > 0) {
+ console.group('Parse Server Operations:');
+ response.debug.operations.forEach((op, index) => {
+ console.log(`${index + 1}. ${op.operation}:`, op);
+ });
+ console.groupEnd();
+ }
+
return {
response: response.response,
conversationId: response.conversationId
From 077b5a0aa613b83d8bf27eea9bc210e3c7944bc2 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 15:38:50 +0200
Subject: [PATCH 26/42] inst
---
Parse-Dashboard/app.js | 20 ++++---
src/dashboard/Data/Agent/Agent.react.js | 2 -
src/lib/AgentService.js | 79 -------------------------
3 files changed, 11 insertions(+), 90 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index c4b8980200..e78bd202bd 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -955,13 +955,6 @@ You have access to database function tools that allow you to:
- Get schema information for classes (read-only, no confirmation needed)
- Count objects that match certain criteria (read-only, no confirmation needed)
-CRITICAL RULE FOR createObject FUNCTION:
-- The createObject function REQUIRES THREE parameters: className, objectData, and confirmed
-- The 'objectData' parameter MUST contain the actual field values as a JSON object
-- NEVER call createObject with only className and confirmed - this will fail
-- Example: createObject({className: 'TestCars', objectData: {model: 'Honda Civic', year: 2023, brand: 'Honda'}, confirmed: true})
-- The objectData object should contain all the fields and their values that you want to save
-
CRITICAL SECURITY RULE FOR WRITE OPERATIONS:
- ANY write operation (create, update, delete) MUST have explicit user confirmation through conversation
- When a user requests a write operation, explain what you will do and ask for confirmation
@@ -986,8 +979,6 @@ When working with the database:
- Be mindful of data types (Date, Pointer, etc.)
- Always consider security and use appropriate query constraints
- Provide clear explanations of what database operations you're performing
-- IMPORTANT: When creating objects, you MUST provide the 'objectData' parameter with actual field values. Never call createObject with only className and confirmed - always include the objectData object with the fields and values to be saved.
-- IMPORTANT: When updating objects, you MUST provide the 'objectData' parameter with the fields you want to update. Include the objectData object with field names and new values.
- If any database function returns an error, you MUST include the full error message in your response to the user. Never hide error details or give vague responses like "there was an issue" - always show the specific error message.
When responding:
@@ -997,6 +988,17 @@ When responding:
- Focus on Parse-specific solutions and recommendations
- If you perform database operations, explain what you did and show the results
- For write operations, always explain the impact and ask for explicit confirmation
+- Format your responses using Markdown for better readability:
+ * Use **bold** for important information
+ * Use *italic* for emphasis
+ * Use \`code\` for field names, class names, and values
+ * Use numbered lists for step-by-step instructions
+ * Use bullet points for listing items
+ * Use tables when showing structured data
+ * Use code blocks with language specification for code examples
+ * Use headers (##, ###) to organize longer responses
+ * When listing database classes, format as a numbered list with descriptions
+ * Use tables for structured data comparison
You have direct access to the Parse database through function calls, so you can query actual data and provide real-time information.${appInfo}`
}
diff --git a/src/dashboard/Data/Agent/Agent.react.js b/src/dashboard/Data/Agent/Agent.react.js
index 3367b9770f..272b76c6fb 100644
--- a/src/dashboard/Data/Agent/Agent.react.js
+++ b/src/dashboard/Data/Agent/Agent.react.js
@@ -175,11 +175,9 @@ class Agent extends DashboardView {
}
// Get response from AI service with conversation context
- const instructions = AgentService.getDefaultInstructions();
const result = await AgentService.sendMessage(
inputValue.trim(),
modelConfig,
- instructions,
appSlug,
this.state.conversationId
);
diff --git a/src/lib/AgentService.js b/src/lib/AgentService.js
index d47be52290..fde85f3524 100644
--- a/src/lib/AgentService.js
+++ b/src/lib/AgentService.js
@@ -47,27 +47,11 @@ export default class AgentService {
const response = await post(`/apps/${appSlug}/agent`, requestBody);
- // Log the request and response for debugging
- console.log('Agent API Request:', {
- message,
- modelName: name,
- appSlug,
- conversationId,
- timestamp: new Date().toISOString()
- });
-
if (response.error) {
console.error('Agent API Error Response:', response.error);
throw new Error(response.error);
}
- console.log('Agent API Success Response:', {
- responseLength: response.response?.length || 0,
- conversationId: response.conversationId,
- debug: response.debug,
- timestamp: new Date().toISOString()
- });
-
// Log Parse Server operations if available
if (response.debug?.operations?.length > 0) {
console.group('Parse Server Operations:');
@@ -136,67 +120,4 @@ export default class AgentService {
return true;
}
- /**
- * Get default system instructions for Parse Dashboard agent
- * @returns {string} Default system instructions
- */
- static getDefaultInstructions() {
- return `You are an AI assistant integrated into Parse Dashboard, a data management interface for Parse Server applications.
-
-Your role is to help users with:
-- Database queries and data operations using the Parse JS SDK
-- Understanding Parse Server concepts and best practices
-- Troubleshooting common issues
-- Best practices for data modeling
-- Cloud Code and server configuration guidance
-
-You have access to database function tools that allow you to:
-- Query classes/tables to retrieve objects (read-only, no confirmation needed)
-- Create new objects in classes (REQUIRES USER CONFIRMATION)
-- Update existing objects (REQUIRES USER CONFIRMATION)
-- Delete objects (REQUIRES USER CONFIRMATION)
-- Get schema information for classes (read-only, no confirmation needed)
-- Count objects that match certain criteria (read-only, no confirmation needed)
-
-CRITICAL SECURITY RULE FOR WRITE OPERATIONS:
-- ANY write operation (create, update, delete) MUST have explicit user confirmation BEFORE execution
-- You MUST ask the user to confirm each write operation individually
-- You CANNOT assume consent or perform write operations without explicit permission
-- The user cannot disable this confirmation requirement
-- Even if the user says "yes to all" or similar, you must still ask for each operation
-- If a user requests multiple write operations, ask for confirmation for each one separately
-
-When working with the database:
-- Read operations (query, getSchema, count) can be performed immediately
-- Write operations require the pattern: 1) Explain what you'll do, 2) Ask for confirmation, 3) Only then execute if confirmed
-- Always use the provided database functions to interact with data
-- Class names are case-sensitive
-- Use proper Parse query syntax for complex queries
-- Handle objectId fields correctly
-- Be mindful of data types (Date, Pointer, etc.)
-- Always consider security and use appropriate query constraints
-- Provide clear explanations of what database operations you're performing
-- If any database function returns an error, you MUST include the full error message in your response to the user. Never hide error details or give vague responses like "there was an issue" - always show the specific error message.
-
-When responding:
-- Be concise and helpful
-- Provide practical examples when relevant
-- Ask clarifying questions if the user's request is unclear
-- Focus on Parse-specific solutions and recommendations
-- If you perform database operations, explain what you did and show the results
-- For write operations, always explain the impact and ask for explicit confirmation
-- Format your responses using Markdown for better readability:
- * Use **bold** for important information
- * Use *italic* for emphasis
- * Use \`code\` for field names, class names, and values
- * Use numbered lists for step-by-step instructions
- * Use bullet points for listing items
- * Use tables when showing structured data
- * Use code blocks with language specification for code examples
- * Use headers (##, ###) to organize longer responses
- * When listing database classes, format as a numbered list with descriptions
- * Use tables for structured data comparison
-
-You have direct access to the Parse database through function calls, so you can query actual data and provide real-time information about the user's Parse Dashboard interface.`;
- }
}
From cdac8fbc938462eb9e5731f4cb9667a2d6d63326 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 15:39:28 +0200
Subject: [PATCH 27/42] debug
---
Parse-Dashboard/app.js | 229 ----------------------------------------
src/lib/AgentService.js | 10 --
2 files changed, 239 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index e78bd202bd..de7f3780dd 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -491,15 +491,6 @@ module.exports = function(config, options) {
async function executeDatabaseFunction(functionName, args, appContext, operationLog = []) {
const Parse = require('parse/node');
- // Debug logging for all Parse Server requests
- console.log('Parse Server Request:', {
- operation: functionName,
- args: args,
- appId: appContext.appId,
- serverURL: appContext.serverURL,
- timestamp: new Date().toISOString()
- });
-
// Initialize Parse for this app context
Parse.initialize(appContext.appId, undefined, appContext.masterKey);
Parse.serverURL = appContext.serverURL;
@@ -558,21 +549,6 @@ module.exports = function(config, options) {
timestamp: new Date().toISOString()
};
- // Log Parse SDK operation details
- console.log('Parse SDK Operation:', {
- method: 'Query.find()',
- className,
- constraints: where,
- limit,
- skip,
- order,
- include,
- select,
- resultCount: results.length,
- useMasterKey: true
- });
-
- console.log('Parse Server Response:', operationSummary);
operationLog.push(operationSummary);
return resultData;
}
@@ -597,30 +573,9 @@ module.exports = function(config, options) {
object.set(key, objectData[key]);
});
- // Log Parse SDK operation details
- console.log('Parse SDK Operation:', {
- method: 'ParseObject.save()',
- className,
- data: objectData,
- useMasterKey: true
- });
-
const result = await object.save(null, { useMasterKey: true });
const resultData = result.toJSON();
- console.log('Parse SDK Result:', {
- method: 'ParseObject.save()',
- className,
- objectId: resultData.objectId,
- createdAt: resultData.createdAt
- });
-
- console.log('Parse Server Response:', {
- operation: 'createObject',
- className,
- objectId: resultData.objectId,
- timestamp: new Date().toISOString()
- });
return resultData;
}
@@ -632,53 +587,16 @@ module.exports = function(config, options) {
throw new Error(`Updating objects requires user confirmation. The AI should ask for permission before updating object ${objectId} in the ${className} class.`);
}
- // Log Parse SDK operation details for getting the object
- console.log('Parse SDK Operation:', {
- method: 'Query.get()',
- className,
- objectId,
- useMasterKey: true
- });
-
const query = new Parse.Query(className);
const object = await query.get(objectId, { useMasterKey: true });
- console.log('Parse SDK Result:', {
- method: 'Query.get()',
- className,
- objectId,
- found: true
- });
-
Object.keys(objectData).forEach(key => {
object.set(key, objectData[key]);
});
- // Log Parse SDK operation details for saving the updated object
- console.log('Parse SDK Operation:', {
- method: 'ParseObject.save() [update]',
- className,
- objectId,
- updateData: objectData,
- useMasterKey: true
- });
-
const result = await object.save(null, { useMasterKey: true });
const resultData = result.toJSON();
- console.log('Parse SDK Result:', {
- method: 'ParseObject.save() [update]',
- className,
- objectId,
- updatedAt: resultData.updatedAt
- });
-
- console.log('Parse Server Response:', {
- operation: 'updateObject',
- className,
- objectId,
- timestamp: new Date().toISOString()
- });
return resultData;
}
@@ -690,48 +608,12 @@ module.exports = function(config, options) {
throw new Error(`Deleting objects requires user confirmation. The AI should ask for permission before permanently deleting object ${objectId} from the ${className} class.`);
}
- // Log Parse SDK operation details for getting the object
- console.log('Parse SDK Operation:', {
- method: 'Query.get()',
- className,
- objectId,
- useMasterKey: true
- });
-
const query = new Parse.Query(className);
const object = await query.get(objectId, { useMasterKey: true });
- console.log('Parse SDK Result:', {
- method: 'Query.get()',
- className,
- objectId,
- found: true
- });
-
- // Log Parse SDK operation details for deleting the object
- console.log('Parse SDK Operation:', {
- method: 'ParseObject.destroy()',
- className,
- objectId,
- useMasterKey: true
- });
-
await object.destroy({ useMasterKey: true });
- console.log('Parse SDK Result:', {
- method: 'ParseObject.destroy()',
- className,
- objectId,
- deleted: true
- });
-
const result = { success: true, objectId };
- console.log('Parse Server Response:', {
- operation: 'deleteObject',
- className,
- objectId,
- timestamp: new Date().toISOString()
- });
return result;
}
@@ -739,45 +621,9 @@ module.exports = function(config, options) {
const { className } = args;
let result;
if (className) {
- // Log Parse SDK operation details for getting a specific schema
- console.log('Parse SDK Operation:', {
- method: 'Parse.Schema.get()',
- className
- });
-
result = await new Parse.Schema(className).get();
-
- console.log('Parse SDK Result:', {
- method: 'Parse.Schema.get()',
- className,
- fields: Object.keys(result.fields || {}).length
- });
-
- console.log('Parse Server Response:', {
- operation: 'getSchema',
- className,
- timestamp: new Date().toISOString()
- });
} else {
- // Log Parse SDK operation details for getting all schemas
- console.log('Parse SDK Operation:', {
- method: 'Parse.Schema.all()'
- });
-
result = await Parse.Schema.all();
-
- console.log('Parse SDK Result:', {
- method: 'Parse.Schema.all()',
- schemaCount: result.length,
- classNames: result.map(schema => schema.className)
- });
-
- console.log('Parse Server Response:', {
- operation: 'getSchema',
- action: 'getAllSchemas',
- schemaCount: result.length,
- timestamp: new Date().toISOString()
- });
}
return result;
}
@@ -809,29 +655,9 @@ module.exports = function(config, options) {
}
});
- // Log Parse SDK operation details
- console.log('Parse SDK Operation:', {
- method: 'Query.count()',
- className,
- constraints: where,
- useMasterKey: true
- });
-
const count = await query.count({ useMasterKey: true });
- console.log('Parse SDK Result:', {
- method: 'Query.count()',
- className,
- count
- });
-
const result = { count };
- console.log('Parse Server Response:', {
- operation: 'countObjects',
- className,
- count,
- timestamp: new Date().toISOString()
- });
return result;
}
@@ -843,14 +669,6 @@ module.exports = function(config, options) {
throw new Error(`Creating classes requires user confirmation. The AI should ask for permission before creating the ${className} class.`);
}
- // Log Parse SDK operation details
- console.log('Parse SDK Operation:', {
- method: 'new Parse.Schema().save()',
- className,
- fields,
- useMasterKey: true
- });
-
const schema = new Parse.Schema(className);
// Add fields to the schema
@@ -890,19 +708,7 @@ module.exports = function(config, options) {
const result = await schema.save();
- console.log('Parse SDK Result:', {
- method: 'new Parse.Schema().save()',
- className,
- savedSchema: result.toJSON()
- });
-
const resultData = { success: true, className, schema: result };
- console.log('Parse Server Response:', {
- operation: 'createClass',
- className,
- fieldsCount: Object.keys(fields).length,
- timestamp: new Date().toISOString()
- });
return resultData;
}
@@ -1077,22 +883,6 @@ You have direct access to the Parse database through function calls, so you can
const functionName = toolCall.function.name;
const functionArgs = JSON.parse(toolCall.function.arguments);
- // Detailed logging to debug function call parameters
- console.log('=== FUNCTION CALL DEBUG ===');
- console.log('Function Name:', functionName);
- console.log('Raw Arguments String:', toolCall.function.arguments);
- console.log('Parsed Arguments:', functionArgs);
- console.log('Arguments Keys:', Object.keys(functionArgs));
- if (functionName === 'createObject') {
- console.log('createObject Analysis:');
- console.log('- className:', functionArgs.className);
- console.log('- objectData:', functionArgs.objectData);
- console.log('- objectData type:', typeof functionArgs.objectData);
- console.log('- objectData keys:', functionArgs.objectData ? Object.keys(functionArgs.objectData) : 'OBJECTDATA IS MISSING');
- console.log('- confirmed:', functionArgs.confirmed);
- }
- console.log('=== END DEBUG ===');
-
console.log('Executing database function:', {
functionName,
args: functionArgs,
@@ -1101,19 +891,6 @@ You have direct access to the Parse database through function calls, so you can
timestamp: new Date().toISOString()
});
- // Special validation for createObject function calls
- if (functionName === 'createObject' && (!functionArgs.objectData || typeof functionArgs.objectData !== 'object' || Object.keys(functionArgs.objectData).length === 0)) {
- const missingDataError = {
- error: 'CRITICAL ERROR: createObject function call is missing the required objectData parameter. You must provide the objectData parameter with actual field values. Example: {"className": "TestCars", "objectData": {"model": "Honda Civic", "year": 2023, "brand": "Honda"}, "confirmed": true}. Please retry the function call with the objectData parameter containing the object fields and values you want to create.'
- };
- toolResponses.push({
- tool_call_id: toolCall.id,
- role: 'tool',
- content: JSON.stringify(missingDataError)
- });
- continue; // Skip to next tool call
- }
-
// Execute the database function
const result = await executeDatabaseFunction(functionName, functionArgs, appContext, operationLog);
@@ -1123,12 +900,6 @@ You have direct access to the Parse database through function calls, so you can
content: result ? JSON.stringify(result) : JSON.stringify({ success: true })
});
} catch (error) {
- console.error('Parse operation error:', {
- functionName: toolCall.function.name,
- args: toolCall.function.arguments,
- error: error.message,
- stack: error.stack
- });
toolResponses.push({
tool_call_id: toolCall.id,
role: 'tool',
diff --git a/src/lib/AgentService.js b/src/lib/AgentService.js
index fde85f3524..c54fb72fe7 100644
--- a/src/lib/AgentService.js
+++ b/src/lib/AgentService.js
@@ -48,19 +48,9 @@ export default class AgentService {
const response = await post(`/apps/${appSlug}/agent`, requestBody);
if (response.error) {
- console.error('Agent API Error Response:', response.error);
throw new Error(response.error);
}
- // Log Parse Server operations if available
- if (response.debug?.operations?.length > 0) {
- console.group('Parse Server Operations:');
- response.debug.operations.forEach((op, index) => {
- console.log(`${index + 1}. ${op.operation}:`, op);
- });
- console.groupEnd();
- }
-
return {
response: response.response,
conversationId: response.conversationId
From 94ccf7681ff9d0e185e5e78462708d7866d865f8 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 16:01:39 +0200
Subject: [PATCH 28/42] instr
---
Parse-Dashboard/app.js | 9 +++++++++
src/lib/AgentService.js | 4 ----
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index de7f3780dd..b70190611e 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -786,6 +786,15 @@ When working with the database:
- Always consider security and use appropriate query constraints
- Provide clear explanations of what database operations you're performing
- If any database function returns an error, you MUST include the full error message in your response to the user. Never hide error details or give vague responses like "there was an issue" - always show the specific error message.
+- IMPORTANT: When creating objects, you MUST provide the 'objectData' parameter with actual field values. Never call createObject with only className and confirmed - always include the objectData object with the fields and values to be saved.
+- IMPORTANT: When updating objects, you MUST provide the 'objectData' parameter with the fields you want to update. Include the objectData object with field names and new values.
+
+CRITICAL RULE FOR createObject FUNCTION:
+- The createObject function REQUIRES THREE parameters: className, objectData, and confirmed
+- The 'objectData' parameter MUST contain the actual field values as a JSON object
+- NEVER call createObject with only className and confirmed - this will fail
+- Example: createObject({className: 'TestCars', objectData: {model: 'Honda Civic', year: 2023, brand: 'Honda'}, confirmed: true})
+- The objectData object should contain all the fields and their values that you want to save
When responding:
- Be concise and helpful
diff --git a/src/lib/AgentService.js b/src/lib/AgentService.js
index c54fb72fe7..ed9f76d6a2 100644
--- a/src/lib/AgentService.js
+++ b/src/lib/AgentService.js
@@ -103,10 +103,6 @@ export default class AgentService {
throw new Error('API key is required in model configuration');
}
- if (apiKey === 'xxxxx' || apiKey.includes('xxx')) {
- throw new Error('Please replace the placeholder API key with your actual API key');
- }
-
return true;
}
From 335b29c71f087f735b41aa6cfaeec6915b060ebe Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 16:10:43 +0200
Subject: [PATCH 29/42] chat tracking
---
src/dashboard/Data/Agent/Agent.react.js | 95 ++++++++++++++++++++++++-
1 file changed, 93 insertions(+), 2 deletions(-)
diff --git a/src/dashboard/Data/Agent/Agent.react.js b/src/dashboard/Data/Agent/Agent.react.js
index 272b76c6fb..704746d8d2 100644
--- a/src/dashboard/Data/Agent/Agent.react.js
+++ b/src/dashboard/Data/Agent/Agent.react.js
@@ -17,9 +17,12 @@ import Toolbar from 'components/Toolbar/Toolbar.react';
import AgentService from 'lib/AgentService';
import styles from './Agent.scss';
import { withRouter } from 'lib/withRouter';
+import { CurrentApp } from 'context/currentApp';
@withRouter
class Agent extends DashboardView {
+ static contextType = CurrentApp;
+
constructor(props) {
super(props);
this.section = 'Core';
@@ -30,7 +33,7 @@ class Agent extends DashboardView {
inputValue: '',
isLoading: false,
selectedModel: this.getStoredSelectedModel(),
- conversationId: null, // Add conversation tracking
+ conversationId: null,
};
this.browserMenuRef = React.createRef();
@@ -44,6 +47,50 @@ class Agent extends DashboardView {
return stored;
}
+ getStoredChatState() {
+ try {
+ const appSlug = this.context ? this.context.slug : null;
+ if (!appSlug) return null;
+
+ const stored = localStorage.getItem(`agentChat_${appSlug}`);
+ if (!stored) return null;
+
+ const parsedState = JSON.parse(stored);
+
+ // Validate the structure
+ if (!parsedState || typeof parsedState !== 'object') return null;
+ if (!Array.isArray(parsedState.messages)) return null;
+
+ // Check if the data is too old (optional: 24 hours expiry)
+ const ONE_DAY = 24 * 60 * 60 * 1000;
+ if (parsedState.timestamp && (Date.now() - parsedState.timestamp > ONE_DAY)) {
+ localStorage.removeItem(`agentChat_${appSlug}`);
+ return null;
+ }
+
+ return parsedState;
+ } catch (error) {
+ console.warn('Failed to parse stored chat state:', error);
+ return null;
+ }
+ }
+
+ saveChatState() {
+ try {
+ const appSlug = this.context ? this.context.slug : null;
+ if (!appSlug) return;
+
+ const chatState = {
+ messages: this.state.messages,
+ conversationId: this.state.conversationId,
+ timestamp: Date.now()
+ };
+ localStorage.setItem(`agentChat_${appSlug}`, JSON.stringify(chatState));
+ } catch (error) {
+ console.warn('Failed to save chat state:', error);
+ }
+ }
+
componentDidMount() {
// Fix the routing issue by ensuring this.state.route is set to 'agent'
if (this.state.route !== 'agent') {
@@ -51,6 +98,30 @@ class Agent extends DashboardView {
}
this.setDefaultModel();
+
+ // Load saved chat state after component mounts when context is available
+ this.loadSavedChatState();
+ }
+
+ loadSavedChatState() {
+ const savedChatState = this.getStoredChatState();
+ if (savedChatState && savedChatState.messages && savedChatState.messages.length > 0) {
+ // Convert timestamp strings back to Date objects
+ const messagesWithDateTimestamps = savedChatState.messages.map(message => ({
+ ...message,
+ timestamp: new Date(message.timestamp)
+ }));
+
+ this.setState({
+ messages: messagesWithDateTimestamps,
+ conversationId: savedChatState.conversationId || null,
+ });
+ }
+ }
+
+ componentWillUnmount() {
+ // Save chat state when component unmounts (navigation away)
+ this.saveChatState();
}
componentDidUpdate(prevProps, prevState) {
@@ -59,6 +130,12 @@ class Agent extends DashboardView {
this.setDefaultModel();
}
+ // Save chat state when messages change
+ if (prevState.messages.length !== this.state.messages.length ||
+ prevState.conversationId !== this.state.conversationId) {
+ this.saveChatState();
+ }
+
// Auto-scroll to bottom when new messages are added or loading state changes
if (prevState.messages.length !== this.state.messages.length ||
prevState.isLoading !== this.state.isLoading) {
@@ -103,6 +180,17 @@ class Agent extends DashboardView {
messages: [],
conversationId: null, // Reset conversation to start fresh
});
+
+ // Clear saved chat state from localStorage
+ try {
+ const appSlug = this.context ? this.context.slug : null;
+ if (appSlug) {
+ localStorage.removeItem(`agentChat_${appSlug}`);
+ }
+ } catch (error) {
+ console.warn('Failed to clear saved chat state:', error);
+ }
+
// Close the menu by simulating an external click
if (this.browserMenuRef.current) {
this.browserMenuRef.current.setState({ open: false });
@@ -299,7 +387,10 @@ class Agent extends DashboardView {
{message.type === 'agent' ? this.formatMessageContent(message.content) : message.content}
- {message.timestamp.toLocaleTimeString()}
+ {message.timestamp instanceof Date ?
+ message.timestamp.toLocaleTimeString() :
+ new Date(message.timestamp).toLocaleTimeString()
+ }
))}
From 3164fe75615529515e266931a7616e62fabb9d27 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 16:26:21 +0200
Subject: [PATCH 30/42] sidebar icon
---
src/components/Sidebar/Sidebar.react.js | 3 ++-
src/components/Sidebar/Sidebar.scss | 11 ++++++++-
.../Sidebar/SidebarSubItem.react.js | 5 +++-
src/dashboard/DashboardView.react.js | 3 ++-
src/icons/sparkle-solid.svg | 23 +++++++++++++++++++
src/icons/sparkle.svg | 5 ----
6 files changed, 41 insertions(+), 9 deletions(-)
create mode 100644 src/icons/sparkle-solid.svg
delete mode 100644 src/icons/sparkle.svg
diff --git a/src/components/Sidebar/Sidebar.react.js b/src/components/Sidebar/Sidebar.react.js
index 69dcecd78a..d504f2934a 100644
--- a/src/components/Sidebar/Sidebar.react.js
+++ b/src/components/Sidebar/Sidebar.react.js
@@ -82,7 +82,7 @@ const Sidebar = ({
}
return (
- {subsections.map(({ name, link }) => {
+ {subsections.map(({ name, link, icon }) => {
const active = subsection === name;
return (
{active ? children : null}
diff --git a/src/components/Sidebar/Sidebar.scss b/src/components/Sidebar/Sidebar.scss
index a051af435f..c73033aa73 100644
--- a/src/components/Sidebar/Sidebar.scss
+++ b/src/components/Sidebar/Sidebar.scss
@@ -271,17 +271,26 @@ $footerHeight: 36px;
font-size: 16px;
font-weight: 700;
color: white;
+ display: flex;
+ align-items: center;
}
a.subitem {
color: #8fb9cf;
font-weight: 400;
- display: inline-block;
+ display: flex;
+ align-items: center;
width: 100%;
&:hover {
color: white;
}
+
+ svg {
+ &:hover {
+ fill: white;
+ }
+ }
}
.action {
diff --git a/src/components/Sidebar/SidebarSubItem.react.js b/src/components/Sidebar/SidebarSubItem.react.js
index 3017466b60..214889a5ec 100644
--- a/src/components/Sidebar/SidebarSubItem.react.js
+++ b/src/components/Sidebar/SidebarSubItem.react.js
@@ -8,13 +8,15 @@
import { Link } from 'react-router-dom';
import React from 'react';
import styles from 'components/Sidebar/Sidebar.scss';
+import Icon from 'components/Icon/Icon.react';
-const SidebarSubItem = ({ active, name, action, link, children }) => {
+const SidebarSubItem = ({ active, name, action, link, children, icon }) => {
if (active) {
return (
{name}
+ {icon && }
{action ? action.renderButton() : null}
{children}
@@ -26,6 +28,7 @@ const SidebarSubItem = ({ active, name, action, link, children }) => {
{name}
+ {icon && }
);
diff --git a/src/dashboard/DashboardView.react.js b/src/dashboard/DashboardView.react.js
index 9f768060be..f438ca3a75 100644
--- a/src/dashboard/DashboardView.react.js
+++ b/src/dashboard/DashboardView.react.js
@@ -83,8 +83,9 @@ export default class DashboardView extends React.Component {
});
coreSubsections.push({
- name: '✨ Agent',
+ name: 'Agent',
link: '/agent',
+ icon: 'sparkle-solid',
});
//webhooks requires removal of heroku link code, then it should work.
diff --git a/src/icons/sparkle-solid.svg b/src/icons/sparkle-solid.svg
new file mode 100644
index 0000000000..d79e26d6a2
--- /dev/null
+++ b/src/icons/sparkle-solid.svg
@@ -0,0 +1,23 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/icons/sparkle.svg b/src/icons/sparkle.svg
deleted file mode 100644
index 17b72ebcf4..0000000000
--- a/src/icons/sparkle.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
From ecf639e6875df88a6543f5b7ed7edfda9c30482d Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 16:43:41 +0200
Subject: [PATCH 31/42] Update README.md
---
README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 57 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index b365badd7a..e60ce8e46c 100644
--- a/README.md
+++ b/README.md
@@ -74,10 +74,13 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [Panel Item](#panel-item)
- [Prefetching](#prefetching)
- [Freeze Columns](#freeze-columns)
- - [Browse as User](#browse-as-user)
- - [Change Pointer Key](#change-pointer-key)
- - [Limitations](#limitations)
- - [CSV Export](#csv-export)
+ - [Browse as User](#browse-as-user)
+ - [Change Pointer Key](#change-pointer-key)
+ - [Limitations](#limitations)
+ - [CSV Export](#csv-export)
+ - [AI Agent](#ai-agent)
+ - [Configuration](#configuration)
+ - [Providers](#providers)
- [Views](#views)
- [Data Sources](#data-sources)
- [Aggregation Pipeline](#aggregation-pipeline)
@@ -1231,7 +1234,7 @@ Prefetching is particularly useful when navigating through lists of objects. To
Right-click on a table column header to freeze columns from the left up to the clicked column in the data browser. When scrolling horizontally, the frozen columns remain visible while the other columns scroll underneath.
-## Browse as User
+### Browse as User
▶️ *Core > Browser > Browse*
@@ -1239,20 +1242,21 @@ This feature allows you to use the data browser as another user, respecting that
> ⚠️ Logging in as another user will trigger the same Cloud Triggers as if the user logged in themselves using any other login method. Logging in as another user requires to enter that user's password.
-## Change Pointer Key
+### Change Pointer Key
▶️ *Core > Browser > Edit > Change pointer key*
This feature allows you to change how a pointer is represented in the browser. By default, a pointer is represented by the `objectId` of the linked object. You can change this to any other column of the object class. For example, if class `Installation` has a field that contains a pointer to class `User`, the pointer will show the `objectId` of the user by default. You can change this to display the field `email` of the user, so that a pointer displays the user's email address instead.
-### Limitations
+#### Limitations
- This does not work for an array of pointers; the pointer will always display the `objectId`.
- System columns like `createdAt`, `updatedAt`, `ACL` cannot be set as pointer key.
- This feature uses browser storage; switching to a different browser resets the pointer key to `objectId`.
> ⚠️ For each custom pointer key in each row, a server request is triggered to resolve the custom pointer key. For example, if the browser shows a class with 50 rows and each row contains 3 custom pointer keys, a total of 150 separate server requests are triggered.
-## CSV Export
+
+### CSV Export
▶️ *Core > Browser > Export*
@@ -1260,6 +1264,51 @@ This feature will take either selected rows or all rows of an individual class a
> ⚠️ There is currently a 10,000 row limit when exporting all data. If more than 10,000 rows are present in the class, the CSV file will only contain 10,000 rows.
+## AI Agent
+
+The Parse Dashboard includes an AI agent that can help manage your Parse Server data through natural language commands. The agent can perform operations like creating classes, adding data, querying records, and more.
+
+> [!Caution]
+> The AI agent has full access to your database using the master key. It can read, modify, and delete any data. This feature is highly recommended for development environments only. Always back up important data before using the AI agent.
+
+### Configuration
+
+To configure the AI agent for your dashboard, you need to add the `agent` configuration to your Parse Dashboard config:
+
+```json
+{
+ "apps": [
+ // ...
+ ],
+ "agent": {
+ "models": [
+ {
+ "name": "ChatGPT 4.1",
+ "provider": "openai",
+ "model": "gpt-4.1",
+ "apiKey": "YOUR_OPENAI_API_KEY"
+ },
+ ]
+ }
+}
+```
+
+| Parameter | Type | Required | Description |
+|-----------------------------|--------|----------|--------------------------------------------------------------------------------|
+| `agent` | Object | Yes | The AI agent configuration object. |
+| `agent.models` | Array | Yes | Array of AI model configurations available to the agent. |
+| `agent.models[*].name` | String | Yes | The display name for the model (e.g., `ChatGPT 4.1`). |
+| `agent.models[*].provider` | String | Yes | The AI provider identifier (e.g., "openai"). |
+| `agent.models[*].model` | String | Yes | The specific model name from the provider (e.g., `gpt-4.1`). |
+| `agent.models[*].apiKey` | String | Yes | The API key for authenticating with the AI provider. |
+
+The agent will use the configured models to process natural language commands and perform database operations using the master key from your app configuration.
+
+### Providers
+
+> [!Note]
+> Currently, only OpenAI models are supported. Support for additional providers may be added in future releases.
+
## Views
▶️ *Core > Views*
From 7c75969d9c72856971c7f9cd886b6559d56da3ca Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 16:59:13 +0200
Subject: [PATCH 32/42] Update README.md
---
README.md | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/README.md b/README.md
index e60ce8e46c..0b3250d33d 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https
- [AI Agent](#ai-agent)
- [Configuration](#configuration)
- [Providers](#providers)
+ - [OpenAI](#openai)
- [Views](#views)
- [Data Sources](#data-sources)
- [Aggregation Pipeline](#aggregation-pipeline)
@@ -1309,6 +1310,38 @@ The agent will use the configured models to process natural language commands an
> [!Note]
> Currently, only OpenAI models are supported. Support for additional providers may be added in future releases.
+#### OpenAI
+
+To get an OpenAI API key for use with the AI agent:
+
+1. **Create an OpenAI account**: Visit [platform.openai.com](https://platform.openai.com) and sign up for an account if you don't already have one.
+
+2. **Access the API section**: Once logged in, navigate to the API section of your OpenAI dashboard.
+
+3. **Create a new project**:
+ - Go to the "Projects" section
+ - Click "Create project"
+ - Name your project "Parse-Dashboard" (or any descriptive name)
+ - Complete the project setup
+
+4. **Configure model access**:
+ - In your project, navigate to "Limits > Model Usage"
+ - Select the AI models you want to use (e.g., `gpt-4`, `gpt-3.5-turbo`)
+ - These model names will be used as the `agent.models[*].model` parameter in your dashboard configuration
+
+5. **Generate an API key**:
+ - Go to the "API Keys" page in your project settings
+ - Click "Create new secret key"
+ - Give your key a descriptive name (e.g., "Parse Dashboard Agent")
+ - Copy the generated API key immediately (you won't be able to see it again)
+
+6. **Set up billing**: Make sure you have a valid payment method added to your OpenAI account, as API usage incurs charges.
+
+7. **Configure the dashboard**: Add the API key to your Parse Dashboard configuration as shown in the example above.
+
+> [!Important]
+> Keep your API key secure and never commit it to version control. Consider using environment variables or secure configuration management for production deployments.
+
## Views
▶️ *Core > Views*
From 04b8ddd1a581b13ae21a4f4f47ddba54c055edf5 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 16:59:18 +0200
Subject: [PATCH 33/42] warning
---
src/dashboard/Data/Agent/Agent.react.js | 50 +++++++++++++++++++------
src/dashboard/Data/Agent/Agent.scss | 30 +++++++++++++++
2 files changed, 68 insertions(+), 12 deletions(-)
diff --git a/src/dashboard/Data/Agent/Agent.react.js b/src/dashboard/Data/Agent/Agent.react.js
index 704746d8d2..d05df39da0 100644
--- a/src/dashboard/Data/Agent/Agent.react.js
+++ b/src/dashboard/Data/Agent/Agent.react.js
@@ -211,7 +211,7 @@ class Agent extends DashboardView {
handleSubmit = async (event) => {
event.preventDefault();
- const { inputValue, selectedModel } = this.state;
+ const { inputValue, selectedModel, messages } = this.state;
const { agentConfig } = this.props;
if (inputValue.trim() === '') {
@@ -238,6 +238,20 @@ class Agent extends DashboardView {
return;
}
+ // Add warning message if this is the first message in the conversation
+ const isFirstMessage = messages.length === 0;
+ let messagesToAdd = [];
+
+ if (isFirstMessage) {
+ const warningMessage = {
+ id: Date.now() - 1,
+ type: 'warning',
+ content: 'The AI agent has full access to your database using the master key. It can read, modify, and delete any data. This feature is highly recommended for development environments only. Always back up important data before using the AI agent.',
+ timestamp: new Date(),
+ };
+ messagesToAdd.push(warningMessage);
+ }
+
// Add user message
const userMessage = {
id: Date.now(),
@@ -245,9 +259,10 @@ class Agent extends DashboardView {
content: inputValue.trim(),
timestamp: new Date(),
};
+ messagesToAdd.push(userMessage);
this.setState(prevState => ({
- messages: [...prevState.messages, userMessage],
+ messages: [...prevState.messages, ...messagesToAdd],
inputValue: '',
isLoading: true,
}));
@@ -381,17 +396,28 @@ class Agent extends DashboardView {
{messages.map((message) => (
-
- {message.type === 'agent' ? this.formatMessageContent(message.content) : message.content}
-
-
- {message.timestamp instanceof Date ?
- message.timestamp.toLocaleTimeString() :
- new Date(message.timestamp).toLocaleTimeString()
- }
-
+ {message.type === 'warning' ? (
+ <>
+
+
+ {message.content}
+
+ >
+ ) : (
+ <>
+
+ {message.type === 'agent' ? this.formatMessageContent(message.content) : message.content}
+
+
+ {message.timestamp instanceof Date ?
+ message.timestamp.toLocaleTimeString() :
+ new Date(message.timestamp).toLocaleTimeString()
+ }
+
+ >
+ )}
))}
{isLoading && (
diff --git a/src/dashboard/Data/Agent/Agent.scss b/src/dashboard/Data/Agent/Agent.scss
index f1a577451e..fe55407c3c 100644
--- a/src/dashboard/Data/Agent/Agent.scss
+++ b/src/dashboard/Data/Agent/Agent.scss
@@ -341,3 +341,33 @@ body:global(.expanded) {
transform: translateY(0);
}
}
+
+.warningMessage {
+ background-color: #fff3cd;
+ border: 1px solid #ffeaa7;
+ color: #856404;
+ padding: 12px 16px;
+ border-radius: 8px;
+ margin-bottom: 16px;
+ font-size: 14px;
+ line-height: 1.4;
+ position: relative;
+ max-width: 100%;
+ align-self: stretch;
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+
+ .warningIcon {
+ flex-shrink: 0;
+ margin-top: 2px;
+ }
+
+ .warningContent {
+ flex: 1;
+ }
+
+ strong {
+ font-weight: 600;
+ }
+}
From 39fb3aa4acbcc31f800516ff922dcfb83ead3635 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 16:59:46 +0200
Subject: [PATCH 34/42] lint
---
src/dashboard/Data/Agent/Agent.react.js | 36 ++++++++++++-------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/src/dashboard/Data/Agent/Agent.react.js b/src/dashboard/Data/Agent/Agent.react.js
index d05df39da0..9d0fb8448b 100644
--- a/src/dashboard/Data/Agent/Agent.react.js
+++ b/src/dashboard/Data/Agent/Agent.react.js
@@ -22,7 +22,7 @@ import { CurrentApp } from 'context/currentApp';
@withRouter
class Agent extends DashboardView {
static contextType = CurrentApp;
-
+
constructor(props) {
super(props);
this.section = 'Core';
@@ -50,24 +50,24 @@ class Agent extends DashboardView {
getStoredChatState() {
try {
const appSlug = this.context ? this.context.slug : null;
- if (!appSlug) return null;
-
+ if (!appSlug) {return null;}
+
const stored = localStorage.getItem(`agentChat_${appSlug}`);
- if (!stored) return null;
-
+ if (!stored) {return null;}
+
const parsedState = JSON.parse(stored);
-
+
// Validate the structure
- if (!parsedState || typeof parsedState !== 'object') return null;
- if (!Array.isArray(parsedState.messages)) return null;
-
+ if (!parsedState || typeof parsedState !== 'object') {return null;}
+ if (!Array.isArray(parsedState.messages)) {return null;}
+
// Check if the data is too old (optional: 24 hours expiry)
const ONE_DAY = 24 * 60 * 60 * 1000;
if (parsedState.timestamp && (Date.now() - parsedState.timestamp > ONE_DAY)) {
localStorage.removeItem(`agentChat_${appSlug}`);
return null;
}
-
+
return parsedState;
} catch (error) {
console.warn('Failed to parse stored chat state:', error);
@@ -78,7 +78,7 @@ class Agent extends DashboardView {
saveChatState() {
try {
const appSlug = this.context ? this.context.slug : null;
- if (!appSlug) return;
+ if (!appSlug) {return;}
const chatState = {
messages: this.state.messages,
@@ -98,7 +98,7 @@ class Agent extends DashboardView {
}
this.setDefaultModel();
-
+
// Load saved chat state after component mounts when context is available
this.loadSavedChatState();
}
@@ -111,7 +111,7 @@ class Agent extends DashboardView {
...message,
timestamp: new Date(message.timestamp)
}));
-
+
this.setState({
messages: messagesWithDateTimestamps,
conversationId: savedChatState.conversationId || null,
@@ -180,7 +180,7 @@ class Agent extends DashboardView {
messages: [],
conversationId: null, // Reset conversation to start fresh
});
-
+
// Clear saved chat state from localStorage
try {
const appSlug = this.context ? this.context.slug : null;
@@ -190,7 +190,7 @@ class Agent extends DashboardView {
} catch (error) {
console.warn('Failed to clear saved chat state:', error);
}
-
+
// Close the menu by simulating an external click
if (this.browserMenuRef.current) {
this.browserMenuRef.current.setState({ open: false });
@@ -240,7 +240,7 @@ class Agent extends DashboardView {
// Add warning message if this is the first message in the conversation
const isFirstMessage = messages.length === 0;
- let messagesToAdd = [];
+ const messagesToAdd = [];
if (isFirstMessage) {
const warningMessage = {
@@ -411,8 +411,8 @@ class Agent extends DashboardView {
{message.type === 'agent' ? this.formatMessageContent(message.content) : message.content}
- {message.timestamp instanceof Date ?
- message.timestamp.toLocaleTimeString() :
+ {message.timestamp instanceof Date ?
+ message.timestamp.toLocaleTimeString() :
new Date(message.timestamp).toLocaleTimeString()
}
From 3f5e34ac6d1df57bd0a65c04d7f316d72fa67b52 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 17:23:51 +0200
Subject: [PATCH 35/42] delete class
---
Parse-Dashboard/app.js | 60 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 57 insertions(+), 3 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index b70190611e..9cd38d5408 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -398,7 +398,7 @@ module.exports = function(config, options) {
type: 'function',
function: {
name: 'deleteObject',
- description: 'Delete an object from a Parse class/table. IMPORTANT: This is a destructive write operation that requires explicit user confirmation before execution. You must ask the user to confirm before calling this function.',
+ description: 'Delete a SINGLE OBJECT/ROW from a Parse class/table using its objectId. Use this when you want to delete one specific record/object, not the entire class. IMPORTANT: This is a destructive write operation that requires explicit user confirmation before execution. You must ask the user to confirm before calling this function.',
parameters: {
type: 'object',
properties: {
@@ -408,7 +408,7 @@ module.exports = function(config, options) {
},
objectId: {
type: 'string',
- description: 'The objectId of the object to delete'
+ description: 'The objectId of the specific object/record to delete'
},
confirmed: {
type: 'boolean',
@@ -482,6 +482,28 @@ module.exports = function(config, options) {
required: ['className', 'confirmed']
}
}
+ },
+ {
+ type: 'function',
+ function: {
+ name: 'deleteClass',
+ description: 'Delete an ENTIRE Parse class/table (the class itself) and ALL its data. Use this when the user wants to delete/remove the entire class/table, not individual objects. This completely removes the class schema and all objects within it. IMPORTANT: This is a highly destructive operation that permanently removes the entire class structure and all objects within it. Requires explicit user confirmation before execution.',
+ parameters: {
+ type: 'object',
+ properties: {
+ className: {
+ type: 'string',
+ description: 'The name of the Parse class/table to completely delete/remove'
+ },
+ confirmed: {
+ type: 'boolean',
+ description: 'Must be true to indicate user has explicitly confirmed this highly destructive operation',
+ default: false
+ }
+ },
+ required: ['className', 'confirmed']
+ }
+ }
}
];
@@ -712,6 +734,32 @@ module.exports = function(config, options) {
return resultData;
}
+ case 'deleteClass': {
+ const { className, confirmed } = args;
+
+ // Require explicit confirmation for class deletion - this is highly destructive
+ if (!confirmed) {
+ throw new Error(`Deleting classes requires user confirmation. The AI should ask for permission before permanently deleting the ${className} class and ALL its data.`);
+ }
+
+ // Check if the class exists first
+ try {
+ await new Parse.Schema(className).get();
+ } catch (error) {
+ if (error.code === 103) {
+ throw new Error(`Class "${className}" does not exist.`);
+ }
+ throw error;
+ }
+
+ // Delete the class and all its data
+ const schema = new Parse.Schema(className);
+ await schema.purge();
+
+ const resultData = { success: true, className, message: `Class "${className}" and all its data have been permanently deleted.` };
+ return resultData;
+ }
+
default:
throw new Error(`Unknown function: ${functionName}`);
}
@@ -757,9 +805,15 @@ You have access to database function tools that allow you to:
- Query classes/tables to retrieve objects (read-only, no confirmation needed)
- Create new objects in classes (REQUIRES USER CONFIRMATION)
- Update existing objects (REQUIRES USER CONFIRMATION)
-- Delete objects (REQUIRES USER CONFIRMATION)
+- Delete INDIVIDUAL objects by objectId (REQUIRES USER CONFIRMATION)
+- Delete ENTIRE classes/tables and all their data (REQUIRES USER CONFIRMATION)
- Get schema information for classes (read-only, no confirmation needed)
- Count objects that match certain criteria (read-only, no confirmation needed)
+- Create new empty classes/tables (REQUIRES USER CONFIRMATION)
+
+IMPORTANT: Choose the correct function based on what the user wants to delete:
+- Use 'deleteObject' when deleting a specific object/record by its objectId
+- Use 'deleteClass' when deleting an entire class/table (the class itself and all its data)
CRITICAL SECURITY RULE FOR WRITE OPERATIONS:
- ANY write operation (create, update, delete) MUST have explicit user confirmation through conversation
From dc6aa006366c15775f7611332f16fa8e69c0b557 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 17:36:26 +0200
Subject: [PATCH 36/42] missing master key
---
Parse-Dashboard/app.js | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index 9cd38d5408..1dbf6d34bf 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -643,9 +643,9 @@ module.exports = function(config, options) {
const { className } = args;
let result;
if (className) {
- result = await new Parse.Schema(className).get();
+ result = await new Parse.Schema(className).get({ useMasterKey: true });
} else {
- result = await Parse.Schema.all();
+ result = await Parse.Schema.all({ useMasterKey: true });
}
return result;
}
@@ -728,7 +728,7 @@ module.exports = function(config, options) {
}
});
- const result = await schema.save();
+ const result = await schema.save({ useMasterKey: true });
const resultData = { success: true, className, schema: result };
return resultData;
@@ -744,7 +744,7 @@ module.exports = function(config, options) {
// Check if the class exists first
try {
- await new Parse.Schema(className).get();
+ await new Parse.Schema(className).get({ useMasterKey: true });
} catch (error) {
if (error.code === 103) {
throw new Error(`Class "${className}" does not exist.`);
@@ -754,10 +754,19 @@ module.exports = function(config, options) {
// Delete the class and all its data
const schema = new Parse.Schema(className);
- await schema.purge();
-
- const resultData = { success: true, className, message: `Class "${className}" and all its data have been permanently deleted.` };
- return resultData;
+
+ try {
+ // First purge all objects from the class
+ await schema.purge({ useMasterKey: true });
+
+ // Then delete the class schema itself
+ await schema.delete({ useMasterKey: true });
+
+ const resultData = { success: true, className, message: `Class "${className}" and all its data have been permanently deleted.` };
+ return resultData;
+ } catch (deleteError) {
+ throw new Error(`Failed to delete class "${className}": ${deleteError.message}`);
+ }
}
default:
From feac640575ffce426bab248a09a86b9d99053863 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 17:40:04 +0200
Subject: [PATCH 37/42] Update app.js
---
Parse-Dashboard/app.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index 1dbf6d34bf..3a9d302976 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -50,8 +50,7 @@ function checkIfIconsExistForApps(apps, iconsFolder) {
if ('ENOENT' == err.code) {// file does not exist
console.warn('Icon with file name: ' + iconName + ' couldn\'t be found in icons folder!');
} else {
- console.log(
- 'An error occurd while checking for icons, please check permission!');
+ console.warn('An error occurred while checking for icons, please check permission!');
}
} else {
//every thing was ok so for example you can read it and send it to client
@@ -1021,14 +1020,14 @@ You have direct access to the Parse database through function calls, so you can
if (!followUpContent) {
console.warn('OpenAI returned null content in follow-up response, using fallback message');
}
- return followUpContent || 'Operation completed successfully.';
+ return followUpContent || 'Done.';
}
const content = responseMessage.content;
if (!content) {
console.warn('OpenAI returned null content in initial response, using fallback message');
}
- return content || 'Operation completed successfully.';
+ return content || 'Done.';
}
// Serve the app icons. Uses the optional `iconsFolder` parameter as
From 3fd8b50101c348c9ce01d99178cb78a5abf82314 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 18:08:01 +0200
Subject: [PATCH 38/42] permissions
---
Parse-Dashboard/app.js | 41 ++++++++++--
src/dashboard/Data/Agent/Agent.react.js | 89 ++++++++++++++++++++++++-
src/lib/AgentService.js | 8 ++-
3 files changed, 129 insertions(+), 9 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index 3a9d302976..0c33558bb9 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -196,7 +196,7 @@ module.exports = function(config, options) {
// Agent API endpoint for handling AI requests - scoped to specific app
app.post('/apps/:appId/agent', async (req, res) => {
try {
- const { message, modelName, conversationId } = req.body;
+ const { message, modelName, conversationId, permissions } = req.body;
const { appId } = req.params;
if (!message || typeof message !== 'string' || message.trim() === '') {
@@ -255,7 +255,7 @@ module.exports = function(config, options) {
const operationLog = [];
// Make request to OpenAI API with app context and conversation history
- const response = await makeOpenAIRequest(message, model, apiKey, app, conversationHistory, operationLog);
+ const response = await makeOpenAIRequest(message, model, apiKey, app, conversationHistory, operationLog, permissions);
// Update conversation history with user message and AI response
conversationHistory.push(
@@ -784,10 +784,39 @@ module.exports = function(config, options) {
}
}
+ /**
+ * Filter database tools based on permissions
+ */
+ function getFilteredDatabaseTools(permissions = {}) {
+ const disallowedOperations = [];
+
+ // Check which operations are not allowed
+ if (!permissions.deleteObject) {
+ disallowedOperations.push('deleteObject');
+ }
+ if (!permissions.deleteClass) {
+ disallowedOperations.push('deleteClass');
+ }
+ if (!permissions.updateObject) {
+ disallowedOperations.push('updateObject');
+ }
+ if (!permissions.createObject) {
+ disallowedOperations.push('createObject');
+ }
+ if (!permissions.createClass) {
+ disallowedOperations.push('createClass');
+ }
+
+ // Filter out disallowed operations
+ return databaseTools.filter(tool =>
+ !disallowedOperations.includes(tool.function.name)
+ );
+ }
+
/**
* Make a request to OpenAI API
*/
- async function makeOpenAIRequest(userMessage, model, apiKey, appContext = null, conversationHistory = [], operationLog = []) {
+ async function makeOpenAIRequest(userMessage, model, apiKey, appContext = null, conversationHistory = [], operationLog = [], permissions = {}) {
const fetch = (await import('node-fetch')).default;
const url = 'https://api.openai.com/v1/chat/completions';
@@ -897,12 +926,14 @@ You have direct access to the Parse database through function calls, so you can
content: userMessage
});
+ const filteredTools = getFilteredDatabaseTools(permissions);
+
const requestBody = {
model: model,
messages: messages,
temperature: 0.7,
max_tokens: 2000,
- tools: databaseTools,
+ tools: filteredTools,
tool_choice: 'auto',
stream: false
};
@@ -992,7 +1023,7 @@ You have direct access to the Parse database through function calls, so you can
messages: followUpMessages,
temperature: 0.7,
max_tokens: 2000,
- tools: databaseTools,
+ tools: filteredTools,
tool_choice: 'auto',
stream: false
};
diff --git a/src/dashboard/Data/Agent/Agent.react.js b/src/dashboard/Data/Agent/Agent.react.js
index 9d0fb8448b..071e13d16f 100644
--- a/src/dashboard/Data/Agent/Agent.react.js
+++ b/src/dashboard/Data/Agent/Agent.react.js
@@ -34,6 +34,9 @@ class Agent extends DashboardView {
isLoading: false,
selectedModel: this.getStoredSelectedModel(),
conversationId: null,
+ permissions: this.getStoredPermissions(),
+ // Force re-render key
+ permissionsKey: 0,
};
this.browserMenuRef = React.createRef();
@@ -47,6 +50,45 @@ class Agent extends DashboardView {
return stored;
}
+ getStoredPermissions() {
+ try {
+ const stored = localStorage.getItem('agentPermissions');
+ return stored ? JSON.parse(stored) : {
+ deleteObject: false,
+ deleteClass: false,
+ updateObject: false,
+ createObject: false,
+ createClass: false,
+ };
+ } catch (error) {
+ console.warn('Failed to parse stored permissions, using defaults:', error);
+ return {
+ deleteObject: false,
+ deleteClass: false,
+ updateObject: false,
+ createObject: false,
+ createClass: false,
+ };
+ }
+ }
+
+ setPermission = (operation, enabled) => {
+ this.setState(prevState => {
+ const newPermissions = {
+ ...prevState.permissions,
+ [operation]: enabled
+ };
+
+ // Save to localStorage immediately
+ localStorage.setItem('agentPermissions', JSON.stringify(newPermissions));
+
+ return {
+ permissions: newPermissions,
+ permissionsKey: prevState.permissionsKey + 1
+ };
+ });
+ }
+
getStoredChatState() {
try {
const appSlug = this.context ? this.context.slug : null;
@@ -246,7 +288,7 @@ class Agent extends DashboardView {
const warningMessage = {
id: Date.now() - 1,
type: 'warning',
- content: 'The AI agent has full access to your database using the master key. It can read, modify, and delete any data. This feature is highly recommended for development environments only. Always back up important data before using the AI agent.',
+ content: 'The AI agent has full access to your database using the master key. It can read, modify, and delete any data. This feature is highly recommended for development environments only. Always back up important data before using the AI agent. Use the permissions menu to restrict operations.',
timestamp: new Date(),
};
messagesToAdd.push(warningMessage);
@@ -282,7 +324,8 @@ class Agent extends DashboardView {
inputValue.trim(),
modelConfig,
appSlug,
- this.state.conversationId
+ this.state.conversationId,
+ this.state.permissions
);
const aiMessage = {
@@ -334,9 +377,17 @@ class Agent extends DashboardView {
renderToolbar() {
const { agentConfig } = this.props;
- const { selectedModel } = this.state;
+ const { selectedModel, permissions, permissionsKey } = this.state;
const models = agentConfig?.models || [];
+ const permissionOperations = [
+ { key: 'deleteObject', label: 'Delete Objects' },
+ { key: 'deleteClass', label: 'Delete Classes' },
+ { key: 'updateObject', label: 'Update Objects' },
+ { key: 'createObject', label: 'Create Objects' },
+ { key: 'createClass', label: 'Create Classes' },
+ ];
+
return (
{models.length > 0 && (
@@ -367,6 +418,38 @@ class Agent extends DashboardView {
))}
)}
+ {}}
+ >
+ {permissionOperations.map((operation) => (
+
} The AI's response and conversation ID
*/
- static async sendMessage(message, modelConfig, appSlug, conversationId = null) {
+ static async sendMessage(message, modelConfig, appSlug, conversationId = null, permissions = {}) {
if (!modelConfig) {
throw new Error('Model configuration is required');
}
@@ -45,6 +46,11 @@ export default class AgentService {
requestBody.conversationId = conversationId;
}
+ // Include permissions if provided
+ if (permissions) {
+ requestBody.permissions = permissions;
+ }
+
const response = await post(`/apps/${appSlug}/agent`, requestBody);
if (response.error) {
From 279ac1768ef6164958215012a49289bdcccb84e8 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 18:36:21 +0200
Subject: [PATCH 39/42] permissions
---
Parse-Dashboard/app.js | 52 ++++++++-----------------
src/dashboard/Data/Agent/Agent.react.js | 4 +-
2 files changed, 18 insertions(+), 38 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index 0c33558bb9..3a63d2461d 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -509,7 +509,20 @@ module.exports = function(config, options) {
/**
* Execute database function calls
*/
- async function executeDatabaseFunction(functionName, args, appContext, operationLog = []) {
+ async function executeDatabaseFunction(functionName, args, appContext, operationLog = [], permissions = {}) {
+ // Check permissions before executing write operations
+ const writeOperations = ['deleteObject', 'deleteClass', 'updateObject', 'createObject', 'createClass'];
+
+ if (writeOperations.includes(functionName)) {
+ // Handle both boolean and string values for permissions
+ const permissionValue = permissions && permissions[functionName];
+ const hasPermission = permissionValue === true || permissionValue === 'true';
+
+ if (!hasPermission) {
+ throw new Error(`Permission denied: The "${functionName}" operation is currently disabled in the permissions settings. Please enable this permission in the Parse Dashboard Permissions menu if you want to allow this operation.`);
+ }
+ }
+
const Parse = require('parse/node');
// Initialize Parse for this app context
@@ -784,35 +797,6 @@ module.exports = function(config, options) {
}
}
- /**
- * Filter database tools based on permissions
- */
- function getFilteredDatabaseTools(permissions = {}) {
- const disallowedOperations = [];
-
- // Check which operations are not allowed
- if (!permissions.deleteObject) {
- disallowedOperations.push('deleteObject');
- }
- if (!permissions.deleteClass) {
- disallowedOperations.push('deleteClass');
- }
- if (!permissions.updateObject) {
- disallowedOperations.push('updateObject');
- }
- if (!permissions.createObject) {
- disallowedOperations.push('createObject');
- }
- if (!permissions.createClass) {
- disallowedOperations.push('createClass');
- }
-
- // Filter out disallowed operations
- return databaseTools.filter(tool =>
- !disallowedOperations.includes(tool.function.name)
- );
- }
-
/**
* Make a request to OpenAI API
*/
@@ -926,14 +910,12 @@ You have direct access to the Parse database through function calls, so you can
content: userMessage
});
- const filteredTools = getFilteredDatabaseTools(permissions);
-
const requestBody = {
model: model,
messages: messages,
temperature: 0.7,
max_tokens: 2000,
- tools: filteredTools,
+ tools: databaseTools,
tool_choice: 'auto',
stream: false
};
@@ -994,7 +976,7 @@ You have direct access to the Parse database through function calls, so you can
});
// Execute the database function
- const result = await executeDatabaseFunction(functionName, functionArgs, appContext, operationLog);
+ const result = await executeDatabaseFunction(functionName, functionArgs, appContext, operationLog, permissions);
toolResponses.push({
tool_call_id: toolCall.id,
@@ -1023,7 +1005,7 @@ You have direct access to the Parse database through function calls, so you can
messages: followUpMessages,
temperature: 0.7,
max_tokens: 2000,
- tools: filteredTools,
+ tools: databaseTools,
tool_choice: 'auto',
stream: false
};
diff --git a/src/dashboard/Data/Agent/Agent.react.js b/src/dashboard/Data/Agent/Agent.react.js
index 071e13d16f..036e2b4ea0 100644
--- a/src/dashboard/Data/Agent/Agent.react.js
+++ b/src/dashboard/Data/Agent/Agent.react.js
@@ -442,9 +442,7 @@ class Agent extends DashboardView {
{operation.label}
}
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
+ onClick={() => {
this.setPermission(operation.key, !permissions[operation.key]);
}}
/>
From 744bd87308b1f0c0809e678a7559c0c783122873 Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 18:36:44 +0200
Subject: [PATCH 40/42] lint
---
Parse-Dashboard/app.js | 10 +++++-----
src/dashboard/Data/Agent/Agent.react.js | 4 ++--
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index 3a63d2461d..cf42817311 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -512,12 +512,12 @@ module.exports = function(config, options) {
async function executeDatabaseFunction(functionName, args, appContext, operationLog = [], permissions = {}) {
// Check permissions before executing write operations
const writeOperations = ['deleteObject', 'deleteClass', 'updateObject', 'createObject', 'createClass'];
-
+
if (writeOperations.includes(functionName)) {
// Handle both boolean and string values for permissions
const permissionValue = permissions && permissions[functionName];
const hasPermission = permissionValue === true || permissionValue === 'true';
-
+
if (!hasPermission) {
throw new Error(`Permission denied: The "${functionName}" operation is currently disabled in the permissions settings. Please enable this permission in the Parse Dashboard Permissions menu if you want to allow this operation.`);
}
@@ -766,14 +766,14 @@ module.exports = function(config, options) {
// Delete the class and all its data
const schema = new Parse.Schema(className);
-
+
try {
// First purge all objects from the class
await schema.purge({ useMasterKey: true });
-
+
// Then delete the class schema itself
await schema.delete({ useMasterKey: true });
-
+
const resultData = { success: true, className, message: `Class "${className}" and all its data have been permanently deleted.` };
return resultData;
} catch (deleteError) {
diff --git a/src/dashboard/Data/Agent/Agent.react.js b/src/dashboard/Data/Agent/Agent.react.js
index 036e2b4ea0..602e37f48e 100644
--- a/src/dashboard/Data/Agent/Agent.react.js
+++ b/src/dashboard/Data/Agent/Agent.react.js
@@ -78,10 +78,10 @@ class Agent extends DashboardView {
...prevState.permissions,
[operation]: enabled
};
-
+
// Save to localStorage immediately
localStorage.setItem('agentPermissions', JSON.stringify(newPermissions));
-
+
return {
permissions: newPermissions,
permissionsKey: prevState.permissionsKey + 1
From df0947299f35437439f8370b9076b419284d772f Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 18:39:46 +0200
Subject: [PATCH 41/42] optimize parse
---
Parse-Dashboard/app.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/Parse-Dashboard/app.js b/Parse-Dashboard/app.js
index cf42817311..9908e9239d 100644
--- a/Parse-Dashboard/app.js
+++ b/Parse-Dashboard/app.js
@@ -7,6 +7,7 @@ const Authentication = require('./Authentication.js');
const fs = require('fs');
const ConfigKeyCache = require('./configKeyCache.js');
const currentVersionFeatures = require('../package.json').parseDashboardFeatures;
+const Parse = require('parse/node');
let newFeaturesInLatestVersion = [];
@@ -523,9 +524,7 @@ module.exports = function(config, options) {
}
}
- const Parse = require('parse/node');
-
- // Initialize Parse for this app context
+ // Configure Parse for this app context
Parse.initialize(appContext.appId, undefined, appContext.masterKey);
Parse.serverURL = appContext.serverURL;
Parse.masterKey = appContext.masterKey;
From de7a95e218aa4d64a6c916922ffba67bf132c9ef Mon Sep 17 00:00:00 2001
From: Manuel Trezza <5673677+mtrezza@users.noreply.github.com>
Date: Tue, 29 Jul 2025 18:41:55 +0200
Subject: [PATCH 42/42] Update Agent.react.js
---
src/dashboard/Data/Agent/Agent.react.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/dashboard/Data/Agent/Agent.react.js b/src/dashboard/Data/Agent/Agent.react.js
index 602e37f48e..7f5473f403 100644
--- a/src/dashboard/Data/Agent/Agent.react.js
+++ b/src/dashboard/Data/Agent/Agent.react.js
@@ -586,15 +586,15 @@ class Agent extends DashboardView {