368 lines
9.9 KiB
C++
368 lines
9.9 KiB
C++
#include "deployment_panel.h"
|
|
|
|
#include <imgui.h>
|
|
|
|
#include <cstring>
|
|
|
|
namespace afs {
|
|
namespace studio {
|
|
namespace ui {
|
|
|
|
DeploymentPanel::DeploymentPanel() {
|
|
// Default test prompt
|
|
std::strncpy(test_prompt_buffer_.data(),
|
|
"Write a simple NOP instruction in 65816 assembly:",
|
|
test_prompt_buffer_.size() - 1);
|
|
}
|
|
|
|
void DeploymentPanel::Render(const ModelMetadata* selected_model) {
|
|
if (!selected_model) {
|
|
ImGui::TextDisabled("Select a model to view deployment options.");
|
|
return;
|
|
}
|
|
|
|
const auto& model = *selected_model;
|
|
|
|
// Header
|
|
ImGui::Text("%s", model.display_name.empty() ? model.model_id.c_str()
|
|
: model.display_name.c_str());
|
|
ImGui::Separator();
|
|
|
|
// Status display
|
|
if (is_busy_ || !status_message_.empty()) {
|
|
RenderDeploymentStatus();
|
|
}
|
|
|
|
// Quick actions
|
|
RenderQuickActions(model);
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
|
|
// Ollama deployment section
|
|
if (ImGui::CollapsingHeader("Deploy to Ollama",
|
|
ImGuiTreeNodeFlags_DefaultOpen)) {
|
|
RenderOllamaDeployment(model);
|
|
}
|
|
|
|
// Conversion options
|
|
if (ImGui::CollapsingHeader("Format Conversion")) {
|
|
RenderConversionOptions(model);
|
|
}
|
|
|
|
// Test prompt
|
|
if (ImGui::CollapsingHeader("Test Model")) {
|
|
RenderTestPrompt(model);
|
|
}
|
|
}
|
|
|
|
void DeploymentPanel::RenderDeploymentStatus() {
|
|
if (is_busy_) {
|
|
ImGui::TextColored(ImVec4(0.2f, 0.7f, 0.9f, 1.0f), "%s",
|
|
current_operation_.c_str());
|
|
ImGui::ProgressBar(progress_, ImVec2(-1, 0));
|
|
}
|
|
|
|
if (!status_message_.empty()) {
|
|
bool is_error = !last_error_.empty();
|
|
ImVec4 color = is_error ? ImVec4(0.9f, 0.3f, 0.3f, 1.0f)
|
|
: ImVec4(0.3f, 0.9f, 0.3f, 1.0f);
|
|
ImGui::TextColored(color, "%s", status_message_.c_str());
|
|
}
|
|
|
|
if (!last_error_.empty()) {
|
|
ImGui::TextWrapped("Error: %s", last_error_.c_str());
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
}
|
|
|
|
void DeploymentPanel::RenderQuickActions(const ModelMetadata& model) {
|
|
// Check current state
|
|
bool is_local = model.locations.count("mac") > 0;
|
|
bool has_gguf = false;
|
|
for (const auto& fmt : model.formats) {
|
|
if (fmt == "gguf") {
|
|
has_gguf = true;
|
|
break;
|
|
}
|
|
}
|
|
bool in_ollama = false;
|
|
for (const auto& backend : model.deployed_backends) {
|
|
if (backend == "ollama") {
|
|
in_ollama = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Quick action buttons
|
|
ImGui::Text("Quick Actions:");
|
|
|
|
// Pull button
|
|
ImGui::BeginDisabled(is_busy_ || is_local);
|
|
if (ImGui::Button("Pull to Mac")) {
|
|
is_busy_ = true;
|
|
current_operation_ = "Pulling model...";
|
|
progress_ = 0.1f;
|
|
status_message_.clear();
|
|
last_error_.clear();
|
|
|
|
// Execute pull
|
|
last_result_ = actions_.PullModel(model.model_id);
|
|
is_busy_ = false;
|
|
|
|
if (last_result_.status == ActionStatus::kCompleted) {
|
|
status_message_ = "Model pulled successfully";
|
|
} else {
|
|
status_message_ = "Pull failed";
|
|
last_error_ = last_result_.error;
|
|
}
|
|
}
|
|
ImGui::EndDisabled();
|
|
ImGui::SameLine();
|
|
if (!is_local) {
|
|
ImGui::TextDisabled("(not local)");
|
|
} else {
|
|
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "(local)");
|
|
}
|
|
|
|
ImGui::SameLine(0, 20);
|
|
|
|
// Convert button
|
|
ImGui::BeginDisabled(is_busy_ || !is_local || has_gguf);
|
|
if (ImGui::Button("Convert to GGUF")) {
|
|
is_busy_ = true;
|
|
current_operation_ = "Converting to GGUF...";
|
|
progress_ = 0.2f;
|
|
status_message_.clear();
|
|
last_error_.clear();
|
|
|
|
auto quant_opts = GetQuantizationOptions();
|
|
std::string quant = quant_opts[selected_quantization_].name;
|
|
|
|
last_result_ = actions_.ConvertToGGUF(model.model_id, quant);
|
|
is_busy_ = false;
|
|
|
|
if (last_result_.status == ActionStatus::kCompleted) {
|
|
status_message_ = "Conversion complete";
|
|
} else {
|
|
status_message_ = "Conversion failed";
|
|
last_error_ = last_result_.error;
|
|
}
|
|
}
|
|
ImGui::EndDisabled();
|
|
ImGui::SameLine();
|
|
if (has_gguf) {
|
|
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "(has GGUF)");
|
|
}
|
|
|
|
ImGui::SameLine(0, 20);
|
|
|
|
// Deploy button
|
|
ImGui::BeginDisabled(is_busy_ || in_ollama);
|
|
if (ImGui::Button("Deploy to Ollama")) {
|
|
is_busy_ = true;
|
|
current_operation_ = "Deploying to Ollama...";
|
|
progress_ = 0.3f;
|
|
status_message_.clear();
|
|
last_error_.clear();
|
|
|
|
std::string name(ollama_name_buffer_.data());
|
|
auto quant_opts = GetQuantizationOptions();
|
|
std::string quant = quant_opts[selected_quantization_].name;
|
|
|
|
last_result_ = actions_.DeployToOllama(model.model_id, name, quant);
|
|
is_busy_ = false;
|
|
|
|
if (last_result_.status == ActionStatus::kCompleted) {
|
|
status_message_ = "Deployed to Ollama";
|
|
} else {
|
|
status_message_ = "Deployment failed";
|
|
last_error_ = last_result_.error;
|
|
}
|
|
}
|
|
ImGui::EndDisabled();
|
|
ImGui::SameLine();
|
|
if (in_ollama) {
|
|
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "(in Ollama)");
|
|
}
|
|
}
|
|
|
|
void DeploymentPanel::RenderOllamaDeployment(const ModelMetadata& model) {
|
|
ImGui::Indent();
|
|
|
|
// Ollama name input
|
|
ImGui::Text("Ollama Model Name:");
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(200);
|
|
ImGui::InputTextWithHint("##OllamaName", model.model_id.c_str(),
|
|
ollama_name_buffer_.data(),
|
|
ollama_name_buffer_.size());
|
|
|
|
// Quantization selection
|
|
ImGui::Text("Quantization:");
|
|
auto quant_opts = GetQuantizationOptions();
|
|
for (int i = 0; i < static_cast<int>(quant_opts.size()); ++i) {
|
|
ImGui::SameLine();
|
|
if (ImGui::RadioButton(quant_opts[i].name.c_str(),
|
|
selected_quantization_ == i)) {
|
|
selected_quantization_ = i;
|
|
}
|
|
}
|
|
|
|
// Current quantization info
|
|
const auto& selected_quant = quant_opts[selected_quantization_];
|
|
ImGui::TextDisabled("%s (%.0f%% of F16 size)",
|
|
selected_quant.description.c_str(),
|
|
selected_quant.size_ratio * 100.0f);
|
|
|
|
// Status indicators
|
|
ImGui::Spacing();
|
|
bool ollama_running = actions_.IsOllamaRunning();
|
|
bool llama_available = actions_.IsLlamaCppAvailable();
|
|
|
|
ImGui::TextDisabled("Prerequisites:");
|
|
ImGui::BulletText("Ollama: %s",
|
|
ollama_running ? "Running" : "Not running");
|
|
if (!ollama_running) {
|
|
ImGui::SameLine();
|
|
ImGui::TextColored(ImVec4(0.9f, 0.6f, 0.2f, 1.0f), "(run: ollama serve)");
|
|
}
|
|
|
|
ImGui::BulletText("llama.cpp: %s",
|
|
llama_available ? "Available" : "Not found");
|
|
|
|
ImGui::Unindent();
|
|
}
|
|
|
|
void DeploymentPanel::RenderConversionOptions(const ModelMetadata& model) {
|
|
ImGui::Indent();
|
|
|
|
// Current formats
|
|
ImGui::Text("Available formats:");
|
|
for (const auto& fmt : model.formats) {
|
|
ImGui::SameLine();
|
|
ImGui::TextColored(ImVec4(0.4f, 0.7f, 0.9f, 1.0f), "[%s]", fmt.c_str());
|
|
}
|
|
|
|
if (model.formats.empty()) {
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("(none)");
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
|
|
// Conversion buttons
|
|
ImGui::BeginDisabled(is_busy_);
|
|
if (ImGui::Button("Convert to GGUF (Q4_K_M)")) {
|
|
is_busy_ = true;
|
|
current_operation_ = "Converting to GGUF Q4_K_M...";
|
|
last_result_ = actions_.ConvertToGGUF(model.model_id, "Q4_K_M");
|
|
is_busy_ = false;
|
|
status_message_ = last_result_.status == ActionStatus::kCompleted
|
|
? "Conversion complete"
|
|
: "Conversion failed";
|
|
if (last_result_.status != ActionStatus::kCompleted) {
|
|
last_error_ = last_result_.error;
|
|
}
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Convert to GGUF (Q5_K_M)")) {
|
|
is_busy_ = true;
|
|
current_operation_ = "Converting to GGUF Q5_K_M...";
|
|
last_result_ = actions_.ConvertToGGUF(model.model_id, "Q5_K_M");
|
|
is_busy_ = false;
|
|
status_message_ = last_result_.status == ActionStatus::kCompleted
|
|
? "Conversion complete"
|
|
: "Conversion failed";
|
|
if (last_result_.status != ActionStatus::kCompleted) {
|
|
last_error_ = last_result_.error;
|
|
}
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
ImGui::Unindent();
|
|
}
|
|
|
|
void DeploymentPanel::RenderTestPrompt(const ModelMetadata& model) {
|
|
ImGui::Indent();
|
|
|
|
// Check if model is deployed
|
|
bool can_test = false;
|
|
std::string deployed_to;
|
|
for (const auto& backend : model.deployed_backends) {
|
|
if (backend == "ollama") {
|
|
can_test = true;
|
|
deployed_to = "ollama";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!can_test) {
|
|
ImGui::TextDisabled("Deploy model to a backend first to test.");
|
|
ImGui::Unindent();
|
|
return;
|
|
}
|
|
|
|
// Test prompt input
|
|
ImGui::Text("Test Prompt:");
|
|
ImGui::SetNextItemWidth(-1);
|
|
ImGui::InputTextMultiline("##TestPrompt", test_prompt_buffer_.data(),
|
|
test_prompt_buffer_.size(), ImVec2(0, 60));
|
|
|
|
// Run test button
|
|
ImGui::BeginDisabled(is_busy_);
|
|
if (ImGui::Button("Run Test")) {
|
|
is_busy_ = true;
|
|
current_operation_ = "Testing model...";
|
|
status_message_.clear();
|
|
last_error_.clear();
|
|
test_output_.clear();
|
|
|
|
last_result_ = actions_.TestModel(model.model_id, DeploymentBackend::kOllama,
|
|
std::string(test_prompt_buffer_.data()));
|
|
is_busy_ = false;
|
|
|
|
if (last_result_.status == ActionStatus::kCompleted) {
|
|
status_message_ = "Test completed";
|
|
test_output_ = last_result_.output;
|
|
} else {
|
|
status_message_ = "Test failed";
|
|
last_error_ = last_result_.error;
|
|
}
|
|
}
|
|
ImGui::EndDisabled();
|
|
|
|
// Test output
|
|
if (!test_output_.empty()) {
|
|
ImGui::Spacing();
|
|
ImGui::Text("Response:");
|
|
ImGui::BeginChild("TestOutput", ImVec2(0, 100), true);
|
|
ImGui::TextWrapped("%s", test_output_.c_str());
|
|
ImGui::EndChild();
|
|
}
|
|
|
|
ImGui::Unindent();
|
|
}
|
|
|
|
void RenderDeploymentPanelWindow(DeploymentPanel& panel,
|
|
const ModelMetadata* model,
|
|
bool* open) {
|
|
if (!open || !*open) return;
|
|
|
|
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
|
|
|
|
if (!ImGui::Begin("Deployment", open)) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
panel.Render(model);
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
} // namespace ui
|
|
} // namespace studio
|
|
} // namespace afs
|