Spaces:
Running
Running
owenkaplinsky
commited on
Commit
·
9c91326
1
Parent(s):
a9a6399
Add local API env
Browse files- project/chat.py +32 -14
- project/src/index.html +17 -1
- project/src/index.js +89 -3
- project/test.py +41 -6
- project/webpack.config.js +24 -40
project/chat.py
CHANGED
|
@@ -2,22 +2,14 @@ import os
|
|
| 2 |
from fastapi import FastAPI, Request
|
| 3 |
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
from openai import OpenAI
|
| 5 |
-
from dotenv import load_dotenv
|
| 6 |
import uvicorn
|
| 7 |
import gradio as gr
|
| 8 |
|
|
|
|
|
|
|
| 9 |
|
| 10 |
-
#
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
# Initialize OpenAI client
|
| 14 |
-
api_key = os.getenv("OPENAI_API_KEY")
|
| 15 |
-
if api_key:
|
| 16 |
-
client = OpenAI(api_key=api_key)
|
| 17 |
-
else:
|
| 18 |
-
print("Warning: OPENAI_API_KEY not found in environment variables.")
|
| 19 |
-
print("Chat functionality will be limited.")
|
| 20 |
-
client = None
|
| 21 |
|
| 22 |
# Global variable to store the latest chat context
|
| 23 |
latest_blockly_chat_code = ""
|
|
@@ -41,6 +33,20 @@ async def update_chat(request: Request):
|
|
| 41 |
print("\n[FASTAPI] Updated Blockly chat code:\n", latest_blockly_chat_code)
|
| 42 |
return {"code": latest_blockly_chat_code}
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
def create_gradio_interface():
|
| 45 |
# Hardcoded system prompt
|
| 46 |
SYSTEM_PROMPT = """You are an AI assistant created to help with Blockly MCP tasks. Users can create MCP (multi-context-protocol) servers
|
|
@@ -61,8 +67,20 @@ When the user asks questions or talks about their project, don't talk like a rob
|
|
| 61 |
are doing, state the goal or things. Remember, that info is just for you and you need to speak normally to the user."""
|
| 62 |
|
| 63 |
def chat_with_context(message, history):
|
| 64 |
-
if
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
# Get the chat context from the global variable
|
| 68 |
global latest_blockly_chat_code
|
|
|
|
| 2 |
from fastapi import FastAPI, Request
|
| 3 |
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
from openai import OpenAI
|
|
|
|
| 5 |
import uvicorn
|
| 6 |
import gradio as gr
|
| 7 |
|
| 8 |
+
# Initialize OpenAI client (will be updated when API key is set)
|
| 9 |
+
client = None
|
| 10 |
|
| 11 |
+
# Store API key in memory for this process
|
| 12 |
+
stored_api_key = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
# Global variable to store the latest chat context
|
| 15 |
latest_blockly_chat_code = ""
|
|
|
|
| 33 |
print("\n[FASTAPI] Updated Blockly chat code:\n", latest_blockly_chat_code)
|
| 34 |
return {"code": latest_blockly_chat_code}
|
| 35 |
|
| 36 |
+
@app.post("/set_api_key_chat")
|
| 37 |
+
async def set_api_key_chat(request: Request):
|
| 38 |
+
"""Receive API key from frontend and store it"""
|
| 39 |
+
global stored_api_key
|
| 40 |
+
data = await request.json()
|
| 41 |
+
api_key = data.get("api_key", "").strip()
|
| 42 |
+
|
| 43 |
+
# Store in memory and set environment variable for this process
|
| 44 |
+
stored_api_key = api_key
|
| 45 |
+
os.environ["OPENAI_API_KEY"] = api_key
|
| 46 |
+
|
| 47 |
+
print(f"[CHAT API KEY] Set OPENAI_API_KEY in chat.py environment")
|
| 48 |
+
return {"success": True}
|
| 49 |
+
|
| 50 |
def create_gradio_interface():
|
| 51 |
# Hardcoded system prompt
|
| 52 |
SYSTEM_PROMPT = """You are an AI assistant created to help with Blockly MCP tasks. Users can create MCP (multi-context-protocol) servers
|
|
|
|
| 67 |
are doing, state the goal or things. Remember, that info is just for you and you need to speak normally to the user."""
|
| 68 |
|
| 69 |
def chat_with_context(message, history):
|
| 70 |
+
# Check if API key is set and create/update client
|
| 71 |
+
global client, stored_api_key
|
| 72 |
+
|
| 73 |
+
# Use stored key or check environment
|
| 74 |
+
api_key = stored_api_key or os.environ.get("OPENAI_API_KEY")
|
| 75 |
+
|
| 76 |
+
if api_key and (not client or (hasattr(client, 'api_key') and client.api_key != api_key)):
|
| 77 |
+
try:
|
| 78 |
+
client = OpenAI(api_key=api_key)
|
| 79 |
+
except Exception as e:
|
| 80 |
+
return f"Error initializing OpenAI client: {str(e)}"
|
| 81 |
+
|
| 82 |
+
if not client or not api_key:
|
| 83 |
+
return "OpenAI API key not configured. Please set it in File > Settings in the Blockly interface."
|
| 84 |
|
| 85 |
# Get the chat context from the global variable
|
| 86 |
global latest_blockly_chat_code
|
project/src/index.html
CHANGED
|
@@ -18,7 +18,9 @@
|
|
| 18 |
<div class="dropdown">
|
| 19 |
<a href="#" id="newButton" class="dropdownItem" data-action="new">New</a>
|
| 20 |
<a href="#" id="loadButton" class="dropdownItem" data-action="open">Open</a>
|
| 21 |
-
<a href="#" id="saveButton" class="dropdownItem" data-action="download">Download</a>
|
|
|
|
|
|
|
| 22 |
</div>
|
| 23 |
</div>
|
| 24 |
|
|
@@ -189,6 +191,20 @@
|
|
| 189 |
verticalResizer.addEventListener('pointerup', onVerticalPointerUp);
|
| 190 |
});
|
| 191 |
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
</body>
|
| 193 |
|
| 194 |
</html>
|
|
|
|
| 18 |
<div class="dropdown">
|
| 19 |
<a href="#" id="newButton" class="dropdownItem" data-action="new">New</a>
|
| 20 |
<a href="#" id="loadButton" class="dropdownItem" data-action="open">Open</a>
|
| 21 |
+
<a href="#" id="saveButton" class="dropdownItem" data-action="download">Download Project</a>
|
| 22 |
+
<a href="#" id="downloadCodeButton" class="dropdownItem" data-action="downloadCode">Download Code</a>
|
| 23 |
+
<a href="#" id="settingsButton" class="dropdownItem" data-action="downloadCode">API Key</a>
|
| 24 |
</div>
|
| 25 |
</div>
|
| 26 |
|
|
|
|
| 191 |
verticalResizer.addEventListener('pointerup', onVerticalPointerUp);
|
| 192 |
});
|
| 193 |
</script>
|
| 194 |
+
|
| 195 |
+
<!-- API Key Modal -->
|
| 196 |
+
<div id="apiKeyModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 9999; align-items: center; justify-content: center;">
|
| 197 |
+
<div style="background: white; padding: 30px; border-radius: 10px; width: 90%; max-width: 500px; box-shadow: 0 10px 30px rgba(0,0,0,0.2);">
|
| 198 |
+
<h2 style="margin-top: 0; margin-bottom: 20px; color: #333;">Settings</h2>
|
| 199 |
+
<label for="apiKeyInput" style="display: block; margin-bottom: 10px; color: #666; font-size: 14px;">OpenAI API Key:</label>
|
| 200 |
+
<input type="password" id="apiKeyInput" style="width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; box-sizing: border-box;" placeholder="sk-...">
|
| 201 |
+
<p style="margin-top: 10px; color: #999; font-size: 12px;">Your API key will be stored securely for this session.</p>
|
| 202 |
+
<div style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
|
| 203 |
+
<button id="cancelApiKey" style="padding: 10px 20px; background: #e5e7eb; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">Cancel</button>
|
| 204 |
+
<button id="saveApiKey" style="padding: 10px 20px; background: #6366f1; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">Save</button>
|
| 205 |
+
</div>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
</body>
|
| 209 |
|
| 210 |
</html>
|
project/src/index.js
CHANGED
|
@@ -84,7 +84,7 @@ saveButton.addEventListener("click", () => {
|
|
| 84 |
const state = Blockly.serialization.workspaces.save(ws);
|
| 85 |
const stateString = JSON.stringify(state);
|
| 86 |
|
| 87 |
-
var filename = "
|
| 88 |
var element = document.createElement('a');
|
| 89 |
|
| 90 |
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(stateString));
|
|
@@ -95,6 +95,87 @@ saveButton.addEventListener("click", () => {
|
|
| 95 |
document.body.removeChild(element);
|
| 96 |
});
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
const weatherText = `{"workspaceComments":[{"height":120,"width":479,"id":"XI5[EHp-Ow+kinXf6n5y","x":51.234375,"y":-83,"text":"Gets temperature of location with a latitude and a longitude.\\n\\nThe API requires a minimum of one decimal point to work."}],"blocks":{"languageVersion":0,"blocks":[{"type":"create_mcp","id":")N.HEG1x]Z/,k#TeWr,S","x":50,"y":50,"deletable":false,"extraState":{"inputCount":2,"inputNames":["latitude","longitude"],"inputTypes":["integer","integer"],"outputCount":1,"outputNames":["output0"],"outputTypes":["string"],"toolCount":0},"inputs":{"X0":{"block":{"type":"input_reference_latitude","id":"]3mj!y}qfRt+!okheU7L","deletable":false,"extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"latitude"}}},"X1":{"block":{"type":"input_reference_longitude","id":"Do/{HFNGSd.!;POiKS?D","deletable":false,"extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"longitude"}}},"R0":{"block":{"type":"in_json","id":"R|j?_8s^H{l0;UZ-oQt3","fields":{"NAME":"temperature_2m"},"inputs":{"JSON":{"block":{"type":"in_json","id":"X=M,R1@7bRjJVZIPi[qD","fields":{"NAME":"current"},"inputs":{"JSON":{"block":{"type":"call_api","id":"^(.vyM.yni08S~c1EBm=","fields":{"METHOD":"GET"},"inputs":{"URL":{"shadow":{"type":"text","id":"}.T;_U_OsRS)B_y09p % { ","fields":{"TEXT":""}},"block":{"type":"text_replace","id":"OwH9uERJPTGQG!UER#ch","inputs":{"FROM":{"shadow":{"type":"text","id":"ya05#^ 7 % UbUeXX#eDSmH","fields":{"TEXT":"{latitude}"}}},"TO":{"shadow":{"type":"text","id":": _ZloQuh9c-MNf-U]!k5","fields":{"TEXT":""}},"block":{"type":"input_reference_latitude","id":"?%@)3sErZ)}=#4ags#gu","extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"latitude"}}},"TEXT":{"shadow":{"type":"text","id":"w@zsP)m6:WjkUp,ln3$x","fields":{"TEXT":""}},"block":{"type":"text_replace","id":"ImNPsvzD7r^+1MJ%IirV","inputs":{"FROM":{"shadow":{"type":"text","id":"%o(3rro?WLIFpmE0#MMM","fields":{"TEXT":"{longitude}"}}},"TO":{"shadow":{"type":"text","id":"Zpql-%oJ_sdSi | r |* er | ","fields":{"TEXT":""}},"block":{"type":"input_reference_longitude","id":"WUgiJP$X + zY#f$5nhnTX","extraState":{"ownerBlockId":") N.HEG1x]Z /, k#TeWr, S"},"fields":{"VARNAME":"longitude"}}},"TEXT":{"shadow":{"type":"text","id":", (vw$o_s7P = b4P; 8]}yj","fields":{"TEXT":"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m,wind_speed_10m"}}}}}}}}}}}}}}}}}}}}]}}`;
|
| 99 |
weatherButton.addEventListener("click", () => {
|
| 100 |
try {
|
|
@@ -140,8 +221,13 @@ const updateCode = () => {
|
|
| 140 |
const call = `def llm_call(prompt, model):
|
| 141 |
from openai import OpenAI
|
| 142 |
import os
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
messages = [{"role": "user", "content": prompt}]
|
| 147 |
|
|
|
|
| 84 |
const state = Blockly.serialization.workspaces.save(ws);
|
| 85 |
const stateString = JSON.stringify(state);
|
| 86 |
|
| 87 |
+
var filename = "mcpBlockly_project.txt";
|
| 88 |
var element = document.createElement('a');
|
| 89 |
|
| 90 |
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(stateString));
|
|
|
|
| 95 |
document.body.removeChild(element);
|
| 96 |
});
|
| 97 |
|
| 98 |
+
// Download Code button
|
| 99 |
+
const downloadCodeButton = document.querySelector('#downloadCodeButton');
|
| 100 |
+
downloadCodeButton.addEventListener("click", () => {
|
| 101 |
+
// Get the current generated code
|
| 102 |
+
const codeEl = document.querySelector('#generatedCode code');
|
| 103 |
+
const code = codeEl ? codeEl.textContent : '';
|
| 104 |
+
|
| 105 |
+
if (!code) {
|
| 106 |
+
alert('No code to download');
|
| 107 |
+
return;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
var filename = "mcp_generated.py";
|
| 111 |
+
var element = document.createElement('a');
|
| 112 |
+
|
| 113 |
+
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(code));
|
| 114 |
+
element.setAttribute('download', filename);
|
| 115 |
+
element.style.display = 'none';
|
| 116 |
+
document.body.appendChild(element);
|
| 117 |
+
element.click();
|
| 118 |
+
document.body.removeChild(element);
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
// Settings button and API Key Modal
|
| 122 |
+
const settingsButton = document.querySelector('#settingsButton');
|
| 123 |
+
const apiKeyModal = document.querySelector('#apiKeyModal');
|
| 124 |
+
const apiKeyInput = document.querySelector('#apiKeyInput');
|
| 125 |
+
const saveApiKeyButton = document.querySelector('#saveApiKey');
|
| 126 |
+
const cancelApiKeyButton = document.querySelector('#cancelApiKey');
|
| 127 |
+
|
| 128 |
+
settingsButton.addEventListener("click", () => {
|
| 129 |
+
apiKeyModal.style.display = 'flex';
|
| 130 |
+
|
| 131 |
+
// Load current API key from backend
|
| 132 |
+
fetch("http://127.0.0.1:7860/get_api_key", {
|
| 133 |
+
method: "GET",
|
| 134 |
+
})
|
| 135 |
+
.then(response => response.json())
|
| 136 |
+
.then(data => {
|
| 137 |
+
apiKeyInput.value = data.api_key || '';
|
| 138 |
+
})
|
| 139 |
+
.catch(err => {
|
| 140 |
+
console.error("Error loading API key:", err);
|
| 141 |
+
});
|
| 142 |
+
});
|
| 143 |
+
|
| 144 |
+
saveApiKeyButton.addEventListener("click", () => {
|
| 145 |
+
const apiKey = apiKeyInput.value;
|
| 146 |
+
|
| 147 |
+
// Save API key to both backend servers (test.py and chat.py)
|
| 148 |
+
Promise.all([
|
| 149 |
+
fetch("http://127.0.0.1:7860/set_api_key", {
|
| 150 |
+
method: "POST",
|
| 151 |
+
headers: { "Content-Type": "application/json" },
|
| 152 |
+
body: JSON.stringify({ api_key: apiKey }),
|
| 153 |
+
}),
|
| 154 |
+
fetch("http://127.0.0.1:7861/set_api_key_chat", {
|
| 155 |
+
method: "POST",
|
| 156 |
+
headers: { "Content-Type": "application/json" },
|
| 157 |
+
body: JSON.stringify({ api_key: apiKey }),
|
| 158 |
+
})
|
| 159 |
+
])
|
| 160 |
+
.then(async (responses) => {
|
| 161 |
+
const results = await Promise.all(responses.map(r => r.json()));
|
| 162 |
+
if (results.every(r => r.success)) {
|
| 163 |
+
alert('API key saved successfully');
|
| 164 |
+
apiKeyModal.style.display = 'none';
|
| 165 |
+
} else {
|
| 166 |
+
alert('Failed to save API key to all services');
|
| 167 |
+
}
|
| 168 |
+
})
|
| 169 |
+
.catch(err => {
|
| 170 |
+
console.error("Error saving API key:", err);
|
| 171 |
+
alert('Failed to save API key');
|
| 172 |
+
});
|
| 173 |
+
});
|
| 174 |
+
|
| 175 |
+
cancelApiKeyButton.addEventListener("click", () => {
|
| 176 |
+
apiKeyModal.style.display = 'none';
|
| 177 |
+
});
|
| 178 |
+
|
| 179 |
const weatherText = `{"workspaceComments":[{"height":120,"width":479,"id":"XI5[EHp-Ow+kinXf6n5y","x":51.234375,"y":-83,"text":"Gets temperature of location with a latitude and a longitude.\\n\\nThe API requires a minimum of one decimal point to work."}],"blocks":{"languageVersion":0,"blocks":[{"type":"create_mcp","id":")N.HEG1x]Z/,k#TeWr,S","x":50,"y":50,"deletable":false,"extraState":{"inputCount":2,"inputNames":["latitude","longitude"],"inputTypes":["integer","integer"],"outputCount":1,"outputNames":["output0"],"outputTypes":["string"],"toolCount":0},"inputs":{"X0":{"block":{"type":"input_reference_latitude","id":"]3mj!y}qfRt+!okheU7L","deletable":false,"extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"latitude"}}},"X1":{"block":{"type":"input_reference_longitude","id":"Do/{HFNGSd.!;POiKS?D","deletable":false,"extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"longitude"}}},"R0":{"block":{"type":"in_json","id":"R|j?_8s^H{l0;UZ-oQt3","fields":{"NAME":"temperature_2m"},"inputs":{"JSON":{"block":{"type":"in_json","id":"X=M,R1@7bRjJVZIPi[qD","fields":{"NAME":"current"},"inputs":{"JSON":{"block":{"type":"call_api","id":"^(.vyM.yni08S~c1EBm=","fields":{"METHOD":"GET"},"inputs":{"URL":{"shadow":{"type":"text","id":"}.T;_U_OsRS)B_y09p % { ","fields":{"TEXT":""}},"block":{"type":"text_replace","id":"OwH9uERJPTGQG!UER#ch","inputs":{"FROM":{"shadow":{"type":"text","id":"ya05#^ 7 % UbUeXX#eDSmH","fields":{"TEXT":"{latitude}"}}},"TO":{"shadow":{"type":"text","id":": _ZloQuh9c-MNf-U]!k5","fields":{"TEXT":""}},"block":{"type":"input_reference_latitude","id":"?%@)3sErZ)}=#4ags#gu","extraState":{"ownerBlockId":")N.HEG1x]Z/,k#TeWr,S"},"fields":{"VARNAME":"latitude"}}},"TEXT":{"shadow":{"type":"text","id":"w@zsP)m6:WjkUp,ln3$x","fields":{"TEXT":""}},"block":{"type":"text_replace","id":"ImNPsvzD7r^+1MJ%IirV","inputs":{"FROM":{"shadow":{"type":"text","id":"%o(3rro?WLIFpmE0#MMM","fields":{"TEXT":"{longitude}"}}},"TO":{"shadow":{"type":"text","id":"Zpql-%oJ_sdSi | r |* er | ","fields":{"TEXT":""}},"block":{"type":"input_reference_longitude","id":"WUgiJP$X + zY#f$5nhnTX","extraState":{"ownerBlockId":") N.HEG1x]Z /, k#TeWr, S"},"fields":{"VARNAME":"longitude"}}},"TEXT":{"shadow":{"type":"text","id":", (vw$o_s7P = b4P; 8]}yj","fields":{"TEXT":"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m,wind_speed_10m"}}}}}}}}}}}}}}}}}}}}]}}`;
|
| 180 |
weatherButton.addEventListener("click", () => {
|
| 181 |
try {
|
|
|
|
| 221 |
const call = `def llm_call(prompt, model):
|
| 222 |
from openai import OpenAI
|
| 223 |
import os
|
| 224 |
+
|
| 225 |
+
api_key = os.environ.get("OPENAI_API_KEY")
|
| 226 |
+
|
| 227 |
+
if not api_key:
|
| 228 |
+
return "Error: OpenAI API key not configured. Please set it in File > Settings"
|
| 229 |
+
|
| 230 |
+
client = OpenAI(api_key=api_key)
|
| 231 |
|
| 232 |
messages = [{"role": "user", "content": prompt}]
|
| 233 |
|
project/test.py
CHANGED
|
@@ -3,7 +3,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
| 3 |
import gradio as gr
|
| 4 |
import uvicorn
|
| 5 |
import re
|
| 6 |
-
|
| 7 |
|
| 8 |
app = FastAPI()
|
| 9 |
|
|
@@ -15,9 +15,8 @@ app.add_middleware(
|
|
| 15 |
allow_headers=["*"],
|
| 16 |
)
|
| 17 |
|
| 18 |
-
load_dotenv()
|
| 19 |
-
|
| 20 |
latest_blockly_code = ""
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
@app.post("/update_code")
|
|
@@ -28,12 +27,48 @@ async def update_code(request: Request):
|
|
| 28 |
print("\n[FASTAPI] Updated Blockly code:\n", latest_blockly_code)
|
| 29 |
return {"ok": True}
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
def execute_blockly_logic(user_inputs):
|
| 33 |
-
global latest_blockly_code
|
| 34 |
if not latest_blockly_code.strip():
|
| 35 |
return "No Blockly code available"
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
result = ""
|
| 38 |
|
| 39 |
# More comprehensive filtering of Gradio-related code
|
|
@@ -232,5 +267,5 @@ demo = build_interface()
|
|
| 232 |
app = gr.mount_gradio_app(app, demo, path="/")
|
| 233 |
|
| 234 |
if __name__ == "__main__":
|
| 235 |
-
print("[BOOT] Running Gradio+FastAPI combo on http://127.0.0.1:
|
| 236 |
-
uvicorn.run(app, host="0.0.0.0", port=
|
|
|
|
| 3 |
import gradio as gr
|
| 4 |
import uvicorn
|
| 5 |
import re
|
| 6 |
+
import os
|
| 7 |
|
| 8 |
app = FastAPI()
|
| 9 |
|
|
|
|
| 15 |
allow_headers=["*"],
|
| 16 |
)
|
| 17 |
|
|
|
|
|
|
|
| 18 |
latest_blockly_code = ""
|
| 19 |
+
stored_api_key = "" # Store the API key in memory
|
| 20 |
|
| 21 |
|
| 22 |
@app.post("/update_code")
|
|
|
|
| 27 |
print("\n[FASTAPI] Updated Blockly code:\n", latest_blockly_code)
|
| 28 |
return {"ok": True}
|
| 29 |
|
| 30 |
+
@app.get("/get_api_key")
|
| 31 |
+
async def get_api_key_endpoint():
|
| 32 |
+
"""Get the current API key from memory"""
|
| 33 |
+
global stored_api_key
|
| 34 |
+
api_key = stored_api_key or os.environ.get("OPENAI_API_KEY", "")
|
| 35 |
+
|
| 36 |
+
# Mask the API key for security (show only first 7 and last 4 characters)
|
| 37 |
+
if api_key and len(api_key) > 15:
|
| 38 |
+
masked_key = api_key[:7] + '...' + api_key[-4:]
|
| 39 |
+
else:
|
| 40 |
+
masked_key = api_key if api_key else ""
|
| 41 |
+
|
| 42 |
+
return {"api_key": masked_key}
|
| 43 |
+
|
| 44 |
+
@app.post("/set_api_key")
|
| 45 |
+
async def set_api_key_endpoint(request: Request):
|
| 46 |
+
"""Save API key to environment variable"""
|
| 47 |
+
global stored_api_key
|
| 48 |
+
data = await request.json()
|
| 49 |
+
api_key = data.get("api_key", "").strip()
|
| 50 |
+
|
| 51 |
+
try:
|
| 52 |
+
# Store in memory and set environment variable
|
| 53 |
+
stored_api_key = api_key
|
| 54 |
+
os.environ["OPENAI_API_KEY"] = api_key
|
| 55 |
+
|
| 56 |
+
print(f"[API KEY] Set OPENAI_API_KEY in environment")
|
| 57 |
+
return {"success": True}
|
| 58 |
+
except Exception as e:
|
| 59 |
+
print(f"Error setting API key: {e}")
|
| 60 |
+
return {"success": False, "error": str(e)}
|
| 61 |
+
|
| 62 |
|
| 63 |
def execute_blockly_logic(user_inputs):
|
| 64 |
+
global latest_blockly_code, stored_api_key
|
| 65 |
if not latest_blockly_code.strip():
|
| 66 |
return "No Blockly code available"
|
| 67 |
|
| 68 |
+
# Ensure API key is set in environment before executing
|
| 69 |
+
if stored_api_key:
|
| 70 |
+
os.environ["OPENAI_API_KEY"] = stored_api_key
|
| 71 |
+
|
| 72 |
result = ""
|
| 73 |
|
| 74 |
# More comprehensive filtering of Gradio-related code
|
|
|
|
| 267 |
app = gr.mount_gradio_app(app, demo, path="/")
|
| 268 |
|
| 269 |
if __name__ == "__main__":
|
| 270 |
+
print("[BOOT] Running Gradio+FastAPI combo on http://127.0.0.1:7860")
|
| 271 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
project/webpack.config.js
CHANGED
|
@@ -1,59 +1,43 @@
|
|
| 1 |
-
const path = require('path');
|
| 2 |
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
|
|
| 3 |
|
| 4 |
-
|
| 5 |
-
const config = {
|
| 6 |
entry: './src/index.js',
|
| 7 |
output: {
|
| 8 |
-
// Compile the source files into a bundle.
|
| 9 |
-
filename: 'bundle.js',
|
| 10 |
path: path.resolve(__dirname, 'dist'),
|
|
|
|
| 11 |
clean: true,
|
| 12 |
},
|
| 13 |
-
// Enable webpack-dev-server to get hot refresh of the app.
|
| 14 |
-
devServer: {
|
| 15 |
-
static: './build',
|
| 16 |
-
},
|
| 17 |
module: {
|
| 18 |
rules: [
|
| 19 |
{
|
| 20 |
-
// Load CSS files. They can be imported into JS files.
|
| 21 |
test: /\.css$/i,
|
| 22 |
use: ['style-loader', 'css-loader'],
|
| 23 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
],
|
| 25 |
},
|
| 26 |
plugins: [
|
| 27 |
-
// Generate the HTML index page based on our template.
|
| 28 |
-
// This will output the same index page with the bundle we
|
| 29 |
-
// created above added in a script tag.
|
| 30 |
new HtmlWebpackPlugin({
|
| 31 |
-
template: 'src/index.html',
|
| 32 |
}),
|
| 33 |
],
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
config.module.rules.push({
|
| 49 |
-
test: /(blockly\/.*\.js)$/,
|
| 50 |
-
use: [require.resolve('source-map-loader')],
|
| 51 |
-
enforce: 'pre',
|
| 52 |
-
});
|
| 53 |
-
|
| 54 |
-
// Ignore spurious warnings from source-map-loader
|
| 55 |
-
// It can't find source maps for some Closure modules and that is expected
|
| 56 |
-
config.ignoreWarnings = [/Failed to parse source map/];
|
| 57 |
-
}
|
| 58 |
-
return config;
|
| 59 |
-
};
|
|
|
|
|
|
|
| 1 |
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
| 2 |
+
const path = require('path');
|
| 3 |
|
| 4 |
+
module.exports = {
|
|
|
|
| 5 |
entry: './src/index.js',
|
| 6 |
output: {
|
|
|
|
|
|
|
| 7 |
path: path.resolve(__dirname, 'dist'),
|
| 8 |
+
filename: 'bundle.js',
|
| 9 |
clean: true,
|
| 10 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
module: {
|
| 12 |
rules: [
|
| 13 |
{
|
|
|
|
| 14 |
test: /\.css$/i,
|
| 15 |
use: ['style-loader', 'css-loader'],
|
| 16 |
},
|
| 17 |
+
{
|
| 18 |
+
test: /\.js$/,
|
| 19 |
+
exclude: /node_modules/, // Exclude all node_modules from source map processing
|
| 20 |
+
use: ["source-map-loader"],
|
| 21 |
+
enforce: "pre"
|
| 22 |
+
}
|
| 23 |
],
|
| 24 |
},
|
| 25 |
plugins: [
|
|
|
|
|
|
|
|
|
|
| 26 |
new HtmlWebpackPlugin({
|
| 27 |
+
template: './src/index.html',
|
| 28 |
}),
|
| 29 |
],
|
| 30 |
+
devServer: {
|
| 31 |
+
static: {
|
| 32 |
+
directory: path.join(__dirname, 'public'),
|
| 33 |
+
},
|
| 34 |
+
compress: true,
|
| 35 |
+
port: 8081,
|
| 36 |
+
hot: true,
|
| 37 |
+
open: true,
|
| 38 |
+
},
|
| 39 |
+
resolve: {
|
| 40 |
+
extensions: ['.js', '.json', '.css'],
|
| 41 |
+
},
|
| 42 |
+
devtool: 'source-map',
|
| 43 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|