28 using ModelAction = std::function<void(
const std::string& model_id)>;
50 progress_.erase(model_id);
52 progress_[model_id] = fraction;
58 if (can_close_ == can_close)
return;
59 can_close_ = can_close;
75 static std::unique_ptr<Label> make_label(
const std::string&
text,
float size, canvas::Color
color,
77 auto l = std::make_unique<Label>(
text);
78 l->set_font_size(size);
80 if (
width > 0.0f)
l->flex().preferred_width =
width;
84 std::unique_ptr<TextButton> make_action(
const std::string&
label,
const std::string&
id,
88 auto b = std::make_unique<TextButton>(
label);
89 b->flex().preferred_width = 96.0f;
90 b->flex().preferred_height = 28.0f;
91 b->on_click = [
this,
id, member] {
92 if (this->*member) (this->*member)(
id);
99 static std::unique_ptr<View> make_progress_bar(
float fraction) {
100 const float f = fraction < 0.0f ? 0.0f : (fraction > 1.0f ? 1.0f : fraction);
101 auto track = std::make_unique<View>();
103 track->flex().flex_grow = 1.0f;
104 track->flex().preferred_height = 6.0f;
106 auto fill = std::make_unique<View>();
107 fill->flex().flex_grow =
f < 0.001f ? 0.001f :
f;
109 auto rest = std::make_unique<View>();
110 rest->flex().flex_grow = (1.0f -
f) < 0.001f ? 0.001f : (1.0f -
f);
111 track->add_child(std::move(
fill));
112 track->add_child(std::move(rest));
120 auto header = std::make_unique<View>();
123 header->flex().preferred_height = 30.0f;
124 auto title = make_label(
"Models", 20.0f, kWhite());
125 title->set_font_weight(700);
126 title->flex().flex_grow = 1.0f;
127 header->add_child(std::move(title));
129 auto done = std::make_unique<ToggleButton>();
130 done->set_label(
"Done");
131 done->flex().preferred_width = 88.0f;
132 done->flex().preferred_height = 28.0f;
134 header->add_child(std::move(done));
138 add_child(make_label(
"Download a model to enable generation. Stored once, shared across plugins.",
141 if (models_.models.empty()) {
142 add_child(make_label(
"No models available yet.", 14.0f, kMuted()));
146 for (
const auto& listed : models_.models) {
147 const std::string
id = listed.model.model_id;
148 const auto it = progress_.find(
id);
149 const bool downloading = it != progress_.end();
150 const bool paused = !downloading && listed.status ==
"partial";
152 auto block = std::make_unique<View>();
154 block->flex().gap = 6.0f;
157 auto line = std::make_unique<View>();
160 line->flex().gap = 10.0f;
161 line->flex().preferred_height = 30.0f;
163 std::string name = listed.model.display_name.empty() ?
id : listed.model.display_name;
164 if (listed.model.is_recommended) name =
"★ " + name;
165 auto name_label = make_label(name, 14.0f, kWhite());
166 name_label->flex().flex_grow = 1.0f;
167 name_label->flex().flex_shrink = 0.0f;
168 line->add_child(std::move(name_label));
171 const int pct =
static_cast<int>(it->second * 100.0f + 0.5f);
172 line->add_child(make_label(std::to_string(pct) +
"%", 12.0f, kTeal(), 44.0f));
175 const float frac = listed.partial_fraction < 0.0f ? 0.0f : listed.partial_fraction;
176 line->add_child(make_label(
"Paused " + std::to_string(
static_cast<int>(frac * 100.0f + 0.5f)) +
"%",
177 12.0f, kMuted(), 90.0f));
180 }
else if (listed.status ==
"installed") {
181 line->add_child(make_label(listed.active ?
"Active" :
"Installed", 12.0f,
182 listed.active ? kTeal() : kBlue(), 80.0f));
187 line->add_child(make_label(
"Available", 12.0f, kMuted(), 80.0f));
190 block->add_child(std::move(line));
194 block->add_child(make_progress_bar(it->second));
196 block->add_child(make_progress_bar(listed.partial_fraction));
202 runtime::ModelListResult models_;
203 std::map<std::string, float> progress_;
204 bool can_close_ =
false;