// data_table_ai_panel — modal "Ask AI" de la tabla TQL. // Sub-funcion extraida de modules/data_table/data_table.cpp (issue 0107c). // // Rangos de lineas del fuente original (antes de la extraccion): // - Stub llm_anthropic (no-op) : lineas 82-112 // - Modal "Ask AI" completo : lineas 4209-4328 // - Boton trigger en viz selector: linea 1588 // // NOTA DEUDA TECNICA: llm_anthropic no esta en el registry todavia (Wave 4 TODO). // Pendiente promover a cpp/functions/infra/llm_anthropic (.h + .cpp + .md). #include "viz/data_table_ai_panel.h" #include "core/data_table_types.h" #include "core/tql_apply.h" #include "imgui.h" // llm_anthropic: usar header real si disponible, stub si no. #ifdef FN_LLM_ANTHROPIC # include "core/llm_anthropic.h" #else // Stub no-op (inline en este TU hasta que llm_anthropic se promueva al registry). // TODO: Wave 4 — promover a cpp/functions/infra/llm_anthropic.cpp + .h + .md namespace llm_anthropic { enum class OutputMode { TQL, SQL }; struct AskInput { std::string question; std::string tql_current; std::vector col_names; std::vector col_types; std::vector joinable_names; OutputMode mode = OutputMode::TQL; std::string model; int max_tokens = 8192; }; struct AskResult { std::string code; std::string raw; std::string error; int tokens_in = 0; int tokens_out = 0; }; inline AskResult ask(const AskInput&, const std::string& = "") { AskResult r; r.error = "llm_anthropic not available (stub). Build with FN_LLM_ANTHROPIC=1."; return r; } } // namespace llm_anthropic #endif #include #include #include namespace data_table { // --------------------------------------------------------------------------- // draw_ask_ai_modal // Dibuja el modal "Ask AI". Debe llamarse cada frame (patron ImGui). // Abre el popup si ask_ai.open == true. // --------------------------------------------------------------------------- void draw_ask_ai_modal(AskAiState& ask_ai, State& st, const std::vector& active_headers, const std::vector& active_types, int orig_cols) { if (ask_ai.open) ImGui::OpenPopup("Ask AI"); ImGui::SetNextWindowSize(ImVec2(820, 560), ImGuiCond_Appearing); if (ImGui::BeginPopupModal("Ask AI", &ask_ai.open, ImGuiWindowFlags_NoSavedSettings)) { ImGui::TextDisabled("Ask en lenguaje natural. Default TQL. SQL solo si DuckDB linkado."); const char* modes[] = {"TQL", "SQL (DuckDB)"}; #ifndef FN_TQL_DUCKDB // SQL mode disabled visually pero el toggle existe (informativo) if (ask_ai.mode == 1) ask_ai.mode = 0; #endif ImGui::Combo("Output##askmode", &ask_ai.mode, modes, IM_ARRAYSIZE(modes)); #ifndef FN_TQL_DUCKDB if (ask_ai.mode == 1) { ImGui::TextColored(ImVec4(1, 0.5f, 0.3f, 1), "SQL mode requires FN_TQL_DUCKDB=1 build flag."); } #endif ImGui::InputTextMultiline("##ask_q", ask_ai.question, sizeof(ask_ai.question), ImVec2(-1, 80)); ImGui::BeginDisabled(ask_ai.busy); if (ImGui::Button("Send")) { ask_ai.busy = true; ask_ai.status = "Sending..."; ask_ai.error.clear(); ask_ai.response_code.clear(); ask_ai.response_raw.clear(); // Build AskInput desde el state actual. llm_anthropic::AskInput in; in.question = ask_ai.question; in.tql_current = ask_ai.current_tql; in.col_names = active_headers; in.col_types = active_types; in.mode = (ask_ai.mode == 1) ? llm_anthropic::OutputMode::SQL : llm_anthropic::OutputMode::TQL; // Llamada blocking (UI freeze breve durante red). auto r = llm_anthropic::ask(in); ask_ai.busy = false; if (!r.error.empty()) { ask_ai.error = r.error; ask_ai.status = "Error"; } else { ask_ai.response_raw = r.raw; ask_ai.response_code = r.code; ask_ai.status = "Got response."; // Llenar edit buffer std::snprintf(ask_ai.edit_buf, sizeof(ask_ai.edit_buf), "%s", r.code.c_str()); } } ImGui::EndDisabled(); ImGui::SameLine(); if (!ask_ai.status.empty()) { ImGui::TextDisabled("%s", ask_ai.status.c_str()); } if (!ask_ai.error.empty()) { ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", ask_ai.error.c_str()); } ImGui::Separator(); ImGui::Columns(2, "ask_cols", true); ImGui::TextUnformatted("Current"); ImGui::InputTextMultiline("##ask_cur", const_cast(ask_ai.current_tql.c_str()), ask_ai.current_tql.size() + 1, ImVec2(-1, 240), ImGuiInputTextFlags_ReadOnly); ImGui::NextColumn(); ImGui::TextUnformatted("Proposed (editable before apply)"); ImGui::InputTextMultiline("##ask_new", ask_ai.edit_buf, sizeof(ask_ai.edit_buf), ImVec2(-1, 240)); ImGui::Columns(1); bool can_apply = !ask_ai.busy && ask_ai.edit_buf[0] != '\0'; ImGui::BeginDisabled(!can_apply); if (ImGui::Button("Apply")) { std::string err; if (ask_ai.mode == 0) { // TQL apply bool ok = tql::apply(ask_ai.edit_buf, st, active_headers, active_types, nullptr, 0, orig_cols, &err); if (ok) { ask_ai.status = "Applied OK."; ask_ai.open = false; } else { ask_ai.error = "tql::apply error: " + err; ask_ai.status = "Apply failed."; } } else { #ifdef FN_TQL_DUCKDB // SQL apply: ejecutar via tql_duckdb sobre TableInputs activas. // Para tablas en memoria construimos un TableInput basico desde // active_headers/types. v1 no recupera cells originales aqui; // reportamos solo error si fallo. Caller real deberia pasar // tables() del render scope. Sin esto, marcamos status info. ask_ai.status = "SQL execute disponible (FN_TQL_DUCKDB ON). " "Integracion full pendiente: usar tql_duckdb::execute desde caller."; #else ask_ai.status = "SQL execute requires FN_TQL_DUCKDB build flag."; #endif } } ImGui::EndDisabled(); ImGui::SameLine(); if (ImGui::Button("Reject")) { ask_ai.response_code.clear(); ask_ai.edit_buf[0] = '\0'; } ImGui::SameLine(); if (ImGui::Button("Close")) { ask_ai.open = false; } ImGui::EndPopup(); } } } // namespace data_table