use super::{ConfigState, NEW_WORKFLOW_HINT, RELOAD_HINT, form_ui, status};
use crate::config_edit::form::{self, Schema};
use crate::config_edit::lernie_global::Editor;
use crate::model_pick::grammar::declare_model;
use crate::theme;
use status::{describe_saved, reload_status, status_line};
use std::path::PathBuf;
const DECLARE_HINT: &str = "The model id to declare in models.yaml, on the provider row picked \
beside it. Declare drafts the entry; Apply writes it. Typed, the same write is \
`/model <role> <provider> <model-id>`.";
pub(super) fn render(ui: &mut egui::Ui, config: &mut ConfigState, provider_rows: &[String]) {
ui.heading(egui::RichText::new("lernie global config").color(theme::integration_hue("lernie")));
ui.monospace(config.lernie_editor.path().display().to_string());
if config.lernie_editor.is_new() {
ui.weak("(new file)");
}
match schema(config) {
Some(schema) => settings(ui, config, &schema, provider_rows),
None => {
ui.weak(form_ui::NO_READER);
}
}
raw(ui, config);
ui.horizontal(|ui| {
if ui
.button("Apply")
.on_hover_text(
"Write this file, refusing it if it names a model whose provider \
brazen does not have — which is the failure you would otherwise \
only meet mid-conversation. Typed, it is `/config models <text…>`.",
)
.clicked()
{
config.lernie_status =
describe_saved(config.lernie_editor.apply(provider_rows, &config.io));
}
if ui.button("Reload").on_hover_text(RELOAD_HINT).clicked() {
config.lernie_status = reload_status(config.lernie_editor.reload(&config.io));
}
if ui
.button("models.yaml")
.on_hover_text(
"Load lernie's global models.yaml into this editor. `/config models` \
with no text reads the same bytes.",
)
.clicked()
{
open(config, config.lernie.models());
}
});
workflow_list(ui, config);
ui.horizontal(|ui| {
ui.label("new workflow:").on_hover_text(NEW_WORKFLOW_HINT);
ui.text_edit_singleline(&mut config.new_workflow)
.on_hover_text(NEW_WORKFLOW_HINT);
if ui
.button("Create")
.on_hover_text(
"Open an empty editor for a workflow file of that name. Nothing is \
written to disk until you press Apply — `/config workflow <name> \
<text…>` writes one outright.",
)
.clicked()
{
new_workflow(config);
}
});
status_line(ui, &config.lernie_status);
}
fn schema(config: &ConfigState) -> Option<Schema> {
let name = config.lernie_editor.path().file_name()?;
form::schema_for(&name.to_string_lossy())
}
fn settings(
ui: &mut egui::Ui,
config: &mut ConfigState,
schema: &Schema,
provider_rows: &[String],
) {
let groups = form::read(schema, config.lernie_editor.draft(), provider_rows);
if let Some((row, value)) = form_ui::render(ui, "lernie", &groups, provider_rows) {
match form::write(schema, config.lernie_editor.draft(), &row, &value) {
Ok(text) => {
config.lernie_editor.set_draft(text);
config.lernie_status =
format!("{}.{} drafted — Apply to write", row.entry, row.field);
}
Err(e) => config.lernie_status = e.to_string(),
}
}
declare(ui, config, schema, provider_rows);
}
fn declare(ui: &mut egui::Ui, config: &mut ConfigState, schema: &Schema, provider_rows: &[String]) {
if schema.block != crate::model_pick::grammar::MODELS {
return;
}
ui.horizontal(|ui| {
ui.label("declare model:");
ui.text_edit_singleline(&mut config.new_model)
.on_hover_text(DECLARE_HINT);
egui::ComboBox::from_id_salt("declare-model-row")
.selected_text(&config.new_model_row)
.show_ui(ui, |ui| {
for name in provider_rows {
ui.selectable_value(&mut config.new_model_row, name.clone(), name)
.on_hover_text(
"The provider row this model calls through — one of the \
rows brazen actually has. Typed, the pick is the middle \
word of `/model <role> <provider> <model-id>`.",
);
}
})
.response
.on_hover_text(
"Which provider row the new model calls through — one of the rows \
brazen actually has. Typed, the pick is the middle word of \
`/model <role> <provider> <model-id>`.",
);
if ui
.button("Declare")
.on_hover_text(
"Draft a `models:` entry for that id on the picked provider row — \
the same write the model picker makes. Nothing lands until Apply; \
`/model <role> <provider> <model-id>` does both halves at once.",
)
.clicked()
{
config.lernie_status = declared(config);
}
});
}
fn declared(config: &mut ConfigState) -> String {
match declare_model(
config.lernie_editor.draft(),
config.new_model.trim(),
config.new_model_row.trim(),
) {
Ok(Some(text)) => {
config.lernie_editor.set_draft(text);
"declared — Apply to write".to_string()
}
Ok(None) => "already declared on that row".to_string(),
Err(e) => e.to_string(),
}
}
fn raw(ui: &mut egui::Ui, config: &mut ConfigState) {
egui::CollapsingHeader::new("raw text")
.id_salt("raw-models")
.show(ui, |ui| {
form_ui::raw_editor(ui, config.lernie_editor.draft_mut());
})
.header_response
.on_hover_text(
"Open the file's raw text — everything it carries beyond the settings \
above. Edits here change nothing until Apply. No key of its own: Tab \
reaches it, Space presses it.",
);
}
fn workflow_list(ui: &mut egui::Ui, config: &mut ConfigState) {
let files = config.workflows.clone();
ui.horizontal_wrapped(|ui| {
for file in files {
let label = file.file_name().map(|n| n.to_string_lossy().into_owned());
if ui
.button(label.unwrap_or_default())
.on_hover_text(
"Load this workflow file into this editor. \
`/config workflow <name>` with no text reads the same bytes.",
)
.clicked()
{
open(config, file);
}
}
});
}
fn new_workflow(config: &mut ConfigState) {
match config.lernie.new_workflow(&config.new_workflow) {
Ok(path) => {
config.lernie_editor = Editor::seeded(path, b"");
config.lernie_status = "new workflow ready".to_string();
}
Err(e) => config.lernie_status = e.to_string(),
}
}
fn open(config: &mut ConfigState, path: PathBuf) {
match Editor::load(path.clone(), &config.io) {
Ok(editor) => config.lernie_editor = editor,
Err(_) => config.lernie_editor = Editor::seeded(path, b""),
}
}