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.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, }; 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, } : { 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, }; 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-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") ]) ]) ]) ]); }, };