diff --git a/app.py b/app.py
index 48f66f2..3042201 100644
--- a/app.py
+++ b/app.py
@@ -14,6 +14,7 @@ from routes.timer import timer
from routes.test import test
from routes.home import home
from routes.http import http
+from routes.llm import llm
from flask_apscheduler import APScheduler
import asyncio
import aiohttp
@@ -39,6 +40,7 @@ app.register_blueprint(timer, url_prefix='/timer')
app.register_blueprint(test, url_prefix='/test')
app.register_blueprint(home, url_prefix='/home')
app.register_blueprint(http, url_prefix='/http')
+app.register_blueprint(llm, url_prefix='/llm')
class User(UserMixin):
def __init__(self, id, username, password_hash, created_at):
@@ -120,149 +122,13 @@ def home():
def documentation():
return render_template("documentation.html")
-def _generate_script_from_natural_language(natural_query):
- """Generates a Javascript function from natural language using Gemini REST API."""
- gemni_model = os.environ.get("GEMINI_MODEL", "gemini-1.5-flash")
- api_key = os.environ.get("GEMINI_API_KEY")
- if not api_key:
- return None, "GEMINI_API_KEY environment variable not set."
-
- api_url = f"https://generativelanguage.googleapis.com/v1beta/models/{gemni_model}:generateContent?key={api_key}"
- headers = {'Content-Type': 'application/json'}
-
- try:
- prompt = f"""
-You are an expert Javascript developer. Your task is to write a complete, production-ready Javascript async function based on the user's request.
-
-**Function Signature:**
-Your function MUST have the following signature: `async (req, environment) => {{ ... }}`
-- `req`: An object containing details about the incoming HTTP request (e.g., `req.method`, `req.headers`, `req.body`, `req.query`).
-- `environment`: A mutable JSON object that persists across executions. You can read and write to it to maintain state.
-
-**Environment & Constraints:**
-- The function will be executed in a simple, sandboxed Javascript environment.
-- **CRITICAL**: ALL helper functions or variables MUST be defined *inside* the main `async` function. Do not define anything in the global scope.
-- **DO NOT** use `require()`, `import`, or any other module loading system.
-- **DO NOT** access the file system (`fs` module) or make network requests.
-- You have access to a `console.log()` function for logging.
-
-**Response Helpers:**
-You must use one of the following functions to return a response:
-- `HtmlResponse(body)`: Returns an HTML response.
-- `JsonResponse(body)`: Returns a JSON response.
-- `TextResponse(body)`: Returns a plain text response.
-- `RedirectResponse(url)`: Redirects the user.
-
-**Complex Example (Tic-Tac-Toe):**
-```javascript
-async (req, environment) => {{
- // Helper function defined INSIDE the main function
- function checkWinner(board) {{
- const winConditions = [
- [0, 1, 2], [3, 4, 5], [6, 7, 8],
- [0, 3, 6], [1, 4, 7], [2, 5, 8],
- [0, 4, 8], [2, 4, 6]
- ];
- for (const condition of winConditions) {{
- const [a, b, c] = condition;
- if (board[a] && board[a] === board[b] && board[a] === board[c]) {{
- return board[a];
- }}
- }}
- return null;
- }}
-
- if (!environment.board) {{
- environment.board = Array(9).fill("");
- environment.turn = "X";
- environment.winner = null;
- }}
-
- if (req.method === "POST" && req.json && req.json.move !== undefined) {{
- const move = parseInt(req.json.move);
- if (environment.board[move] === "" && !environment.winner) {{
- environment.board[move] = environment.turn;
- environment.turn = environment.turn === "X" ? "O" : "X";
- environment.winner = checkWinner(environment.board);
- }}
- }}
-
- const boardHTML = environment.board.map((cell, index) => ``).join("");
- const message = environment.winner ? `Player ${{environment.winner}} wins!` : `Turn: ${{environment.turn}}`;
-
- return HtmlResponse(`
-
-
Tic-Tac-Toe
-
${{message}}
${{boardHTML}}
-
- `);
-}}
-```
-
-**User's request:** "{natural_query}"
-
-Return ONLY the complete Javascript function code, without any explanation, comments, or surrounding text/markdown.
-"""
- payload = json.dumps({
- "contents": [{"parts": [{"text": prompt}]}]
- })
-
- response = requests.post(api_url, headers=headers, data=payload)
- response.raise_for_status()
-
- response_data = response.json()
-
- candidates = response_data.get('candidates', [])
- if not candidates:
- return None, "No candidates found in API response."
-
- content = candidates[0].get('content', {})
- parts = content.get('parts', [])
- if not parts:
- return None, "No parts found in API response content."
-
- generated_script = parts[0].get('text', '').strip()
-
- # More robustly extract from markdown
- if generated_script.startswith("```javascript"):
- generated_script = generated_script[12:]
- if generated_script.endswith("```"):
- generated_script = generated_script[:-3]
-
- generated_script = generated_script.strip()
-
- # Remove any leading non-code characters
- if not generated_script.startswith('async ('):
- async_start = generated_script.find('async (')
- if async_start != -1:
- generated_script = generated_script[async_start:]
-
- return generated_script.strip(), None
-
- except requests.exceptions.RequestException as e:
- app.logger.error(f"Gemini API request error: {e}")
- return None, f"Error communicating with API: {e}"
- except (KeyError, IndexError, Exception) as e:
- app.logger.error(f"Error processing Gemini API response: {e} - Response: {response_data if 'response_data' in locals() else 'N/A'}")
- return None, f"Error processing API response: {e}"
-
-@app.route("/api/generate_script", methods=["POST"])
+@ app.route("/dashboard", methods=["GET"])
@login_required
-def generate_script():
- try:
- natural_query = request.json.get('natural_query')
- if not natural_query:
- return jsonify({"error": "natural_query is required"}), 400
-
- script_content, error = _generate_script_from_natural_language(natural_query)
-
- if error:
- return jsonify({"error": error}), 500
-
- return jsonify({"script_content": script_content})
-
- except Exception as e:
- return jsonify({'error': str(e)}), 500
+def dashboard():
+ user_id = current_user.id
+ http_functions = db.get_http_functions_for_user(user_id)
+ http_functions = create_http_functions_view_model(http_functions)
+ return render_template("dashboard/http_functions/overview.html", http_functions=http_functions)
@app.route('/execute', methods=['POST'])
def execute_code():
diff --git a/routes/llm.py b/routes/llm.py
new file mode 100644
index 0000000..c49c03b
--- /dev/null
+++ b/routes/llm.py
@@ -0,0 +1,149 @@
+import os
+import json
+import requests
+from flask import Blueprint, jsonify, request
+from flask_login import login_required
+
+llm = Blueprint('llm', __name__)
+
+def _generate_script_from_natural_language(natural_query):
+ """Generates a Javascript function from natural language using Gemini REST API."""
+ gemni_model = os.environ.get("GEMINI_MODEL", "gemini-1.5-flash")
+ api_key = os.environ.get("GEMINI_API_KEY")
+ if not api_key:
+ return None, "GEMINI_API_KEY environment variable not set."
+
+ api_url = f"https://generativelanguage.googleapis.com/v1beta/models/{gemni_model}:generateContent?key={api_key}"
+ headers = {'Content-Type': 'application/json'}
+
+ try:
+ prompt = f"""
+You are an expert Javascript developer. Your task is to write a complete, production-ready Javascript async function based on the user's request.
+
+**Function Signature:**
+Your function MUST have the following signature: `async (req, environment) => {{ ... }}`
+- `req`: An object containing details about the incoming HTTP request (e.g., `req.method`, `req.headers`, `req.body`, `req.query`).
+- `environment`: A mutable JSON object that persists across executions. You can read and write to it to maintain state.
+
+**Environment & Constraints:**
+- The function will be executed in a simple, sandboxed Javascript environment.
+- **CRITICAL**: ALL helper functions or variables MUST be defined *inside* the main `async` function. Do not define anything in the global scope.
+- **DO NOT** use `require()`, `import`, or any other module loading system.
+- **DO NOT** access the file system (`fs` module) or make network requests.
+- You have access to a `console.log()` function for logging.
+
+**Response Helpers:**
+You must use one of the following functions to return a response:
+- `HtmlResponse(body)`: Returns an HTML response.
+- `JsonResponse(body)`: Returns a JSON response.
+- `TextResponse(body)`: Returns a plain text response.
+- `RedirectResponse(url)`: Redirects the user.
+
+**Complex Example (Tic-Tac-Toe):**
+```javascript
+async (req, environment) => {{
+ // Helper function defined INSIDE the main function
+ function checkWinner(board) {{
+ const winConditions = [
+ [0, 1, 2], [3, 4, 5], [6, 7, 8],
+ [0, 3, 6], [1, 4, 7], [2, 5, 8],
+ [0, 4, 8], [2, 4, 6]
+ ];
+ for (const condition of winConditions) {{
+ const [a, b, c] = condition;
+ if (board[a] && board[a] === board[b] && board[a] === board[c]) {{
+ return board[a];
+ }}
+ }}
+ return null;
+ }}
+
+ if (!environment.board) {{
+ environment.board = Array(9).fill("");
+ environment.turn = "X";
+ environment.winner = null;
+ }}
+
+ if (req.method === "POST" && req.json && req.json.move !== undefined) {{
+ const move = parseInt(req.json.move);
+ if (environment.board[move] === "" && !environment.winner) {{
+ environment.board[move] = environment.turn;
+ environment.turn = environment.turn === "X" ? "O" : "X";
+ environment.winner = checkWinner(environment.board);
+ }}
+ }}
+
+ const boardHTML = environment.board.map((cell, index) => ``).join("");
+ const message = environment.winner ? `Player ${{environment.winner}} wins!` : `Turn: ${{environment.turn}}`;
+
+ return HtmlResponse(`
+
+ Tic-Tac-Toe
+
${{message}}
${{boardHTML}}
+
+ `);
+}}
+```
+
+**User's request:** "{natural_query}"
+
+Return ONLY the complete Javascript function code, without any explanation, comments, or surrounding text/markdown.
+"""
+ payload = json.dumps({
+ "contents": [{"parts": [{"text": prompt}]}]
+ })
+
+ response = requests.post(api_url, headers=headers, data=payload)
+ response.raise_for_status()
+
+ response_data = response.json()
+
+ candidates = response_data.get('candidates', [])
+ if not candidates:
+ return None, "No candidates found in API response."
+
+ content = candidates[0].get('content', {})
+ parts = content.get('parts', [])
+ if not parts:
+ return None, "No parts found in API response content."
+
+ generated_script = parts[0].get('text', '').strip()
+
+ # More robustly extract from markdown
+ if generated_script.startswith("```javascript"):
+ generated_script = generated_script[12:]
+ if generated_script.endswith("```"):
+ generated_script = generated_script[:-3]
+
+ generated_script = generated_script.strip()
+
+ # Remove any leading non-code characters
+ if not generated_script.startswith('async ('):
+ async_start = generated_script.find('async (')
+ if async_start != -1:
+ generated_script = generated_script[async_start:]
+
+ return generated_script.strip(), None
+
+ except requests.exceptions.RequestException as e:
+ return None, f"Error communicating with API: {e}"
+ except (KeyError, IndexError, Exception) as e:
+ return None, f"Error processing API response: {e}"
+
+@llm.route("/generate_script", methods=["POST"])
+@login_required
+def generate_script():
+ try:
+ natural_query = request.json.get('natural_query')
+ if not natural_query:
+ return jsonify({"error": "natural_query is required"}), 400
+
+ script_content, error = _generate_script_from_natural_language(natural_query)
+
+ if error:
+ return jsonify({"error": error}), 500
+
+ return jsonify({"script_content": script_content})
+
+ except Exception as e:
+ return jsonify({'error': str(e)}), 500
\ No newline at end of file
diff --git a/static/js/mithril/editor.js b/static/js/mithril/editor.js
index 1b0bc11..1342646 100644
--- a/static/js/mithril/editor.js
+++ b/static/js/mithril/editor.js
@@ -74,6 +74,7 @@ const Editor = {
this.naturalLanguageQuery = "";
this.generateLoading = false;
this.showNaturalLanguageQuery = false;
+ this.generateUrl = vnode.attrs.generateUrl;
},
oncreate() {
@@ -245,7 +246,7 @@ const Editor = {
try {
const resp = await m.request({
method: "POST",
- url: "/api/generate_script", // Assuming this is the new endpoint
+ url: this.generateUrl,
body: { natural_query: this.naturalLanguageQuery },
});
diff --git a/templates/dashboard/http_functions/editor.html b/templates/dashboard/http_functions/editor.html
index 876621c..b5c2448 100644
--- a/templates/dashboard/http_functions/editor.html
+++ b/templates/dashboard/http_functions/editor.html
@@ -36,6 +36,7 @@ history_url=url_for('http.history', function_id=function_id)) }}
saveUrl: "{{ url_for('http.edit', function_id=id) if id else url_for('http.new') }}",
deleteUrl: "{{ url_for('http.delete', function_id=id) if id else '' }}",
cancelUrl: "{{ url_for('http.overview') }}",
+ generateUrl: "{{ url_for('llm.generate_script') }}",
showDeleteButton: true
})
})
diff --git a/templates/dashboard/http_functions/new.html b/templates/dashboard/http_functions/new.html
index 5776e35..17f8df0 100644
--- a/templates/dashboard/http_functions/new.html
+++ b/templates/dashboard/http_functions/new.html
@@ -34,6 +34,7 @@ title='New HTTP Function')
executeUrl: "{{ url_for('execute_code', playground='true') }}",
saveUrl: "{{ url_for('http.new') }}",
showDeleteButton: false,
+ generateUrl: "{{ url_for('llm.generate_script') }}",
dashboardUrl: "{{ url_for('http.overview') }}"
})
})
diff --git a/templates/dashboard/timer_functions/edit.html b/templates/dashboard/timer_functions/edit.html
index a9721a9..57c3b93 100644
--- a/templates/dashboard/timer_functions/edit.html
+++ b/templates/dashboard/timer_functions/edit.html
@@ -38,6 +38,7 @@ history_url=url_for('timer.history', function_id=function_id)) }}
isTimer: true,
showTimerSettings: true,
cancelUrl: "{{ url_for('timer.overview') }}",
+ generateUrl: "{{ url_for('llm.generate_script') }}",
showPublicToggle: false,
showLogRequestToggle: false,
showLogResponseToggle: false
diff --git a/templates/dashboard/timer_functions/new.html b/templates/dashboard/timer_functions/new.html
index 537b903..5af28f0 100644
--- a/templates/dashboard/timer_functions/new.html
+++ b/templates/dashboard/timer_functions/new.html
@@ -31,6 +31,7 @@ title='New Timer Function')
saveUrl: "{{ url_for('timer.new') }}",
showDeleteButton: false,
dashboardUrl: "{{ url_for('timer.overview') }}",
+ generateUrl: "{{ url_for('llm.generate_script') }}",
isTimer: true,
showTimerSettings: true,
triggerType: 'interval',
diff --git a/templates/home.html b/templates/home.html
index f34c2e0..d35af96 100644
--- a/templates/home.html
+++ b/templates/home.html
@@ -131,6 +131,7 @@
showHeader: false,
showFunctionSettings: false,
executeUrl: "{{ url_for('execute_code', playground='true') }}",
+ generateUrl: "{{ url_for('llm.generate_script') }}"
})
})