chore: snapshot WIP previo + flow 0008 + 7 sub-issues (0112-0119)
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>
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user