Add support to use LLM's (gemni) to create functions, also create documentation page

This commit is contained in:
Peter Stockings
2025-06-21 19:04:00 +10:00
parent 525471d8c0
commit 917189b3d9
4 changed files with 610 additions and 186 deletions

View File

@@ -58,17 +58,22 @@ const Editor = {
// New timer-specific props
this.isTimer = vnode.attrs.isTimer || false;
this.triggerType = vnode.attrs.triggerType || 'interval'; // 'interval' or 'date'
this.triggerType = vnode.attrs.triggerType || "interval"; // 'interval' or 'date'
this.frequencyMinutes = vnode.attrs.frequencyMinutes || 60;
this.runDate = vnode.attrs.runDate || '';
this.runDate = vnode.attrs.runDate || "";
// Show timer settings panel
this.showTimerSettings = vnode.attrs.showTimerSettings === true; // default false
this.cancelUrl = vnode.attrs.cancelUrl || '/dashboard/http_functions';
this.cancelUrl = vnode.attrs.cancelUrl || "/dashboard/http_functions";
// Add enabled property for timer functions
this.isEnabled = vnode.attrs.isEnabled !== false; // default true
// New state for AI generation
this.naturalLanguageQuery = "";
this.generateLoading = false;
this.showNaturalLanguageQuery = false;
},
oncreate() {
@@ -142,44 +147,52 @@ const Editor = {
environment_info: this.jsonValue,
is_public: this.isPublic,
log_request: this.logRequest,
log_response: this.logResponse
log_response: this.logResponse,
};
// Create payload based on whether this is a timer function
payload = this.isTimer ? {
name: this.name,
script_content: this.jsValue,
environment_info: this.jsonValue,
trigger_type: this.triggerType,
frequency_minutes: this.triggerType === 'interval' ? parseInt(this.frequencyMinutes) : null,
run_date: this.triggerType === 'date' ? this.runDate : null,
is_enabled: this.isEnabled // Add enabled status to payload
} : {
name: this.name,
script_content: this.jsValue,
environment_info: this.jsonValue
};
payload = this.isTimer
? {
name: this.name,
script_content: this.jsValue,
environment_info: this.jsonValue,
trigger_type: this.triggerType,
frequency_minutes:
this.triggerType === "interval"
? parseInt(this.frequencyMinutes)
: null,
run_date: this.triggerType === "date" ? this.runDate : null,
is_enabled: this.isEnabled, // Add enabled status to payload
}
: {
name: this.name,
script_content: this.jsValue,
environment_info: this.jsonValue,
};
const response = await m.request({
method: 'POST',
method: "POST",
url: this.saveUrl,
body: payload
body: payload,
});
if (response.status === 'success') {
if (response.status === "success") {
if (this.isAdd) {
window.location.href = this.dashboardUrl;
} else {
// Increment version number after successful save
this.versionNumber = (parseInt(this.versionNumber) + 1).toString();
}
Alert.show(response.message || 'Function saved successfully!', 'success');
Alert.show(
response.message || "Function saved successfully!",
"success"
);
} else {
Alert.show(response.message || 'Error saving function', 'error');
Alert.show(response.message || "Error saving function", "error");
this.error = new Error(response.message);
}
} catch (err) {
Alert.show(err?.response.message || 'Error saving function', 'error');
Alert.show(err?.response.message || "Error saving function", "error");
this.error = err?.response;
} finally {
this.saveLoading = false;
@@ -188,7 +201,7 @@ const Editor = {
},
async delete() {
if (!confirm('Are you sure you want to delete this function?')) {
if (!confirm("Are you sure you want to delete this function?")) {
return;
}
@@ -197,20 +210,23 @@ const Editor = {
try {
const response = await m.request({
method: 'DELETE',
url: this.deleteUrl
method: "DELETE",
url: this.deleteUrl,
});
if (response.status === 'success') {
Alert.show(response.message || 'Function deleted successfully!', 'success');
if (response.status === "success") {
Alert.show(
response.message || "Function deleted successfully!",
"success"
);
// Optionally redirect to a different page after deletion
window.location.href = this.cancelUrl;
} else {
Alert.show(response.message || 'Error deleting function', 'error');
Alert.show(response.message || "Error deleting function", "error");
this.error = new Error(response.message);
}
} catch (err) {
Alert.show(err.message || 'Error deleting function', 'error');
Alert.show(err.message || "Error deleting function", "error");
this.error = err;
} finally {
this.deleteLoading = false;
@@ -218,6 +234,31 @@ const Editor = {
}
},
async generateWithAI() {
this.generateLoading = true;
this.error = null;
m.redraw();
try {
const resp = await m.request({
method: "POST",
url: "/api/generate_script", // Assuming this is the new endpoint
body: { natural_query: this.naturalLanguageQuery },
});
if (resp.script_content) {
this.editorJS.setValue(resp.script_content, -1);
} else if (resp.error) {
throw new Error(resp.error);
}
} catch (err) {
this.error = err;
} finally {
this.generateLoading = false;
m.redraw();
}
},
view() {
return m("div", { class: "" }, [
/* ─────────────────────────────────────────────────────────────────
@@ -350,6 +391,49 @@ const Editor = {
]
),
/* ─────────────────────────────────────────────────────────────────
AI Generation
─────────────────────────────────────────────────────────────────*/
m("div", { class: "p-2 border-b border-gray-200 dark:border-gray-800" }, [
m(
"button",
{
class: "text-sm text-blue-500 hover:underline",
onclick: () =>
(this.showNaturalLanguageQuery = !this.showNaturalLanguageQuery),
},
"Generate with AI"
),
this.showNaturalLanguageQuery &&
m("div", { class: "mt-2" }, [
m("textarea", {
class:
"w-full p-2 border rounded bg-gray-50 dark:bg-gray-700 dark:border-gray-600",
rows: 3,
placeholder:
"Enter a description of what you want this function to do...",
oninput: (e) => (this.naturalLanguageQuery = e.target.value),
value: this.naturalLanguageQuery,
}),
m(
"button",
{
class:
"mt-2 px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 disabled:opacity-50",
onclick: () => this.generateWithAI(),
disabled: this.generateLoading,
},
this.generateLoading
? m("div", {
class:
"animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full",
})
: "Generate"
),
]),
]),
/* ─────────────────────────────────────────────────────────────────
JS Editor
─────────────────────────────────────────────────────────────────*/
@@ -419,170 +503,248 @@ const Editor = {
),
// Function settings panel
this.showFunctionSettings && m("div", { class: "bg-gray-100 dark:bg-gray-800 p-4 border-b" }, [
// Settings group
m("div", { class: "flex flex-col space-y-4" }, [
// Toggles group
m("div", { class: "flex flex-wrap gap-6" }, [
// Public/Private toggle
this.showPublicToggle && m("label", {
class: "flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer"
}, [
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.isPublic,
onchange: (e) => this.isPublic = e.target.checked
}),
m("div", {
class: "w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
})
]),
m("span", "Public Function")
]),
// Log Request toggle
this.showLogRequestToggle && m("label", {
class: "flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer"
}, [
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.logRequest,
onchange: (e) => this.logRequest = e.target.checked
}),
m("div", {
class: "w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
})
]),
m("span", "Log Requests")
]),
// Log Response toggle
this.showLogResponseToggle && m("label", {
class: "flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer"
}, [
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.logResponse,
onchange: (e) => this.logResponse = e.target.checked
}),
m("div", {
class: "w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
})
]),
m("span", "Log Responses")
])
]),
// Timer settings (shown only if isTimer is true)
this.isTimer && this.showTimerSettings && [
// Enabled toggle
m("label", {
class: "flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer mt-4"
}, [
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.isEnabled,
onchange: (e) => this.isEnabled = e.target.checked
}),
m("div", {
class: "w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"
})
]),
m("span", "Enabled")
]),
// Timer settings group
m("div", { class: "grid grid-cols-2 gap-4 mt-4" }, [
// Trigger Type Selection
m("div", { class: "flex flex-col space-y-2" }, [
m("label", { class: "text-sm font-medium text-gray-700 dark:text-gray-300" },
"Trigger Type"
this.showFunctionSettings &&
m("div", { class: "bg-gray-100 dark:bg-gray-800 p-4 border-b" }, [
// Settings group
m("div", { class: "flex flex-col space-y-4" }, [
// Toggles group
m("div", { class: "flex flex-wrap gap-6" }, [
// Public/Private toggle
this.showPublicToggle &&
m(
"label",
{
class:
"flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer",
},
[
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.isPublic,
onchange: (e) => (this.isPublic = e.target.checked),
}),
m("div", {
class:
"w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600",
}),
]),
m("span", "Public Function"),
]
),
m("select", {
class: "bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.triggerType,
onchange: (e) => this.triggerType = e.target.value
}, [
m("option", { value: "interval" }, "Interval"),
m("option", { value: "date" }, "Specific Date")
])
]),
// Interval Settings or Date Settings based on triggerType
this.triggerType === 'interval' ?
m("div", { class: "flex flex-col space-y-2" }, [
m("label", { class: "text-sm font-medium text-gray-700 dark:text-gray-300" },
"Frequency (minutes)"
),
m("input[type=number]", {
class: "bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.frequencyMinutes,
min: 1,
onchange: (e) => this.frequencyMinutes = e.target.value
})
]) :
m("div", { class: "flex flex-col space-y-2" }, [
m("label", { class: "text-sm font-medium text-gray-700 dark:text-gray-300" },
"Run Date"
),
m("input[type=datetime-local]", {
class: "bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.runDate,
onchange: (e) => this.runDate = e.target.value
})
])
])
],
// Log Request toggle
this.showLogRequestToggle &&
m(
"label",
{
class:
"flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer",
},
[
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.logRequest,
onchange: (e) => (this.logRequest = e.target.checked),
}),
m("div", {
class:
"w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600",
}),
]),
m("span", "Log Requests"),
]
),
// Actions group
m("div", { class: "flex items-center justify-end space-x-3 pt-2" }, [
// Save button
this.showSaveButton && m("button", {
class: "px-4 py-2 bg-blue-500 text-white text-sm font-medium rounded-lg hover:bg-blue-600 transition-colors flex items-center space-x-2 disabled:opacity-50",
onclick: () => this.save(),
disabled: this.saveLoading
}, [
this.saveLoading && m("div", {
class: "animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full"
}),
m("span", this.saveLoading ? "Saving..." : "Save Function")
// Log Response toggle
this.showLogResponseToggle &&
m(
"label",
{
class:
"flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer",
},
[
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.logResponse,
onchange: (e) => (this.logResponse = e.target.checked),
}),
m("div", {
class:
"w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600",
}),
]),
m("span", "Log Responses"),
]
),
]),
// Delete button
this.showDeleteButton && m("button", {
class: "px-4 py-2 bg-white text-red-600 text-sm font-medium border border-red-200 rounded-lg hover:bg-red-50 transition-colors flex items-center space-x-2 disabled:opacity-50",
onclick: () => this.delete(),
disabled: this.deleteLoading
}, [
this.deleteLoading && m("div", {
class: "animate-spin h-4 w-4 border-2 border-red-600 border-t-transparent rounded-full"
}),
m("span", this.deleteLoading ? "Deleting..." : "Delete Function")
])
])
])
]),
// Timer settings (shown only if isTimer is true)
this.isTimer &&
this.showTimerSettings && [
// Enabled toggle
m(
"label",
{
class:
"flex items-center space-x-3 text-sm text-gray-600 dark:text-gray-300 cursor-pointer mt-4",
},
[
m("div", { class: "relative" }, [
m("input[type=checkbox]", {
class: "sr-only peer",
checked: this.isEnabled,
onchange: (e) => (this.isEnabled = e.target.checked),
}),
m("div", {
class:
"w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600",
}),
]),
m("span", "Enabled"),
]
),
// Timer settings group
m("div", { class: "grid grid-cols-2 gap-4 mt-4" }, [
// Trigger Type Selection
m("div", { class: "flex flex-col space-y-2" }, [
m(
"label",
{
class:
"text-sm font-medium text-gray-700 dark:text-gray-300",
},
"Trigger Type"
),
m(
"select",
{
class:
"bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.triggerType,
onchange: (e) => (this.triggerType = e.target.value),
},
[
m("option", { value: "interval" }, "Interval"),
m("option", { value: "date" }, "Specific Date"),
]
),
]),
// Interval Settings or Date Settings based on triggerType
this.triggerType === "interval"
? m("div", { class: "flex flex-col space-y-2" }, [
m(
"label",
{
class:
"text-sm font-medium text-gray-700 dark:text-gray-300",
},
"Frequency (minutes)"
),
m("input[type=number]", {
class:
"bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.frequencyMinutes,
min: 1,
onchange: (e) =>
(this.frequencyMinutes = e.target.value),
}),
])
: m("div", { class: "flex flex-col space-y-2" }, [
m(
"label",
{
class:
"text-sm font-medium text-gray-700 dark:text-gray-300",
},
"Run Date"
),
m("input[type=datetime-local]", {
class:
"bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-2",
value: this.runDate,
onchange: (e) => (this.runDate = e.target.value),
}),
]),
]),
],
// Actions group
m(
"div",
{ class: "flex items-center justify-end space-x-3 pt-2" },
[
// Save button
this.showSaveButton &&
m(
"button",
{
class:
"px-4 py-2 bg-blue-500 text-white text-sm font-medium rounded-lg hover:bg-blue-600 transition-colors flex items-center space-x-2 disabled:opacity-50",
onclick: () => this.save(),
disabled: this.saveLoading,
},
[
this.saveLoading &&
m("div", {
class:
"animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full",
}),
m(
"span",
this.saveLoading ? "Saving..." : "Save Function"
),
]
),
// Delete button
this.showDeleteButton &&
m(
"button",
{
class:
"px-4 py-2 bg-white text-red-600 text-sm font-medium border border-red-200 rounded-lg hover:bg-red-50 transition-colors flex items-center space-x-2 disabled:opacity-50",
onclick: () => this.delete(),
disabled: this.deleteLoading,
},
[
this.deleteLoading &&
m("div", {
class:
"animate-spin h-4 w-4 border-2 border-red-600 border-t-transparent rounded-full",
}),
m(
"span",
this.deleteLoading ? "Deleting..." : "Delete Function"
),
]
),
]
),
]),
]),
/* ─────────────────────────────────────────────────────────────────
ResponseView (child) if needed
─────────────────────────────────────────────────────────────────*/
!this.executeLoading &&
!this.error &&
this.response &&
m(ResponseView, {
response: this.response,
responseTime: this.responseTime,
responseSize: this.responseSize,
envEditorValue: this.jsonValue,
isTimer: this.isTimer,
onClose: () => {
this.response = null;
},
}),
!this.error &&
this.response &&
m(ResponseView, {
response: this.response,
responseTime: this.responseTime,
responseSize: this.responseSize,
envEditorValue: this.jsonValue,
isTimer: this.isTimer,
onClose: () => {
this.response = null;
},
}),
]);
},
};