Files
function/static/js/mithril/editor.js
2025-11-21 10:30:14 +11:00

886 lines
36 KiB
JavaScript

const Editor = {
oninit(vnode) {
// Props
this.isEdit = vnode.attrs.isEdit || false;
this.isAdd = vnode.attrs.isAdd || false;
this.isPublic = vnode.attrs.isPublic || false;
this.logRequest = vnode.attrs.logRequest || false;
this.logResponse = vnode.attrs.logResponse || false;
this.runtime = vnode.attrs.runtime || "node";
this.showHeader = vnode.attrs.showHeader !== false;
this.showPublicToggle = vnode.attrs.showPublicToggle !== false;
this.showLogRequestToggle = vnode.attrs.showLogRequestToggle !== false;
this.showLogResponseToggle = vnode.attrs.showLogResponseToggle !== false;
this.showSaveButton = vnode.attrs.showSaveButton !== false;
this.showFunctionSettings = vnode.attrs.showFunctionSettings !== false;
this.name = vnode.attrs.name || "foo";
this.path = vnode.attrs.path || "";
this.versionNumber = vnode.attrs.versionNumber || "1";
this.description = vnode.attrs.description || "";
this.nameEditing = false;
this.pathEditing = false;
this.jsValue = vnode.attrs.jsValue || "";
this.jsonValue = vnode.attrs.jsonValue || "{}";
this.executeUrl = vnode.attrs.executeUrl;
this.showEnvironment = false;
this.loading = false;
this.error = null;
this.response = null;
this.responseRaw = "";
this.responseTime = 0;
this.responseSize = 0;
this.saveUrl = vnode.attrs.saveUrl;
this.executeLoading = false;
this.saveLoading = false;
this.showDeleteButton = vnode.attrs.showDeleteButton !== false;
this.deleteUrl = vnode.attrs.deleteUrl;
this.dashboardUrl = vnode.attrs.dashboardUrl;
this.isTimer = vnode.attrs.isTimer || false;
this.triggerType = vnode.attrs.triggerType || "interval";
this.frequencyMinutes = vnode.attrs.frequencyMinutes || 60;
this.runDate = vnode.attrs.runDate || "";
this.showTimerSettings = vnode.attrs.showTimerSettings === true;
this.cancelUrl = vnode.attrs.cancelUrl || "/dashboard/http_functions";
this.isEnabled = vnode.attrs.isEnabled !== false;
this.generateUrl = vnode.attrs.generateUrl;
this.logsUrl = vnode.attrs.logsUrl; // Needed for debug feature
// AI State
this.naturalLanguageQuery = "";
this.aiLoading = false; // General AI loading state
this.showNaturalLanguageQuery = false;
this.aiModalOpen = false;
this.aiModalContent = "";
this.aiModalTitle = "";
},
oncreate() {
this.editorJS = ace.edit("js-editor");
this.editorJS.setOptions({ maxLines: 100 });
this.editorJS.setTheme("ace/theme/github_dark");
this.editorJS.session.setMode(
this.runtime === "python" ? "ace/mode/python" : "ace/mode/javascript"
);
this.editorJS.setValue(this.jsValue, -1);
this.editorJS.session.on("change", () => {
this.jsValue = this.editorJS.getValue();
m.redraw();
});
this.editorJSON = ace.edit("json-editor");
this.editorJSON.setOptions({ maxLines: 100 });
this.editorJSON.setTheme("ace/theme/github_dark");
this.editorJSON.session.setMode("ace/mode/json");
this.editorJSON.setValue(this.jsonValue, -1);
this.editorJSON.session.on("change", () => {
this.jsonValue = this.editorJSON.getValue();
m.redraw();
});
},
async execute() {
this.executeLoading = true;
this.error = null;
this.response = null;
this.responseRaw = "";
this.responseSize = 0;
this.responseTime = 0;
const startTime = Date.now();
try {
const code = this.editorJS.getValue();
const environment_info = this.editorJSON.getValue();
const resp = await fetch(this.executeUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
code,
environment_info,
runtime: this.runtime,
}),
});
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
this.responseRaw = await resp.text();
this.responseSize = new Blob([this.responseRaw]).size;
this.response = JSON.parse(this.responseRaw);
this.responseTime = Date.now() - startTime;
} catch (err) {
this.error = err;
} finally {
this.executeLoading = false;
m.redraw();
}
},
async save() {
this.saveLoading = true;
this.error = null;
try {
let payload = {
name: this.name,
path: this.path,
script_content: this.jsValue,
environment_info: this.jsonValue,
is_public: this.isPublic,
log_request: this.logRequest,
log_response: this.logResponse,
runtime: this.runtime,
description: this.description,
};
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,
description: this.description,
}
: {
name: this.name,
path: this.path,
script_content: this.jsValue,
environment_info: this.jsonValue,
is_public: this.isPublic,
log_request: this.logRequest,
log_response: this.logResponse,
runtime: this.runtime,
description: this.description,
};
const response = await m.request({
method: "POST",
url: this.saveUrl,
body: payload,
});
if (response.status === "success") {
if (this.isAdd) {
window.location.href = this.dashboardUrl;
} else {
this.versionNumber = (parseInt(this.versionNumber) + 1).toString();
}
Alert.show(
response.message || "Function saved successfully!",
"success"
);
} else {
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");
this.error = err?.response;
} finally {
this.saveLoading = false;
m.redraw();
}
},
async delete() {
if (!confirm("Are you sure you want to delete this function?")) {
return;
}
this.deleteLoading = true;
this.error = null;
try {
const response = await m.request({
method: "DELETE",
url: this.deleteUrl,
});
if (response.status === "success") {
Alert.show(
response.message || "Function deleted successfully!",
"success"
);
window.location.href = this.cancelUrl;
} else {
Alert.show(response.message || "Error deleting function", "error");
this.error = new Error(response.message);
}
} catch (err) {
Alert.show(err.message || "Error deleting function", "error");
this.error = err;
} finally {
this.deleteLoading = false;
m.redraw();
}
},
async callAI(action, extraData = {}) {
this.aiLoading = true;
this.error = null;
m.redraw();
try {
const payload = {
action: action,
code: this.editorJS.getValue(),
runtime: this.runtime,
...extraData
};
const resp = await m.request({
method: "POST",
url: this.generateUrl,
body: payload,
});
if (resp.result) {
if (action === 'generate' || action === 'optimize') {
this.editorJS.setValue(resp.result, -1);
Alert.show("Code updated successfully!", "success");
} else {
// Explain or Debug - show in modal
this.aiModalTitle = action === 'explain' ? 'Code Explanation' : 'Debug Analysis';
this.aiModalContent = resp.result;
this.aiModalOpen = true;
}
} else if (resp.error) {
throw new Error(resp.error);
}
} catch (err) {
this.error = err;
Alert.show(err.message || "AI Error", "error");
} finally {
this.aiLoading = false;
m.redraw();
}
},
async debugWithAI() {
// Fetch logs first? For now, we'll just pass the code.
// Ideally we'd fetch recent error logs here.
// Let's assume the user copies logs or we just analyze the code for now.
// OR we can try to fetch the last error log if available.
// For this iteration, let's just analyze code unless we have logs in state.
// TODO: Fetch logs from server if possible.
// For now, we will prompt the user to paste logs or just analyze code.
// Actually, let's just analyze the code for potential bugs.
this.callAI('debug', { logs: "No logs provided, please analyze code for potential bugs." });
},
view() {
return m("div", { class: "" }, [
/* Header */
m(
"div",
{
class: "flex items-center justify-between pl-2",
},
[
this.showHeader
? m("div", { class: "flex space-x-2" }, [
this.isEdit
? m(
"div",
{
class:
"inline-flex items-center space-x-1 h-10 py-2 text-gray-600 dark:text-gray-400 text-md font-medium cursor-pointer",
},
[
m("span", { class: "inline-flex items-center" }, [
!this.pathEditing
? m(
"span",
{
class: "font-mono text-gray-500 mr-1",
onclick: () => (this.pathEditing = true),
},
this.path ? `${this.path}/` : "/"
)
: m("input", {
class:
"bg-gray-50 border border-gray-300 text-sm rounded-lg p-1.5 dark:bg-gray-700 dark:border-gray-600 w-24 mr-1",
value: this.path,
placeholder: "path",
oninput: (e) => (this.path = e.target.value),
onblur: () => (this.pathEditing = false),
autofocus: true,
}),
!this.nameEditing
? m(
"span",
{
class: "font-mono",
onclick: () => (this.nameEditing = true),
},
this.name
)
: m("input", {
class:
"bg-gray-50 border border-gray-300 text-sm rounded-lg p-1.5 dark:bg-gray-700 dark:border-gray-600",
value: this.name,
oninput: (e) => (this.name = e.target.value),
onblur: () => (this.nameEditing = false),
autofocus: true,
}),
m(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
"stroke-width": "1.5",
stroke: "currentColor",
class: "w-5 h-5",
onclick: () =>
(this.nameEditing = !this.nameEditing),
},
m("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10",
})
),
]),
m(
"span",
{
class:
"bg-blue-500 text-white text-xs font-semibold px-2 py-1 rounded ml-2",
},
`v${this.versionNumber}`
),
]
)
: null,
this.isAdd
? m("div", { class: "flex space-x-2 w-full" }, [
m("div", { class: "w-1/3" }, [
m(
"label",
{ class: "block mb-2 text-sm font-medium" },
"Path"
),
m("input", {
type: "text",
class: "bg-gray-50 border w-full p-2.5 rounded-lg",
placeholder: "api/v1",
value: this.path,
oninput: (e) => (this.path = e.target.value),
}),
]),
m("div", { class: "w-2/3" }, [
m(
"label",
{ class: "block mb-2 text-sm font-medium" },
"Function name"
),
m("input", {
type: "text",
class: "bg-gray-50 border w-full p-2.5 rounded-lg",
placeholder: "foo",
required: true,
value: this.name,
oninput: (e) => (this.name = e.target.value),
}),
]),
])
: null,
])
: m("div"),
m("div"),
]
),
/* AI Toolbar */
m(
"div",
{
class:
"flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-800",
},
[
m("div", { class: "flex space-x-2" }, [
m(
"button",
{
class: "text-sm text-blue-500 hover:underline flex items-center",
onclick: () =>
(this.showNaturalLanguageQuery = !this.showNaturalLanguageQuery),
},
[
m("span", "Generate"),
m("svg", {class: "w-4 h-4 ml-1", fill:"none", viewBox:"0 0 24 24", stroke:"currentColor"}, m("path", {d:"M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z", "stroke-width":1.5, "stroke-linecap":"round", "stroke-linejoin":"round"}))
]
),
m("span", {class: "text-gray-300"}, "|"),
m(
"button",
{
class: "text-sm text-gray-600 hover:text-gray-900",
onclick: () => this.callAI('explain'),
disabled: this.aiLoading
},
"Explain"
),
m(
"button",
{
class: "text-sm text-gray-600 hover:text-gray-900",
onclick: () => this.callAI('optimize'),
disabled: this.aiLoading
},
"Optimize"
),
m(
"button",
{
class: "text-sm text-gray-600 hover:text-gray-900",
onclick: () => this.debugWithAI(),
disabled: this.aiLoading
},
"Debug"
),
this.aiLoading && m("div", {class: "animate-spin h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full ml-2"})
]),
m("div", { class: "flex items-center space-x-4" }, [
m(
"select",
{
key: "runtime-selector",
class:
"bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
onchange: (e) => {
this.runtime = e.target.value;
this.editorJS.session.setMode(
this.runtime === "python"
? "ace/mode/python"
: "ace/mode/javascript"
);
},
},
[
m(
"option",
{ value: "node", selected: this.runtime === "node" },
"Node"
),
m(
"option",
{ value: "deno", selected: this.runtime === "deno" },
"Deno"
),
m(
"option",
{ value: "python", selected: this.runtime === "python" },
"Python"
),
]
),
this.executeLoading
? m("div", {
key: "spinner",
class:
"animate-spin h-6 w-6 border-4 border-green-300 border-t-transparent rounded-full",
})
: m(
"button",
{
key: "execute-button",
class: "p-2 rounded-full hover:bg-gray-200 text-green-700",
onclick: () => this.execute(),
title: "Execute",
},
m(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
"stroke-width": "1.5",
stroke: "currentColor",
class: "w-6 h-6",
},
m("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M5.25 5.25 19.5 12 5.25 18.75 5.25 5.25z",
})
)
),
]),
]
),
this.showNaturalLanguageQuery &&
m(
"div",
{ class: "p-2 border-b border-gray-200 dark:border-gray-800" },
[
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.callAI('generate', { natural_query: this.naturalLanguageQuery }),
disabled: this.aiLoading,
},
"Generate"
),
]
),
m("div", { id: "js-editor", class: "rounded shadow h-64" }),
m(
"div",
{ class: "flex space-x-2 border-b border-gray-200 justify-between" },
[
m(
"button",
{
class:
"inline-flex items-center px-4 py-2 h-10 text-gray-600 hover:bg-accent hover:text-accent-foreground",
onclick: () => (this.showEnvironment = !this.showEnvironment),
},
[
m(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
"stroke-width": "1.5",
stroke: "currentColor",
class: "w-4 h-4",
},
m("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125",
})
),
m("span", { class: "ml-1" }, "Environment"),
]
),
m("div", { class: "flex-auto" }),
]
),
m("div", { id: "json-editor", class: "rounded shadow h-64" }),
m("input", { type: "hidden", name: "script", value: this.jsValue }),
m("input", {
type: "hidden",
name: "environment",
value: this.jsonValue,
}),
this.error &&
m(
"div",
{ class: "mt-2 p-2 text-red-600 font-semibold" },
`Error: ${this.error.message}`
),
this.showFunctionSettings &&
m("div", { class: "bg-gray-100 dark:bg-gray-800 p-4 border-b" }, [
m("div", { class: "flex flex-col space-y-4" }, [
m("div", { class: "flex flex-col space-y-2" }, [
m("label", { class: "text-sm font-medium text-gray-700 dark:text-gray-300" }, "Description"),
m("textarea", {
class: "w-full p-2 border rounded bg-white dark:bg-gray-700 dark:border-gray-600 text-sm",
rows: 2,
placeholder: "Describe what this function does...",
value: this.description,
oninput: (e) => (this.description = e.target.value)
})
]),
m("div", { class: "flex flex-wrap gap-6" }, [
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"),
]
),
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"),
]
),
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"),
]
),
]),
this.isTimer &&
this.showTimerSettings && [
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"),
]
),
m("div", { class: "grid grid-cols-2 gap-4 mt-4" }, [
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"),
]
),
]),
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),
}),
]),
]),
],
m(
"div",
{ class: "flex items-center justify-end space-x-3 pt-2" },
[
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(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
"stroke-width": "1.5",
stroke: "currentColor",
class: "w-4 h-4",
},
m("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "m4.5 12.75 6 6 9-13.5",
})
),
m("span", "Save"),
]
),
this.showDeleteButton &&
m(
"button",
{
class:
"px-4 py-2 bg-red-500 text-white text-sm font-medium rounded-lg hover:bg-red-600 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-white border-t-transparent rounded-full",
})
: [
m(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
viewBox: "0 0 24 24",
"stroke-width": "1.5",
stroke: "currentColor",
class: "w-4 h-4",
},
m("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
d: "m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0",
})
),
m("span", "Delete"),
]
),
]
),
]),
]),
/* AI Modal */
this.aiModalOpen && m("div", {
class: "fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50",
onclick: () => this.aiModalOpen = false
}, [
m("div", {
class: "bg-white dark:bg-gray-800 rounded-lg shadow-xl w-3/4 max-w-4xl max-h-[80vh] flex flex-col",
onclick: (e) => e.stopPropagation()
}, [
m("div", {class: "p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center"}, [
m("h3", {class: "text-lg font-semibold"}, this.aiModalTitle),
m("button", {
class: "text-gray-500 hover:text-gray-700",
onclick: () => this.aiModalOpen = false
}, m("svg", {class: "w-6 h-6", fill:"none", viewBox:"0 0 24 24", stroke:"currentColor"}, m("path", {d:"M6 18L18 6M6 6l12 12", "stroke-width":2, "stroke-linecap":"round", "stroke-linejoin":"round"})))
]),
m("div", {class: "p-4 overflow-y-auto flex-1"}, [
m("pre", {class: "whitespace-pre-wrap text-sm font-mono bg-gray-50 dark:bg-gray-900 p-4 rounded"}, this.aiModalContent)
]),
m("div", {class: "p-4 border-t border-gray-200 dark:border-gray-700 flex justify-end"}, [
m("button", {
class: "px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700",
onclick: () => this.aiModalOpen = false
}, "Close")
])
])
])
]);
},
};