Spaces:
Runtime error
Runtime error
| FROM node:22-slim | |
| # 1. Install system utilities required for git operations | |
| RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* | |
| # 2. Configure directory structure with correct permission scopes for Hugging Face (User 1000) | |
| WORKDIR /appw | |
| RUN mkdir -p /appw/server/workspaces /appw/server/public && chown -R 1000:1000 /appw | |
| # Switch to the non-root node user | |
| USER 1000 | |
| # 3. Clone repository to a temporary directory, merge files into the active workdir, and run npm install | |
| RUN git clone https://github.com/Electroiscoding/Swades-Agent.git /tmp/swades \ | |
| && cp -r /tmp/swades/. /appw/ \ | |
| && rm -rf /tmp/swades \ | |
| && npm install | |
| # 4. Write the Backend Broker | |
| RUN cat << 'EOF' > server/index.js | |
| const express = require("express"); | |
| const { spawn, execSync } = require("child_process"); | |
| const http = require("http"); | |
| const WebSocket = require("ws"); | |
| const path = require("path"); | |
| const fs = require("fs"); | |
| const app = express(); | |
| const server = http.createServer(app); | |
| const wss = new WebSocket.Server({ server }); | |
| const PORT = process.env.PORT || 7860; | |
| const WORKSPACE_BASE = path.join(__dirname, "workspaces"); | |
| const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || ""; | |
| const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET || ""; | |
| const activeWebsocketPool = new Map(); | |
| if (!fs.existsSync(WORKSPACE_BASE)) { | |
| fs.mkdirSync(WORKSPACE_BASE, { recursive: true }); | |
| } | |
| app.use(express.static(path.join(__dirname, "public"))); | |
| app.get("/login/github", (req, res) => { | |
| const { socketId } = req.query; | |
| if (!socketId) return res.send("Active channel window connection identifier tracking key missing."); | |
| res.redirect(`https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&scope=repo&state=${socketId}`); | |
| }); | |
| app.get("/callback", async (req, res) => { | |
| const { code, state } = req.query; | |
| if (!code || !state) return res.send("Authorization metadata parameters corrupted."); | |
| try { | |
| const response = await fetch("https://github.com/login/oauth/access_token", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", "Accept": "application/json" }, | |
| body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, client_secret: GITHUB_CLIENT_SECRET, code }) | |
| }); | |
| const data = await response.json(); | |
| const token = data.access_token; | |
| const userRes = await fetch("https://api.github.com/user", { | |
| headers: { "Authorization": `token ${token}`, "User-Agent": "Swades-Orchestrator" } | |
| }); | |
| const userData = await userRes.json(); | |
| const reposRes = await fetch("https://api.github.com/user/repos?per_page=100&sort=updated", { | |
| headers: { "Authorization": `token ${token}`, "User-Agent": "Swades-Orchestrator" } | |
| }); | |
| const reposData = await reposRes.json(); | |
| const formattedRepos = Array.isArray(reposData) ? reposData.map(r => ({ name: r.full_name, url: r.clone_url })) : []; | |
| const clientSocket = activeWebsocketPool.get(state); | |
| if (clientSocket && clientSocket.readyState === WebSocket.OPEN) { | |
| clientSocket.send(JSON.stringify({ | |
| type: "AUTH_COMPLETE", | |
| token: token, | |
| username: userData.login, | |
| avatar: userData.avatar_url, | |
| repos: formattedRepos | |
| })); | |
| } | |
| res.send(` | |
| <script> | |
| window.close(); | |
| </script> | |
| Identity authenticated. Syncing dashboard... | |
| `); | |
| } catch (err) { | |
| res.status(500).send("Handshake Sync breakdown: " + err.message); | |
| } | |
| }); | |
| wss.on("connection", (ws) => { | |
| let boundSocketId = null; | |
| let agentProcess = null; | |
| ws.on("message", async (message) => { | |
| try { | |
| const payload = JSON.parse(message); | |
| if (payload.type === "register_channel") { | |
| boundSocketId = payload.socketId; | |
| activeWebsocketPool.set(boundSocketId, ws); | |
| return; | |
| } | |
| if (payload.type === "hydrate_repos") { | |
| try { | |
| const reposRes = await fetch("https://api.github.com/user/repos?per_page=100&sort=updated", { | |
| headers: { "Authorization": `token ${payload.token}`, "User-Agent": "Swades-Orchestrator" } | |
| }); | |
| const reposData = await reposRes.json(); | |
| const formattedRepos = Array.isArray(reposData) ? reposData.map(r => ({ name: r.full_name, url: r.clone_url })) : []; | |
| ws.send(JSON.stringify({ | |
| type: "REPO_HYDRATION_COMPLETE", | |
| repos: formattedRepos | |
| })); | |
| } catch (e) { | |
| ws.send(JSON.stringify({ type: "system", data: "⚠️ Session validation expired. Please re-authenticate." })); | |
| } | |
| return; | |
| } | |
| if (payload.type === "run") { | |
| if (agentProcess) { | |
| return ws.send(JSON.stringify({ type: "system", data: "[Busy] Thread execution lock active." })); | |
| } | |
| const { task, autonomous, userApiKey, userModel, repoUrl, githubToken } = payload; | |
| if (!userApiKey || !repoUrl || !githubToken) { | |
| return ws.send(JSON.stringify({ type: "system", data: "❌ Setup Error: Verification attributes empty." })); | |
| } | |
| const sessionID = "session_" + Date.now(); | |
| const userWorkspace = path.join(WORKSPACE_BASE, sessionID); | |
| ws.send(JSON.stringify({ type: "system", data: "⏳ Provisioning clean workspace sandbox container nodes..." })); | |
| try { | |
| const cleanRepo = repoUrl.trim().replace(/^https?:\/\//, ""); | |
| const authenticatedRepoUrl = `https://x-access-token:${githubToken}@${cleanRepo}`; | |
| execSync(`git clone ${authenticatedRepoUrl} ${userWorkspace}`, { stdio: "ignore" }); | |
| execSync(`git remote set-url origin ${authenticatedRepoUrl}`, { cwd: userWorkspace }); | |
| ws.send(JSON.stringify({ type: "system", data: "🧠 Activating Swades context engine layer..." })); | |
| const args = [path.join(__dirname, "../src/index.js"), task]; | |
| if (autonomous) args.push("--autonomous"); | |
| agentProcess = spawn("node", args, { | |
| cwd: path.join(__dirname, ".."), | |
| env: { | |
| ...process.env, | |
| API_KEY: userApiKey, | |
| BASE_URL: "https://openrouter.ai/api/v1", | |
| MODEL: userModel || "openrouter/free", | |
| WORKDIR: userWorkspace | |
| } | |
| }); | |
| agentProcess.stdout.on("data", (data) => { | |
| ws.send(JSON.stringify({ type: "output", data: data.toString() })); | |
| }); | |
| agentProcess.stderr.on("data", (data) => { | |
| ws.send(JSON.stringify({ type: "output", data: data.toString() })); | |
| }); | |
| agentProcess.on("close", (code) => { | |
| agentProcess = null; | |
| ws.send(JSON.stringify({ type: "system", data: "🏁 Task sequence terminated. Evaluating modification tree..." })); | |
| try { | |
| const statusCheck = execSync(`git status --porcelain`, { cwd: userWorkspace }).toString().trim(); | |
| if (!statusCheck) { | |
| ws.send(JSON.stringify({ type: "system", data: "ℹ No changes detected. Tree matches origin exactly. Skipping branch creation." })); | |
| return; | |
| } | |
| const branchName = `swades-patch-${Math.floor(1000 + Math.random() * 9000)}`; | |
| execSync(`git checkout -b ${branchName}`, { cwd: userWorkspace, stdio: "ignore" }); | |
| execSync(`git config --local user.email "agent@swades.ai" && git config --local user.name "Swades Agent Hub"`, { cwd: userWorkspace }); | |
| execSync(`git add . && git commit -m "feat: automated engineering updates via Swades ReAct core loop"`, { cwd: userWorkspace }); | |
| execSync(`git push origin ${branchName}`, { cwd: userWorkspace }); | |
| const cleanRepoWebPath = repoUrl.replace(/\.git$/, "").replace(/\/$/, ""); | |
| const prUrl = `${cleanRepoWebPath}/tree/${branchName}`; | |
| ws.send(JSON.stringify({ | |
| type: "success_link", | |
| data: `\x1b[32m✔ Engineering adjustments published upstream!\x1b[0m`, | |
| link: prUrl | |
| })); | |
| } catch (gitErr) { | |
| ws.send(JSON.stringify({ type: "system", data: `❌ Workspace synchronization failed: ${gitErr.message}` })); | |
| } | |
| }); | |
| } catch (setupErr) { | |
| ws.send(JSON.stringify({ type: "system", data: `❌ Workspace Initialization Failure: ${setupErr.message}` })); | |
| } | |
| } | |
| if (payload.type === "input" && agentProcess) { | |
| agentProcess.stdin.write(payload.data); | |
| } | |
| } catch (e) { | |
| ws.send(JSON.stringify({ type: "system", data: e.message })); | |
| } | |
| }); | |
| ws.on("close", () => { | |
| if (agentProcess) agentProcess.kill(); | |
| if (boundSocketId) activeWebsocketPool.delete(boundSocketId); | |
| }); | |
| }); | |
| server.listen(PORT, "0.0.0.0", () => console.log("System Online")); | |
| EOF | |
| # 5. Write the Premium Silicon Valley Chat Interface Layout with Infinite Session Persistence | |
| RUN cat << 'EOF' > server/public/index.html | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Swades Core Workspace</title> | |
| <style> | |
| :root { --bg: #000000; --panel: #0a0a0a; --border: #1c1c1c; --text: #f5f5f5; --muted: #737373; --user-msg: #171717; --accent: #ffffff; } | |
| body { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; padding: 0; display: flex; flex-direction: column; height: 100vh; -webkit-font-smoothing: antialiased; } | |
| .config-bar { background: var(--panel); border-bottom: 1px solid var(--border); padding: 16px 24px; display: flex; justify-content: space-between; align-items: center; gap: 20px; } | |
| .inputs-wrapper { display: flex; gap: 12px; flex: 1; align-items: center; } | |
| input { background: #000000; border: 1px solid var(--border); border-radius: 6px; padding: 10px 14px; color: var(--text); font-family: "SF Mono", Menlo, monospace; font-size: 13px; box-sizing: border-box; transition: border-color 0.15s ease; } | |
| input:focus { border-color: var(--muted); outline: none; } | |
| .repo-select-container { position: relative; width: 320px; display: none; } | |
| .dropdown-menu { position: absolute; top: calc(100% + 6px); left: 0; width: 100%; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; max-height: 240px; overflow-y: auto; z-index: 100; display: none; box-shadow: 0 10px 30px rgba(0,0,0,0.7); } | |
| .dropdown-item { padding: 10px 14px; font-family: "SF Mono", monospace; font-size: 13px; color: #d4d4d4; cursor: pointer; border-bottom: 1px solid #141414; } | |
| .dropdown-item:hover { background: #141414; color: #fff; } | |
| .user-profile-identity { display: none; align-items: center; gap: 14px; } | |
| .user-profile-identity img { width: 34px; height: 34px; border-radius: 50%; border: 1px solid var(--border); } | |
| .user-profile-identity .meta { display: flex; flex-direction: column; gap: 2px; } | |
| .user-profile-identity .name { font-size: 13px; font-weight: 500; font-family: "SF Mono", monospace; color: var(--text); } | |
| .user-profile-identity .logout-trigger { font-size: 11px; color: var(--muted); cursor: pointer; text-decoration: underline; background: transparent; border: none; padding: 0; text-align: left; } | |
| .user-profile-identity .logout-trigger:hover { color: #ef4444; } | |
| .oauth-btn { background: #24292e; color: #fff; border: 1px solid var(--border); border-radius: 6px; padding: 10px 18px; font-size: 13px; font-weight: 500; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: background 0.15s; height: 38px; box-sizing: border-box; } | |
| .oauth-btn:hover { background: #2f363d; } | |
| .chat-container { flex: 1; overflow-y: auto; padding: 40px 24px; display: flex; flex-direction: column; gap: 24px; max-width: 840px; width: 100%; margin: 0 auto; box-sizing: border-box; } | |
| .message { display: flex; flex-direction: column; gap: 6px; max-width: 100%; } | |
| .message .sender { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); } | |
| .message .bubble { padding: 16px 20px; border-radius: 12px; border: 1px solid var(--border); font-size: 14px; line-height: 1.6; white-space: pre-wrap; position: relative; } | |
| .message.user { align-self: flex-end; max-width: 85%; } | |
| .message.user .bubble { background: var(--user-msg); border-color: transparent; } | |
| .message.agent .bubble { background: var(--panel); font-family: "SF Mono", Menlo, monospace; font-size: 13px; color: #d4d4d4; border-color: var(--border); } | |
| .message.system-status .bubble { background: transparent; border-style: dashed; color: var(--muted); font-size: 13px; } | |
| .message.success-status .bubble { border-color: #262626; background: #0a0a0a; color: #e5e5e5; display: flex; flex-direction: column; gap: 14px; } | |
| .action-btn-link { display: inline-flex; align-items: center; justify-content: center; background: var(--accent); color: var(--bg); font-weight: 500; text-decoration: none; padding: 10px 16px; border-radius: 6px; font-size: 13px; font-family: -apple-system, sans-serif; transition: opacity 0.15s; width: fit-content; } | |
| .action-btn-link:hover { opacity: 0.9; } | |
| .footer-input-area { border-top: 1px solid var(--border); background: var(--bg); padding: 24px; } | |
| .input-box-wrapper { max-width: 840px; margin: 0 auto; display: flex; gap: 12px; align-items: center; background: var(--panel); border: 1px solid var(--border); padding: 8px 8px 8px 16px; border-radius: 24px; } | |
| .input-box-wrapper input { flex: 1; border: none; background: transparent; padding: 8px 0; color: #fff; font-size: 14px; width: 100%; } | |
| .input-box-wrapper input:focus { outline: none; } | |
| .controls { display: flex; align-items: center; gap: 16px; } | |
| button.send { padding: 10px 20px; background: var(--accent); color: var(--bg); border: none; border-radius: 18px; cursor: pointer; font-weight: 500; font-size: 13px; } | |
| .toggle { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); cursor: pointer; user-select: none; } | |
| .toggle input { width: auto; margin: 0; accent-color: var(--text); } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="config-bar"> | |
| <div class="inputs-wrapper"> | |
| <input type="password" id="key" style="width: 240px;" placeholder="OpenRouter API Key"> | |
| <input type="text" id="model" style="width: 160px;" placeholder="Model" value="openrouter/free"> | |
| <div class="repo-select-container" id="repoContainer"> | |
| <input type="text" id="repoSearch" placeholder="Search your repositories..." autocomplete="off"> | |
| <div class="dropdown-menu" id="repoDropdown"></div> | |
| </div> | |
| </div> | |
| <button class="oauth-btn" id="githubAuthBtn"> | |
| <svg height="16" width="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg> | |
| <span>Connect GitHub</span> | |
| </button> | |
| <div class="user-profile-identity" id="userProfile"> | |
| <img id="userAvatar" src="" alt="Avatar"> | |
| <div class="meta"> | |
| <span id="usernameDisplay" class="name"></span> | |
| <button id="logoutBtn" class="logout-trigger">Log Out</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chat-container" id="chatContainer"> | |
| <div class="message system-status"> | |
| <div class="sender">System</div> | |
| <div class="bubble" id="statusMessage">● Swades session channel online. Checking authentication state layers...</div> | |
| </div> | |
| </div> | |
| <div class="footer-input-area"> | |
| <div class="input-box-wrapper"> | |
| <input type="text" id="taskPrompt" placeholder="Instruct the agent workflow scope..."> | |
| <div class="controls"> | |
| <label class="toggle"><input type="checkbox" id="autoMode" checked> director_autonomy</label> | |
| <button class="send" id="sendBtn">Send</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const localSocketChannelId = "ch_" + Math.random().toString(36).substring(2, 15); | |
| let activeToken = ""; | |
| let selectedRepoUrl = ""; | |
| let repositoryCacheList = []; | |
| const chatContainer = document.getElementById("chatContainer"); | |
| const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; | |
| const socket = new WebSocket(`${protocol}//${window.location.host}`); | |
| let currentAgentBubble = null; | |
| const authBtn = document.getElementById("githubAuthBtn"); | |
| const userProfile = document.getElementById("userProfile"); | |
| const repoContainer = document.getElementById("repoContainer"); | |
| const repoSearch = document.getElementById("repoSearch"); | |
| const repoDropdown = document.getElementById("repoDropdown"); | |
| socket.onopen = () => { | |
| socket.send(JSON.stringify({ type: "register_channel", socketId: localSocketChannelId })); | |
| const cachedToken = localStorage.getItem("swades_persistent_token"); | |
| const cachedUser = localStorage.getItem("swades_persistent_user"); | |
| const cachedAvatar = localStorage.getItem("swades_persistent_avatar"); | |
| if (cachedToken && cachedUser && cachedAvatar) { | |
| activeToken = cachedToken; | |
| authBtn.style.display = "none"; | |
| userProfile.style.display = "flex"; | |
| document.getElementById("userAvatar").src = cachedAvatar; | |
| document.getElementById("usernameDisplay").textContent = cachedUser; | |
| repoContainer.style.display = "block"; | |
| document.getElementById("statusMessage").textContent = "● Session restored from browser layer. Fetching repository list updates..."; | |
| socket.send(JSON.stringify({ type: "hydrate_repos", token: cachedToken })); | |
| } else { | |
| document.getElementById("statusMessage").textContent = "● No persistent profile token found. Please tap 'Connect GitHub' to initialize."; | |
| } | |
| }; | |
| authBtn.onclick = () => { | |
| const w = 600, h = 650; | |
| const left = (screen.width/2)-(w/2), top = (screen.height/2)-(h/2); | |
| window.open(`/login/github?socketId=${localSocketChannelId}`, "GitHub Authentication", `width=${w},height=${h},top=${top},left=${left}`); | |
| }; | |
| document.getElementById("logoutBtn").onclick = () => { | |
| localStorage.clear(); | |
| window.location.reload(); | |
| }; | |
| socket.onmessage = (event) => { | |
| const packet = JSON.parse(event.data); | |
| if (packet.type === "AUTH_COMPLETE") { | |
| activeToken = packet.token; | |
| repositoryCacheList = packet.repos; | |
| localStorage.setItem("swades_persistent_token", packet.token); | |
| localStorage.setItem("swades_persistent_user", packet.username); | |
| localStorage.setItem("swades_persistent_avatar", packet.avatar); | |
| authBtn.style.display = "none"; | |
| userProfile.style.display = "flex"; | |
| document.getElementById("userAvatar").src = packet.avatar; | |
| document.getElementById("usernameDisplay").textContent = packet.username; | |
| repoContainer.style.display = "block"; | |
| populateDropdown(repositoryCacheList); | |
| appendMessage("System", `✔ Identity recognized: ${packet.username}. Mounted ${repositoryCacheList.length} repositories into selection arrays.`, "system-status"); | |
| return; | |
| } | |
| if (packet.type === "REPO_HYDRATION_COMPLETE") { | |
| repositoryCacheList = packet.repos; | |
| populateDropdown(repositoryCacheList); | |
| appendMessage("System", `✔ Repositories successfully synchronized. ready for target instructions.`, "system-status"); | |
| return; | |
| } | |
| if (packet.type === "system") { | |
| appendMessage("System", packet.data, "system-status"); | |
| currentAgentBubble = null; | |
| } | |
| else if (packet.type === "success_link") { | |
| const msgDiv = document.createElement("div"); | |
| msgDiv.className = "message success-status"; | |
| const senderDiv = document.createElement("div"); | |
| senderDiv.className = "sender"; senderDiv.textContent = "System"; | |
| const bubbleDiv = document.createElement("div"); | |
| bubbleDiv.className = "bubble"; | |
| bubbleDiv.innerHTML = `<span>${packet.data}</span><a class="action-btn-link" href="${packet.link}" target="_blank">View Patch Branch →</a>`; | |
| msgDiv.appendChild(senderDiv); | |
| msgDiv.appendChild(bubbleDiv); | |
| chatContainer.appendChild(msgDiv); | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| currentAgentBubble = null; | |
| } | |
| else if (packet.type === "output") { | |
| const cleanChunk = packet.data.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, ''); | |
| if (cleanChunk.trim() === "") return; | |
| if (!currentAgentBubble) { | |
| currentAgentBubble = appendMessage("Swades Engine", cleanChunk, "agent"); | |
| } else { | |
| currentAgentBubble.textContent += cleanChunk; | |
| } | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| } | |
| }; | |
| repoSearch.addEventListener("input", (e) => { | |
| const criteria = e.target.value.toLowerCase(); | |
| const filtered = repositoryCacheList.filter(r => r.name.toLowerCase().includes(criteria)); | |
| populateDropdown(filtered); | |
| repoDropdown.style.display = "block"; | |
| }); | |
| repoSearch.addEventListener("focus", () => { | |
| if(repositoryCacheList.length > 0) repoDropdown.style.display = "block"; | |
| }); | |
| document.addEventListener("click", (e) => { | |
| if (!repoContainer.contains(e.target)) repoDropdown.style.display = "none"; | |
| }); | |
| function populateDropdown(items) { | |
| repoDropdown.innerHTML = ""; | |
| if(items.length === 0) { | |
| const fallback = document.createElement("div"); | |
| fallback.className = "dropdown-item"; | |
| fallback.textContent = "No repositories detected."; | |
| repoDropdown.appendChild(fallback); | |
| return; | |
| } | |
| items.forEach(repo => { | |
| const dItem = document.createElement("div"); | |
| dItem.className = "dropdown-item"; | |
| dItem.textContent = repo.name; | |
| dItem.onclick = () => { | |
| repoSearch.value = repo.name; | |
| selectedRepoUrl = repo.url; | |
| repoDropdown.style.display = "none"; | |
| }; | |
| repoDropdown.appendChild(dItem); | |
| }); | |
| } | |
| function appendMessage(sender, text, typeClass) { | |
| const msgDiv = document.createElement("div"); | |
| msgDiv.className = `message ${typeClass}`; | |
| const senderDiv = document.createElement("div"); | |
| senderDiv.className = "sender"; senderDiv.textContent = sender; | |
| const bubbleDiv = document.createElement("div"); | |
| bubbleDiv.className = "bubble"; bubbleDiv.textContent = text; | |
| msgDiv.appendChild(senderDiv); msgDiv.appendChild(bubbleDiv); | |
| chatContainer.appendChild(msgDiv); | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| return bubbleDiv; | |
| } | |
| document.getElementById("sendBtn").onclick = () => { | |
| const promptInput = document.getElementById("taskPrompt"); | |
| const taskText = promptInput.value.trim(); | |
| const k = document.getElementById("key").value.trim(); | |
| if (!taskText) return; | |
| if (!activeToken) return alert("Verification Failed: Connect GitHub profile scopes first."); | |
| if (!selectedRepoUrl) return alert("Friction Alert: Selection required. Please select a repository from the list."); | |
| if (!k) return alert("Configuration values missing: OpenRouter Key populated context field required."); | |
| appendMessage("User Request", taskText, "user"); | |
| promptInput.value = ""; | |
| currentAgentBubble = null; | |
| socket.send(JSON.stringify({ | |
| type: "run", | |
| task: taskText, | |
| autonomous: document.getElementById("autoMode").checked, | |
| userApiKey: k, | |
| userModel: document.getElementById("model").value.trim(), | |
| repoUrl: selectedRepoUrl, | |
| githubToken: activeToken | |
| })); | |
| }; | |
| document.getElementById("taskPrompt").addEventListener("keydown", (e) => { | |
| if (e.key === "Enter") document.getElementById("sendBtn").click(); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| EOF | |
| # 6. Install clean web interfaces | |
| RUN npm install express ws --prefix server | |
| ENV PORT=7860 | |
| EXPOSE 7860 | |
| # 7. Start application server instance | |
| CMD ["node", "server/index.js"] | |