PageSourceSearch

https://www.hamienet.com/assets/DevAgent-7yet-ucp.js

js hamienet.com collected 2026-10-02 05:01:40 UTC 39,478 bytes, 754 lines download raw bytes

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.