b9716a7cd6
Snapshot de WIP acumulado de sesiones previas antes de merge wave 1 del flow 0008 (kanban_cpp + agent_runner_api + DoD schema). Incluye: - dev/flows/0008-kanban-cpp-and-agent-workflows.md - dev/issues/0112-0119*.md (7 sub-issues) - WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
189 lines
7.3 KiB
C++
189 lines
7.3 KiB
C++
// 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<std::string> col_names;
|
|
std::vector<data_table::ColumnType> col_types;
|
|
std::vector<std::string> 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 <string>
|
|
#include <vector>
|
|
#include <cstdio>
|
|
|
|
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<std::string>& active_headers,
|
|
const std::vector<ColumnType>& 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<char*>(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
|