1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-w5lXls15.js"; 3import { g as getAiModel, a as ai } from "./ai-B0Mlroaa.js"; 4import { G as GoogleGenAI } from "./vendor-ai-gQv1oOjO.js"; 5import { e4 as Bot, b5 as Trash2, b9 as Settings, cM as RefreshCw, bx as Send, aY as TriangleAlert, cd as Save, ck as Layers, bM as Clock, fk as FileCode, c4 as RotateCcw, a$ as Shield, aW as X, cF as Key, cf as Plus, bz as EyeOff, e7 as Lock, cg as Activity } from "./vendor-D0P4XgNj.js"; 6import "./vendor-i18n-BC7ZMXWQ.js"; 7const DEFAULT_SETTINGS = { 8 restrictedPaths: [".env", "config/database.php", "admin_users.db", "web/prisma/schema.prisma"], 9 autoReadLimit: 3, 10 ignoreList: ["node_modules", "dist", "build", ".git"], 11 systemPrompt: "You are an expert Full-Stack Developer. Write clean, efficient code.", 12 model: "gemini-3-pro-preview", 13 temperature: 0.7, 14 apiKeys: [], 15 selectedKey: "" 16}; 17const AVAILABLE_MODELS = [ 18 { id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro (Latest)" }, 19 { id: "gemini-3-pro-preview", name: "Gemini 3 Pro (Recommended for Coding)" }, 20 { id: "gemini-3-flash-preview", name: "Gemini 3 Flash (Fast)" }, 21 { id: "gemini-flash-latest", name: "Gemini Flash Latest" }, 22 { id: "gemini-flash-lite-latest", name: "Gemini Flash Lite" } 23]; 24const SettingsModal = ({ isOpen, onClose, settings, onSave }) => { 25 const [localSettings, setLocalSettings] = reactExports.useState(settings); 26 const [newKey, setNewKey] = reactExports.useState(""); 27 const [customModels, setCustomModels] = reactExports.useState(() => { 28 try { 29 const globalConfig = localStorage.getItem("hamie_ai_config"); 30 if (globalConfig) { 31 const parsed = JSON.parse(globalConfig); 32 if (parsed.customModels && Array.isArray(parsed.customModels)) { 33 return parsed.customModels; 34 } 35 } 36 } catch (e) { 37 console.error("Failed to load custom models in DevAgent", e); 38 } 39 return []; 40 }); 41 if (!isOpen) return null; 42 const handleChange = (key, val) => { 43 if (key === "autoReadLimit") { 44 setLocalSettings({ ...localSettings, [key]: parseInt(val) || 3 }); 45 } else if (key === "temperature") { 46 setLocalSettings({ ...localSettings, [key]: parseFloat(val) }); 47 } else if (Array.isArray(localSettings[key]) && key !== "apiKeys") { 48 setLocalSettings({ ...localSettings, [key]: val.split(",").map((s) => s.trim()).filter(Boolean) }); 49 } else { 50 setLocalSettings({ ...localSettings, [key]: val }); 51 } 52 }; 53 const handleAddKey = () => { 54 if (newKey && !localSettings.apiKeys.includes(newKey)) { 55 setLocalSettings((prev) => ({ 56 ...prev, 57 apiKeys: [...prev.apiKeys, newKey], 58 selectedKey: newKey 59 // Auto-select new key 60 })); 61 setNewKey(""); 62 } 63 }; 64 const removeKey = (key) => { 65 setLocalSettings((prev) => ({ 66 ...prev, 67 apiKeys: prev.apiKeys.filter((k) => k !== key), 68 selectedKey: prev.selectedKey === key ? "" : prev.selectedKey 69 })); 70 }; 71 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "fixed inset-0 z-[100] flex items-center justify-center p-4", children: [ 72 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/60 backdrop-blur-sm", onClick: onClose }), 73 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative w-full max-w-lg bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl p-6 animate-in zoom-in-95 flex flex-col max-h-[90vh]", children: [
74 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-6 border-b border-zinc-800 pb-4 shrink-0", children: [ 75 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-xl font-bold text-white flex items-center gap-2", children: [ 76 /* @__PURE__ */ jsxRuntimeExports.jsx(Shield, { className: "text-indigo-500" }), 77 " Agent Configuration" 78 ] }), 79 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 20, className: "text-zinc-500 hover:text-white" }) }) 80 ] }), 81 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6 overflow-y-auto pr-2 custom-scrollbar", children: [ 82 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-950 p-4 rounded-lg border border-zinc-800", children: [ 83 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-3 flex items-center gap-2", children: [
84 /* @__PURE__ */ jsxRuntimeExports.jsx(Key, { size: 12 }), 85 " API Keys" 86 ] }), 87 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 mb-4", children: [ 88 /* @__PURE__ */ jsxRuntimeExports.jsx( 89 "div", 90 { 91 onClick: () => handleChange("selectedKey", ""), 92 className: `flex items-center justify-between p-2 rounded cursor-pointer border ${localSettings.selectedKey === "" ? "bg-indigo-900/20 border-indigo-500" : "bg-zinc-900 border-zinc-700 hover:border-zinc-600"}`, 93 children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 94 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-3 h-3 rounded-full ${localSettings.selectedKey === "" ? "bg-indigo-500" : "bg-zinc-600"}` }), 95 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-mono text-zinc-300", children: "System Default (Env)" }) 96 ] }) 97 } 98 ), 99 localSettings.apiKeys.map((k, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 100 "div", 101 { 102 onClick: () => handleChange("selectedKey", k), 103 className: `flex items-center justify-between p-2 rounded cursor-pointer border ${localSettings.selectedKey === k ? "bg-indigo-900/20 border-indigo-500" : "bg-zinc-900 border-zinc-700 hover:border-zinc-600"}`, 104 children: [
105 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 overflow-hidden", children: [ 106 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-3 h-3 rounded-full shrink-0 ${localSettings.selectedKey === k ? "bg-indigo-500" : "bg-zinc-600"}` }), 107 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm font-mono text-zinc-300 truncate", children: [ 108 k.substring(0, 8), 109 "...", 110 k.substring(k.length - 4) 111 ] }) 112 ] }), 113 /* @__PURE__ */ jsxRuntimeExports.jsx( 114 "button", 115 { 116 onClick: (e) => { 117 e.stopPropagation(); 118 removeKey(k); 119 }, 120 className: "text-zinc-500 hover:text-red-500 p-1", 121 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) 122 } 123 ) 124 ] 125 }, 126 i 127 )) 128 ] }),
129 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 130 /* @__PURE__ */ jsxRuntimeExports.jsx( 131 "input", 132 { 133 type: "password", 134 value: newKey, 135 onChange: (e) => setNewKey(e.target.value), 136 placeholder: "sk-...", 137 className: "flex-1 bg-black border border-zinc-700 rounded px-3 py-1.5 text-xs text-white outline-none focus:border-indigo-500" 138 } 139 ), 140 /* @__PURE__ */ jsxRuntimeExports.jsxs( 141 "button", 142 { 143 onClick: handleAddKey, 144 disabled: !newKey, 145 className: "bg-zinc-800 hover:bg-zinc-700 text-white px-3 py-1.5 rounded text-xs font-bold flex items-center gap-1 disabled:opacity-50", 146 children: [ 147 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 12 }), 148 " Add" 149 ] 150 } 151 ) 152 ] }) 153 ] }),
154 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 155 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2 flex items-center gap-2", children: [ 156 /* @__PURE__ */ jsxRuntimeExports.jsx(Bot, { size: 12 }), 157 " System Persona" 158 ] }), 159 /* @__PURE__ */ jsxRuntimeExports.jsx( 160 "textarea", 161 { 162 value: localSettings.systemPrompt, 163 onChange: (e) => handleChange("systemPrompt", e.target.value), 164 className: "w-full h-20 bg-black border border-zinc-800 rounded p-3 text-xs text-zinc-300 focus:border-indigo-500 outline-none resize-none" 165 } 166 ) 167 ] }), 168 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [ 169 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 170 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2", children: "Model" }),
171 /* @__PURE__ */ jsxRuntimeExports.jsx( 172 "select", 173 { 174 value: localSettings.model, 175 onChange: (e) => handleChange("model", e.target.value), 176 className: "w-full bg-black border border-zinc-800 rounded p-2 text-xs text-white focus:border-indigo-500 outline-none", 177 children: AVAILABLE_MODELS.concat(customModels).map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: m.id, children: m.name }, m.id)) 178 } 179 ) 180 ] }), 181 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 182 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2", children: [ 183 "Temperature (", 184 localSettings.temperature, 185 ")" 186 ] }), 187 /* @__PURE__ */ jsxRuntimeExports.jsx( 188 "input", 189 { 190 type: "range", 191 min: "0", 192 max: "1", 193 step: "0.1", 194 value: localSettings.temperature, 195 onChange: (e) => handleChange("temperature", e.target.value), 196 className: "w-full accent-indigo-500" 197 } 198 ) 199 ] }) 200 ] }),
201 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 202 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2 flex items-center gap-2", children: [ 203 /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 12 }), 204 " Ignored Directories (Context Window)" 205 ] }), 206 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-400 mb-2", children: "Folders to exclude from the project structure scan to save tokens." }), 207 /* @__PURE__ */ jsxRuntimeExports.jsx( 208 "textarea", 209 { 210 value: localSettings.ignoreList.join(", "), 211 onChange: (e) => handleChange("ignoreList", e.target.value), 212 className: "w-full h-20 bg-black border border-zinc-800 rounded p-3 text-xs font-mono text-zinc-300 focus:border-indigo-500 outline-none resize-none" 213 } 214 ) 215 ] }), 216 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
217 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2 flex items-center gap-2", children: [ 218 /* @__PURE__ */ jsxRuntimeExports.jsx(Lock, { size: 12 }), 219 " Restricted Paths (Guardrails)" 220 ] }), 221 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-400 mb-2", children: "Paths the AI is explicitly forbidden from reading or modifying." }), 222 /* @__PURE__ */ jsxRuntimeExports.jsx( 223 "textarea", 224 { 225 value: localSettings.restrictedPaths.join(", "), 226 onChange: (e) => handleChange("restrictedPaths", e.target.value), 227 className: "w-full h-20 bg-black border border-zinc-800 rounded p-3 text-xs font-mono text-red-400 focus:border-red-500 outline-none resize-none" 228 } 229 ) 230 ] }), 231 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 232 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-xs font-bold text-zinc-500 uppercase mb-2 flex items-center gap-2", children: [
233 /* @__PURE__ */ jsxRuntimeExports.jsx(Activity, { size: 12 }), 234 " Auto-Read Limit" 235 ] }), 236 /* @__PURE__ */ jsxRuntimeExports.jsx( 237 "input", 238 { 239 type: "number", 240 value: localSettings.autoReadLimit, 241 onChange: (e) => handleChange("autoReadLimit", e.target.value), 242 className: "w-full bg-black border border-zinc-800 rounded p-2 text-sm text-white focus:border-indigo-500 outline-none", 243 min: 1, 244 max: 10 245 } 246 ) 247 ] }) 248 ] }), 249 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-3 mt-6 pt-4 border-t border-zinc-800 shrink-0", children: [ 250 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "px-4 py-2 text-zinc-400 hover:text-white font-bold text-sm", children: "Cancel" }), 251 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => { 252 onSave(localSettings); 253 onClose(); 254 }, className: "px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-bold text-sm", children: "Save Configuration" }) 255 ] }) 256 ] }) 257 ] }); 258}; 259const FileTree = ({ nodes, depth = 0 }) => { 260 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs font-mono select-none", children: nodes.map((node) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 261 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1 py-0.5", style: { paddingLeft: depth * 12 }, children: node.type === "folder" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-indigo-400", children: [ 262 "ð ", 263 node.name 264 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-400", children: [ 265 "ð ", 266 node.name 267 ] }) }), 268 node.children && /* @__PURE__ */ jsxRuntimeExports.jsx(FileTree, { nodes: node.children, depth: depth + 1 }) 269 ] }, node.path)) }); 270}; 271const DiffViewer = ({ file, onChange }) => { 272 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 h-full overflow-hidden font-mono text-xs", children: [ 273 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col border-r border-zinc-800 bg-[#1e1e1e]", children: [
274 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2 font-bold text-zinc-500 bg-[#252526] uppercase flex justify-between items-center", children: [ 275 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Original" }), 276 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] opacity-50", children: "Read Only" }) 277 ] }), 278 /* @__PURE__ */ jsxRuntimeExports.jsx( 279 "textarea", 280 { 281 className: "flex-1 w-full bg-transparent text-zinc-500 p-4 resize-none outline-none leading-relaxed", 282 value: file.originalContent, 283 readOnly: true, 284 spellCheck: false 285 } 286 ) 287 ] }), 288 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col bg-[#1e1e1e]", children: [ 289 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2 font-bold text-indigo-400 bg-[#252526] uppercase flex justify-between items-center", children: [ 290 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Proposed Changes" }),
291 file.isModified && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] bg-indigo-500/20 px-2 rounded text-indigo-300", children: "Modified" }) 292 ] }), 293 /* @__PURE__ */ jsxRuntimeExports.jsx( 294 "textarea", 295 { 296 className: "flex-1 w-full bg-transparent text-zinc-200 p-4 resize-none outline-none leading-relaxed", 297 value: file.modifiedContent, 298 onChange: (e) => onChange(e.target.value), 299 spellCheck: false 300 } 301 ) 302 ] }) 303 ] }); 304}; 305const HistoryPanel = ({ history, onRestore }) => { 306 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 bg-[#0c0c0c] p-6 overflow-y-auto", children: [ 307 /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-lg font-bold text-white mb-6 flex items-center gap-2", children: [ 308 /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 20 }), 309 " Change History" 310 ] }), 311 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 max-w-4xl", children: [ 312 history.map((commit) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-[#151515] border border-zinc-800 rounded-xl p-4", children: [
313 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-3 border-b border-zinc-800 pb-2", children: [ 314 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 315 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm font-bold text-zinc-300", children: [ 316 "Commit ", 317 commit.id.split("-")[1] 318 ] }), 319 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-500", children: new Date(commit.timestamp).toLocaleString() }) 320 ] }), 321 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs bg-zinc-800 px-2 py-0.5 rounded text-zinc-400", children: [ 322 commit.files.length, 323 " files" 324 ] }) 325 ] }), 326 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: commit.files.map((f, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between bg-black/50 p-2 rounded border border-zinc-800/50 hover:border-zinc-700 transition-colors group", children: [
327 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs font-mono text-zinc-400", children: [ 328 /* @__PURE__ */ jsxRuntimeExports.jsx(FileCode, { size: 12 }), 329 " ", 330 f.originalPath 331 ] }), 332 /* @__PURE__ */ jsxRuntimeExports.jsxs( 333 "button", 334 { 335 onClick: () => onRestore(f), 336 className: "px-3 py-1 bg-zinc-800 hover:bg-zinc-700 rounded text-[10px] font-bold text-zinc-300 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity", 337 children: [ 338 /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCcw, { size: 10 }), 339 " Restore" 340 ] 341 } 342 ) 343 ] }, i)) }) 344 ] }, commit.id)), 345 history.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-zinc-500 text-center py-10", children: "No history available yet." }) 346 ] }) 347 ] }); 348}; 349const DevAgent = () => { 350 const [messages, setMessages] = reactExports.useState(() => { 351 const savedChat = localStorage.getItem("dev_agent_chat_history"); 352 if (savedChat) { 353 try { 354 return JSON.parse(savedChat); 355 } catch (e) { 356 } 357 } 358 return [{ 359 role: "assistant", 360 content: "I'm your Autonomous AI Developer. I scan the project, read files, and implement changes. What would you like to build?", 361 timestamp: Date.now() 362 }]; 363 }); 364 const [input, setInput] = reactExports.useState(""); 365 const [isProcessing, setIsProcessing] = reactExports.useState(false); 366 const [status, setStatus] = reactExports.useState("Ready"); 367 const [settings, setSettings] = reactExports.useState(() => { 368 const savedSettings = localStorage.getItem("agent_settings"); 369 if (savedSettings) try { 370 return JSON.parse(savedSettings); 371 } catch (e) { 372 } 373 return DEFAULT_SETTINGS; 374 }); 375 const [showSettings, setShowSettings] = reactExports.useState(false); 376 const [activeTab, setActiveTab] = reactExports.useState("workspace"); 377 const [fileStructure, setFileStructure] = reactExports.useState([]); 378 const [openFiles, setOpenFiles] = reactExports.useState([]); 379 const [activeFileIndex, setActiveFileIndex] = reactExports.useState(-1); 380 const [history, setHistory] = reactExports.useState([]); 381 const messagesEndRef = reactExports.useRef(null); 382 reactExports.useEffect(() => { 383 loadStructure(); 384 loadHistory(); 385 }, []); 386 reactExports.useEffect(() => { 387 var _a; 388 localStorage.setItem("dev_agent_chat_history", JSON.stringify(messages)); 389 (_a = messagesEndRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" }); 390 }, [messages]); 391 const loadStructure = async () => { 392 try { 393 const res = await fetch("/api/dev-agent/structure", { 394 method: "POST", 395 headers: { "Content-Type": "application/json" }, 396 body: JSON.stringify({ ignoreList: settings.ignoreList }) 397 }); 398 setFileStructure(await res.json()); 399 } catch (e) { 400 console.error(e); 401 } 402 }; 403 const loadHistory = async () => { 404 try { 405 const res = await fetch("/api/dev-agent/history"); 406 setHistory(await res.json()); 407 } catch (e) { 408 console.error(e); 409 } 410 }; 411 const saveSettings = (newSettings) => { 412 setSettings(newSettings); 413 localStorage.setItem("agent_settings", JSON.stringify(newSettings)); 414 }; 415 const clearChat = () => { 416 if (confirm("Clear conversation history?")) { 417 setMessages([{ 418 role: "assistant", 419 content: "Chat cleared. Ready for new tasks.", 420 timestamp: Date.now() 421 }]); 422 } 423 }; 424 const cleanJson = (text) => { 425 if (!text) return "{}"; 426 const jsonBlockMatch = text.match(/```json\s*([\s\S]*?)\s*```/i); 427 if (jsonBlockMatch) return jsonBlockMatch[1]; 428 const codeBlockMatch = text.match(/```\s*([\s\S]*?)\s*```/); 429 if (codeBlockMatch) return codeBlockMatch[1]; 430 const firstBrace = text.indexOf("{"); 431 const lastBrace = text.lastIndexOf("}"); 432 if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) { 433 return text.substring(firstBrace, lastBrace + 1); 434 } 435 const clean = text.replace(/```json\s*/g, "").replace(/```\s*/g, ""); 436 return clean.trim(); 437 }; 438 const getAiClient = () => { 439 if (settings.selectedKey) { 440 return new GoogleGenAI({ apiKey: settings.selectedKey }); 441 } 442 return ai; 443 }; 444 const handleSendMessage = async () => { 445 if (!input.trim() || isProcessing) return; 446 const userMsg = { role: "user", content: input, timestamp: Date.now() }; 447 setMessages((prev) => [...prev, userMsg]); 448 setInput(""); 449 setIsProcessing(true); 450 setActiveTab("workspace"); 451 const client = getAiClient(); 452 try {
453 setStatus("Phase A: Scouting files..."); 454 const flatStructure = JSON.stringify(fileStructure).substring(0, 4e4); 455 const scoutPrompt = ` 456 ${settings.systemPrompt} 457 458 Objective: Analyze the User Request and Project File Map to identify target files. 459 460 User Request: "${userMsg.content}" 461 462 Project File Map (JSON): 463 ${flatStructure} 464 465 Instructions: 466 1. Identify files that need reading/editing. 467 2. Return JSON object: { "target_files": ["path/to/file1", ...] } 468 3. Max ${settings.autoReadLimit} files. 469 4. STRICTLY JSON ONLY. 470 `; 471 const scoutRes = await client.models.generateContent({ 472 model: getAiModel(settings.model), 473 contents: scoutPrompt, 474 config: { temperature: settings.temperature } 475 }); 476 const rawScout = cleanJson(scoutRes.text || "{}"); 477 let targetFiles = [];
478 try { 479 targetFiles = JSON.parse(rawScout).target_files || []; 480 } catch (e) { 481 console.warn("JSON Parse Error (Scout):", e, rawScout); 482 } 483 const filteredFiles = targetFiles.filter((path) => { 484 const isRestricted = settings.restrictedPaths.some((r) => path.includes(r)); 485 return !isRestricted; 486 }).slice(0, settings.autoReadLimit); 487 if (filteredFiles.length === 0) { 488 setMessages((prev) => [...prev, { 489 role: "assistant", 490 content: "I couldn't identify any safe, relevant files to modify. Please check your prompt or restrictions.", 491 timestamp: Date.now() 492 }]); 493 setIsProcessing(false); 494 setStatus("Ready"); 495 return; 496 } 497 setStatus(`Phase B: Reading ${filteredFiles.length} files...`); 498 setMessages((prev) => [...prev, { 499 role: "scout", 500 content: `Identified targets: ${filteredFiles.join(", ")}. Fetching content...`, 501 timestamp: Date.now(), 502 files: filteredFiles, 503 isPhase1: true 504 }]); 505 const fileContents = await api.readDevAgentFile(filteredFiles); 506 setStatus("Generating code changes..."); 507 const executePrompt = ` 508 ${settings.systemPrompt} 509 510 Task: Implement the request by modifying the files. 511 User Request: "${userMsg.content}" 512 513 Context Files (JSON): 514 ${JSON.stringify(fileContents, null, 2)} 515 516 Instructions: 517 1. Return a JSON Object where keys are file paths and values are the FULL NEW CONTENT. 518 2. Ensure code integrity. Do not truncate logic. 519 3. STRICTLY JSON ONLY. No markdown. 520 `; 521 const executeRes = await client.models.generateContent({ 522 model: getAiModel(settings.model), 523 contents: executePrompt, 524 config: { temperature: settings.temperature } 525 }); 526 const rawExecute = cleanJson(executeRes.text || "{}"); 527 let generatedChanges = {}; 528 try { 529 generatedChanges = JSON.parse(rawExecute); 530 } catch (e) { 531 console.error("JSON Parse Error (Execute):", e, rawExecute); 532 throw new Error("Failed to parse AI response. Code generation format was invali
532d."); 533 } 534 const newOpenFiles = []; 535 for (const [path, newContent] of Object.entries(generatedChanges)) { 536 const original = fileContents[path] || ""; 537 newOpenFiles.push({ 538 path, 539 originalContent: original, 540 modifiedContent: newContent, 541 isModified: original !== newContent 542 }); 543 } 544 setOpenFiles(newOpenFiles); 545 if (newOpenFiles.length > 0) setActiveFileIndex(0); 546 setMessages((prev) => [...prev, { 547 role: "assistant", 548 content: `I've prepared changes for ${newOpenFiles.length} files. Review diffs and commit when ready.`, 549 timestamp: Date.now(), 550 files: newOpenFiles.map((f) => f.path) 551 }]); 552 } catch (e) { 553 setMessages((prev) => [...prev, { role: "system", content: `Error: ${e.message}`, timestamp: Date.now() }]); 554 } finally { 555 setIsProcessing(false); 556 setStatus("Ready"); 557 } 558 }; 559 const handleCommit = async () => { 560 const modifiedFiles = openFiles.filter((f) => f.isModified); 561 if (modifiedFiles.length === 0) return; 562 if (!confirm(`Commit changes to ${modifiedFiles.length} files?`)) return; 563 setIsProcessing(true); 564 setStatus("Committing changes..."); 565 try { 566 const changesPayload = {}; 567 modifiedFiles.forEach((f) => changesPayload[f.path] = f.modifiedContent); 568 const res = await api.commitDevAgentFile(changesPayload, "Agent Auto-Commit"); 569 if (res.success) { 570 setMessages((prev) => [...prev, { 571 role: "system", 572 content: `Committed changes. Backup ID: ${res.backupLocation.split("/").pop()}.`, 573 timestamp: Date.now() 574 }]); 575 setOpenFiles((prev) => prev.map((f) => ({ ...f, originalContent: f.modifiedContent, isModified: false }))); 576 loadHistory(); 577 } 578 } catch (e) { 579 alert(`Commit failed: ${e.message}`); 580 } finally { 581 setIsProcessing(false); 582 setStatus("Ready"); 583 } 584 }; 585 const handleRestore = async (fileRec) => { 586 if (!confirm(`Restore ${fileRec.originalPath} from backup? This overwrites current version.`)) return; 587 try { 588 const res = await fetch("/api/dev-agent/restore", { 589 method: "POST", 590 headers: { "Content-Type": "application/json" }, 591 body: JSON.stringify({ file: fileRec }) 592 }); 593 const data = await res.json(); 594 if (data.success) { 595 alert("File restored successfully."); 596 loadStructure(); 597 } else { 598 alert("Restore failed: " + data.error); 599 } 600 } catch (e) { 601 alert("Restore error: " + e.message); 602 } 603 }; 604 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-screen bg-[#111] text-zinc-300 overflow-hidden font-sans", children: [ 605 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-96 flex flex-col border-r border-zinc-800 bg-black", children: [
606 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 border-b border-zinc-800 bg-[#151515] flex justify-between items-center", children: [ 607 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 608 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-bold text-white flex items-center gap-2", children: [ 609 /* @__PURE__ */ jsxRuntimeExports.jsx(Bot, { className: "text-indigo-500" }), 610 " AI Developer Agent" 611 ] }), 612 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mt-2 text-xs text-zinc-500", children: [ 613 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-2 h-2 rounded-full ${isProcessing ? "bg-amber-500 animate-pulse" : "bg-emerald-500"}` }), 614 status 615 ] }) 616 ] }), 617 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [ 618 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: clearChat, className: "p-2 hover:bg-zinc-800 rounded-lg text-zinc-500 hover:text-red-400 transition-colors", title: "Clear Chat", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 18 }) }),
619 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setShowSettings(true), className: "p-2 hover:bg-zinc-800 rounded-lg text-zinc-500 hover:text-white transition-colors", title: "Settings", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 18 }) }) 620 ] }) 621 ] }), 622 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 custom-scrollbar", children: [ 623 messages.map((msg, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex flex-col ${msg.role === "user" ? "items-end" : "items-start"}`, children: [ 624 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `max-w-[90%] p-3 rounded-xl text-sm ${msg.role === "user" ? "bg-indigo-600 text-white rounded-br-none" : msg.role === "system" ? "bg-zinc-800 text-yellow-400 font-mono text-xs border border-yellow-900/30" : msg.role === "scout" ? "bg-indigo-900/20 text-indigo-300 font-mono text-xs border border-indigo-500/30" : "bg-[#222] text-zinc-200 border border-zinc-800 rounded-bl-none"}`, children: msg.content }), 625 msg.files && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 flex flex-wrap gap-1", children: msg.files.map((f) => /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] bg-zinc-900 border border-zinc-800 px-2 py-1 rounded text-zinc-500 font-mono truncate max-w-[200px]", children: f.split("/").pop() }, f)) }) 626 ] }, i)),
627 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: messagesEndRef }) 628 ] }), 629 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 border-t border-zinc-800 bg-[#151515]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 bg-zinc-900 border border-zinc-800 rounded-lg p-2 focus-within:border-indigo-500 transition-colors", children: [ 630 /* @__PURE__ */ jsxRuntimeExports.jsx( 631 "textarea", 632 { 633 className: "flex-1 bg-transparent border-none outline-none text-sm resize-none h-12 custom-scrollbar", 634 placeholder: "Describe a task...", 635 value: input, 636 onChange: (e) => setInput(e.target.value), 637 onKeyDown: (e) => { 638 if (e.key === "Enter" && !e.shiftKey) { 639 e.preventDefault(); 640 handleSendMessage(); 641 } 642 }, 643 disabled: isProcessing 644 } 645 ), 646 /* @__PURE__ */ jsxRuntimeExports.jsx( 647 "button", 648 { 649 onClick: handleSendMessage, 650 disabled: !input.trim() || isProcessing, 651 className: "self-end p-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-md disabled:opacity-50 transition-colors",
652 children: isProcessing ? /* @__PURE__ */ jsxRuntimeExports.jsx(RefreshCw, { className: "animate-spin", size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Send, { size: 16 }) 653 } 654 ) 655 ] }) }) 656 ] }), 657 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-w-0 bg-[#0c0c0c]", children: [ 658 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-12 border-b border-zinc-800 bg-[#151515] flex items-center px-2", children: [ 659 /* @__PURE__ */ jsxRuntimeExports.jsxs( 660 "button", 661 { 662 onClick: () => setActiveTab("workspace"), 663 className: `px-4 py-3 text-xs font-bold border-b-2 transition-colors ${activeTab === "workspace" ? "border-indigo-500 text-white" : "border-transparent text-zinc-500 hover:text-zinc-300"}`, 664 children: [ 665 "Workspace (", 666 openFiles.length, 667 ")" 668 ] 669 } 670 ), 671 /* @__PURE__ */ jsxRuntimeExports.jsx( 672 "button", 673 { 674 onClick: () => setActiveTab("history"), 675 className: `px-4 py-3 text-xs font-bold border-b-2 transition-colors ${activeTab === "history" ? "border-indigo-500 text-white" : "border-transparent text-zinc-500 hover:text-zinc-300"}`, 676 children: "History" 677 } 678 ) 679 ] }), 680 activeTab === "workspace" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col min-h-0", children: openFiles.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
681 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex bg-[#151515] border-b border-zinc-800 overflow-x-auto custom-scrollbar", children: openFiles.map((file, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 682 "button", 683 { 684 onClick: () => setActiveFileIndex(i), 685 className: `flex items-center gap-2 px-4 py-2 text-xs font-medium border-r border-zinc-800 transition-colors min-w-[150px] max-w-[250px] ${activeFileIndex === i ? "bg-[#1e1e1e] text-indigo-400 border-t-2 border-t-indigo-500" : "text-zinc-500 hover:text-zinc-300 hover:bg-[#1a1a1a]"}`, 686 children: [ 687 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-2 h-2 rounded-full ${file.isModified ? "bg-amber-500" : "bg-transparent"}` }), 688 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", title: file.path, children: file.path.split("/").pop() }) 689 ] 690 }, 691 file.path 692 )) }), 693 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 relative", children: activeFileIndex !== -1 && openFiles[activeFileIndex] && /* @__PURE__ */ jsxRuntimeExports.jsx( 694 DiffViewer, 695 { 696 file: openFiles[activeFileIndex], 697 onChange: (val) => { 698 const newFiles = [...openFiles]; 699 newFiles[activeFileIndex].modifiedContent = val; 700 newFiles[activeFileIndex].isModified = val !== newFiles[activeFileIndex].originalContent; 701 setOpenFiles(newFiles); 702 } 703 } 704 ) }), 705 openFiles.some((f) => f.isModified) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-16 bg-[#151515] border-t border-zinc-800 flex items-center justify-between px-6 shrink-0", children: [
706 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs text-amber-500 flex items-center gap-2", children: [ 707 /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { size: 14 }), 708 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 709 openFiles.filter((f) => f.isModified).length, 710 " files modified." 711 ] }) 712 ] }), 713 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3", children: [ 714 /* @__PURE__ */ jsxRuntimeExports.jsx( 715 "button", 716 { 717 onClick: () => setOpenFiles((prev) => prev.map((f) => ({ ...f, modifiedContent: f.originalContent, isModified: false }))), 718 className: "px-4 py-2 rounded-lg text-xs font-bold text-zinc-400 hover:text-white hover:bg-zinc-800 transition-colors", 719 children: "Discard All" 720 } 721 ), 722 /* @__PURE__ */ jsxRuntimeExports.jsxs( 723 "button", 724 { 725 onClick: handleCommit, 726 className: "px-6 py-2 rounded-lg text-xs font-bold bg-indigo-600 hover:bg-indigo-700 text-white flex items-center gap-2 transition-colors shadow-lg shadow-indigo-500/10", 727 children: [
728 /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { size: 14 }), 729 " Commit Changes" 730 ] 731 } 732 ) 733 ] }) 734 ] }) 735 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col items-center justify-center text-zinc-600", children: [ 736 /* @__PURE__ */ jsxRuntimeExports.jsx(Layers, { size: 48, className: "mb-4 opacity-20" }), 737 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "Ready for instruction." }), 738 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-12 w-64 h-64 border border-zinc-800 rounded-lg p-4 overflow-auto opacity-30", children: /* @__PURE__ */ jsxRuntimeExports.jsx(FileTree, { nodes: fileStructure }) }) 739 ] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, { history, onRestore: handleRestore }) 740 ] }), 741 showSettings && /* @__PURE__ */ jsxRuntimeExports.jsx( 742 SettingsModal, 743 { 744 isOpen: showSettings, 745 onClose: () => setShowSettings(false), 746 settings, 747 onSave: saveSettings 748 } 749 ) 750 ] }); 751}; 752export { 753 DevAgent as default 754};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.