chore: auto-commit (11 archivos)

- CMakeLists.txt
- app.md
- main.cpp
- panels.cpp
- appicon.ico
- autoextract_panel.cpp
- picker_state.cpp
- picker_state.h
- py_subprocess.cpp
- py_subprocess.h
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 16:33:25 +02:00
parent fdd607b570
commit 8357774b86
11 changed files with 1626 additions and 20 deletions
+133
View File
@@ -20,6 +20,8 @@
#include "local_api.h"
#include "cdp_http.h"
#include "session_state.h"
#include "picker_state.h"
#include "py_subprocess.h"
#include <algorithm>
#include <atomic>
@@ -27,6 +29,7 @@
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <fstream>
#include <map>
#include <mutex>
#include <sstream>
@@ -532,6 +535,56 @@ void render_tab_detail_panel(bool* p_open) {
ImGui::Text("Browser :%d", port);
ImGui::Text("Tab id %s", sel_id.c_str());
ImGui::TextWrapped("WS %s", sel_ws.c_str());
ImGui::Separator();
// --- Pick element ---
bool active = picker_is_active();
if (active) ImGui::PushStyleColor(ImGuiCol_Button, fn_tokens::colors::primary);
if (ImGui::Button(active ? (TI_FLASK " Picking... (click to stop)")
: (TI_FLASK " Pick element"))) {
if (active) {
picker_stop();
} else {
std::string err = picker_start(port, sel_id, sel_ws);
if (!err.empty()) {
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::error);
ImGui::TextWrapped("Pick error: %s", err.c_str());
ImGui::PopStyleColor();
}
}
}
if (active) ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::TextDisabled("(injects functions/browser/cdp_pick_element_js.js via CDP)");
PickedElement last = picker_last();
if (last.valid) {
ImGui::Separator();
ImGui::TextDisabled("Last picked:");
if (ImGui::BeginChild("##picked_card", ImVec2(0, 110), true)) {
ImGui::Text("tag: %s", last.tag.c_str());
ImGui::TextWrapped("selector: %s", last.selector.c_str());
ImGui::TextWrapped("xpath: %s", last.xpath.c_str());
std::string short_text = last.text;
if (short_text.size() > 200) short_text = short_text.substr(0, 200) + "...";
ImGui::TextWrapped("text: %s", short_text.c_str());
}
ImGui::EndChild();
if (ImGui::SmallButton("Copy selector")) {
ImGui::SetClipboardText(last.selector.c_str());
}
ImGui::SameLine();
if (ImGui::SmallButton("Save to recipe (new)")) {
// Placeholder: futura integracion para crear recipe nueva con un
// unico field a partir del selector. Por ahora se copia.
ImGui::SetClipboardText(last.selector.c_str());
}
ImGui::SameLine();
if (ImGui::SmallButton("Clear")) picker_clear_last();
} else {
ImGui::TextDisabled("(no picked element yet — click 'Pick element' and click on the page)");
}
ImGui::Separator();
ImGui::TextWrapped(
"Tab Detail (HTML preview + screenshot + Runtime.evaluate REPL) llega "
@@ -744,8 +797,88 @@ void draw_request_detail(const NetworkRequest& r, NetworkSession* net) {
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Response")) {
// Detect JSON response (content-type: application/json).
bool is_json = false;
for (const auto& h : r.response_headers) {
std::string n = h.name; std::transform(n.begin(), n.end(), n.begin(), ::tolower);
if (n == "content-type" && h.value.find("application/json") != std::string::npos) {
is_json = true; break;
}
}
if (r.body_fetched && !r.body_text.empty()) {
if (ImGui::SmallButton("Copy")) copy_to_clipboard(r.body_text);
if (is_json) {
ImGui::SameLine();
if (ImGui::SmallButton(TI_LIST_DETAILS " Parse")) {
// Llama infer_json_rows_schema via subprocess.
static std::string g_parsed; // sticky entre frames
g_parsed.clear();
const char* code = R"PY(
import sys, os, json, traceback
root = os.environ.get('FN_REGISTRY_ROOT', '')
if not root:
print(json.dumps({"error":"FN_REGISTRY_ROOT not set"})); sys.exit(2)
for sub in ('core',):
sys.path.insert(0, os.path.join(root, 'python', 'functions', sub))
try:
from infer_json_rows_schema import infer_json_rows_schema
body = sys.stdin.read()
obj = json.loads(body)
res = infer_json_rows_schema(obj)
print(json.dumps(res if isinstance(res, dict) else {"result": res}))
except Exception as e:
print(json.dumps({"error": str(e), "trace": traceback.format_exc()})); sys.exit(1)
)PY";
std::vector<std::string> argv;
argv.push_back(py_resolve_interpreter());
argv.push_back("-c");
argv.push_back(code);
// Lanza un thread y deja log en g_net_ui.* via clipboard (simple).
std::string body = r.body_text;
std::thread([argv, body]() {
(void)argv; (void)body;
// py_run no soporta stdin todavia; usamos un archivo temporal.
// Para mantener el patch minimo: escribimos body a archivo temp,
// y pasamos su path como argv extra; el script lo lee.
char tmp[256];
std::snprintf(tmp, sizeof(tmp), "%s%snav_body_%lld.json",
#ifdef _WIN32
getenv("TEMP") ? getenv("TEMP") : ".", "\\",
#else
"/tmp", "/",
#endif
(long long)std::time(nullptr));
{
std::ofstream f(tmp, std::ios::binary);
if (f) f.write(body.data(), body.size());
}
const char* code2 = R"PY(
import sys, os, json, traceback
root = os.environ.get('FN_REGISTRY_ROOT', '')
if not root:
print(json.dumps({"error":"FN_REGISTRY_ROOT not set"})); sys.exit(2)
for sub in ('core',):
sys.path.insert(0, os.path.join(root, 'python', 'functions', sub))
try:
from infer_json_rows_schema import infer_json_rows_schema
with open(sys.argv[1], 'rb') as f: body = f.read().decode('utf-8','replace')
obj = json.loads(body)
res = infer_json_rows_schema(obj)
print(json.dumps(res if isinstance(res, dict) else {"result": res}))
except Exception as e:
print(json.dumps({"error": str(e), "trace": traceback.format_exc()})); sys.exit(1)
)PY";
std::vector<std::string> a2 = {
py_resolve_interpreter(), "-c", code2, tmp
};
PyResult pr = py_run(a2, 30000);
ImGui::SetClipboardText(pr.stdout_data.c_str());
std::remove(tmp);
}).detach();
}
ImGui::SameLine();
ImGui::TextDisabled("(result -> clipboard)");
}
ImGui::Separator();
ImGui::InputTextMultiline("##body", (char*)r.body_text.c_str(), r.body_text.size() + 1,
ImVec2(-1, -1), ImGuiInputTextFlags_ReadOnly);