Files
LLM-Connect/config_gui.lua

256 lines
10 KiB
Lua

-- config_gui.lua
-- LLM API Configuration GUI (llm_root only)
-- v0.8.1: Added timeout field for better control
local core = core
local M = {}
local function has_priv(name, priv)
local p = core.get_player_privs(name) or {}
return p[priv] == true
end
local function get_llm_api()
if not _G.llm_api then
error("[config_gui] llm_api not available")
end
return _G.llm_api
end
function M.show(name)
if not has_priv(name, "llm_root") then
core.chat_send_player(name, "Missing privilege: llm_root")
return
end
local llm_api = get_llm_api()
local cfg = llm_api.config
local W, H = 14.0, 14.5
local PAD = 0.3
local HEADER_H = 0.8
local FIELD_H = 0.8
local BTN_H = 0.9
local fs = {
"formspec_version[6]",
"size[" .. W .. "," .. H .. "]",
"bgcolor[#0f0f0f;both]",
"style_type[*;bgcolor=#1a1a1a;textcolor=#e0e0e0;font=mono]",
}
-- Header
table.insert(fs, "box[0,0;" .. W .. "," .. HEADER_H .. ";#202020]")
table.insert(fs, "label[" .. PAD .. "," .. (HEADER_H/2 - 0.2) .. ";LLM Configuration (llm_root only)]")
table.insert(fs, "label[" .. (W - 4) .. "," .. (HEADER_H/2 - 0.2) .. ";" .. os.date("%H:%M") .. "]")
local y = HEADER_H + PAD * 2
-- API Key
table.insert(fs, "label[" .. PAD .. "," .. y .. ";API Key:]")
y = y + 0.5
table.insert(fs, "field[" .. PAD .. "," .. y .. ";" .. (W - PAD*2) .. "," .. FIELD_H .. ";api_key;;" .. core.formspec_escape(cfg.api_key or "") .. "]")
table.insert(fs, "style[api_key;bgcolor=#1e1e1e]")
y = y + FIELD_H + PAD
-- API URL
table.insert(fs, "label[" .. PAD .. "," .. y .. ";API URL:]")
y = y + 0.5
table.insert(fs, "field[" .. PAD .. "," .. y .. ";" .. (W - PAD*2) .. "," .. FIELD_H .. ";api_url;;" .. core.formspec_escape(cfg.api_url or "") .. "]")
table.insert(fs, "style[api_url;bgcolor=#1e1e1e]")
y = y + FIELD_H + PAD
-- Model
table.insert(fs, "label[" .. PAD .. "," .. y .. ";Model:]")
y = y + 0.5
table.insert(fs, "field[" .. PAD .. "," .. y .. ";" .. (W - PAD*2) .. "," .. FIELD_H .. ";model;;" .. core.formspec_escape(cfg.model or "") .. "]")
table.insert(fs, "style[model;bgcolor=#1e1e1e]")
y = y + FIELD_H + PAD
-- Max Tokens & Temperature (side by side)
table.insert(fs, "label[" .. PAD .. "," .. y .. ";Max Tokens:]")
table.insert(fs, "label[" .. (W/2 + PAD) .. "," .. y .. ";Temperature:]")
y = y + 0.5
local half_w = (W - PAD*3) / 2
table.insert(fs, "field[" .. PAD .. "," .. y .. ";" .. half_w .. "," .. FIELD_H .. ";max_tokens;;" .. tostring(cfg.max_tokens or 4000) .. "]")
table.insert(fs, "style[max_tokens;bgcolor=#1e1e1e]")
table.insert(fs, "field[" .. (W/2 + PAD) .. "," .. y .. ";" .. half_w .. "," .. FIELD_H .. ";temperature;;" .. tostring(cfg.temperature or 0.7) .. "]")
table.insert(fs, "style[temperature;bgcolor=#1e1e1e]")
y = y + FIELD_H + PAD
-- Timeout field (new in v0.8.1)
table.insert(fs, "label[" .. PAD .. "," .. y .. ";Timeout (seconds):]")
y = y + 0.5
table.insert(fs, "field[" .. PAD .. "," .. y .. ";" .. half_w .. "," .. FIELD_H .. ";timeout;;" .. tostring(cfg.timeout or 120) .. "]")
table.insert(fs, "style[timeout;bgcolor=#1e1e1e]")
table.insert(fs, "tooltip[timeout;Global fallback timeout (30-600s). Per-mode overrides below override this.]")
y = y + FIELD_H + PAD
-- Per-mode timeout overrides
table.insert(fs, "label[" .. PAD .. "," .. y .. ";Per-mode timeout overrides (0 = use global):]")
y = y + 0.5
local third_w = (W - PAD * 2 - 0.2 * 2) / 3
local function tx(i) return PAD + i * (third_w + 0.2) end
table.insert(fs, "label[" .. tx(0) .. "," .. y .. ";Chat:]")
table.insert(fs, "label[" .. tx(1) .. "," .. y .. ";IDE:]")
table.insert(fs, "label[" .. tx(2) .. "," .. y .. ";WorldEdit:]")
y = y + 0.45
table.insert(fs, "field[" .. string.format("%.2f", tx(0)) .. "," .. y .. ";" .. string.format("%.2f", third_w) .. "," .. FIELD_H .. ";timeout_chat;;" .. tostring(cfg.timeout_chat or 0) .. "]")
table.insert(fs, "style[timeout_chat;bgcolor=#1e1e1e]")
table.insert(fs, "tooltip[timeout_chat;Chat mode timeout (0 = global)]")
table.insert(fs, "field[" .. string.format("%.2f", tx(1)) .. "," .. y .. ";" .. string.format("%.2f", third_w) .. "," .. FIELD_H .. ";timeout_ide;;" .. tostring(cfg.timeout_ide or 0) .. "]")
table.insert(fs, "style[timeout_ide;bgcolor=#1e1e1e]")
table.insert(fs, "tooltip[timeout_ide;IDE mode timeout (0 = global)]")
table.insert(fs, "field[" .. string.format("%.2f", tx(2)) .. "," .. y .. ";" .. string.format("%.2f", third_w) .. "," .. FIELD_H .. ";timeout_we;;" .. tostring(cfg.timeout_we or 0) .. "]")
table.insert(fs, "style[timeout_we;bgcolor=#1e1e1e]")
table.insert(fs, "tooltip[timeout_we;WorldEdit mode timeout (0 = global)]")
y = y + FIELD_H + PAD * 2
-- WEA toggle + separator
table.insert(fs, "box[" .. PAD .. "," .. y .. ";" .. (W - PAD*2) .. ",0.02;#333333]")
y = y + 0.18
local wea_val = core.settings:get_bool("llm_worldedit_additions", true)
local wea_label = "Enable WorldEditAdditions tools (torus, ellipsoid, erode, convolve...)"
local wea_is_installed = type(worldeditadditions) == "table"
if not wea_is_installed then
wea_label = wea_label .. " [WEA mod not detected]"
end
table.insert(fs, "checkbox[" .. PAD .. "," .. y .. ";wea_enabled;" .. core.formspec_escape(wea_label) .. ";" .. (wea_val and "true" or "false") .. "]")
y = y + 0.55 + PAD
-- 4 Buttons gleichmaessig verteilt: Save, Reload, Test, Close
local btn_count = 4
local btn_spacing = 0.2
local btn_w = (W - PAD * 2 - btn_spacing * (btn_count - 1)) / btn_count
local function bx(i) return PAD + i * (btn_w + btn_spacing) end
table.insert(fs, "button[" .. string.format("%.2f", bx(0)) .. "," .. y .. ";" .. string.format("%.2f", btn_w) .. "," .. BTN_H .. ";save;Save Config]")
table.insert(fs, "button[" .. string.format("%.2f", bx(1)) .. "," .. y .. ";" .. string.format("%.2f", btn_w) .. "," .. BTN_H .. ";reload;Reload]")
table.insert(fs, "button[" .. string.format("%.2f", bx(2)) .. "," .. y .. ";" .. string.format("%.2f", btn_w) .. "," .. BTN_H .. ";test;Test Connection]")
table.insert(fs, "style[close;bgcolor=#3a1a1a;textcolor=#ffaaaa]")
table.insert(fs, "button[" .. string.format("%.2f", bx(3)) .. "," .. y .. ";" .. string.format("%.2f", btn_w) .. "," .. BTN_H .. ";close;✕ Close]")
y = y + BTN_H + PAD
-- Info label
table.insert(fs, "label[" .. PAD .. "," .. y .. ";Note: Runtime changes. Edit minetest.conf for persistence.]")
core.show_formspec(name, "llm_connect:config", table.concat(fs))
end
function M.handle_fields(name, formname, fields)
if not formname:match("^llm_connect:config") then
return false
end
if not has_priv(name, "llm_root") then
return true
end
local llm_api = get_llm_api()
-- WEA checkbox: instant toggle (no Save needed)
if fields.wea_enabled ~= nil then
local val = fields.wea_enabled == "true"
core.settings:set_bool("llm_worldedit_additions", val)
core.chat_send_player(name, "[LLM] WorldEditAdditions tools: " .. (val and "enabled" or "disabled"))
M.show(name)
return true
end
if fields.save then
-- Validation
local max_tokens = tonumber(fields.max_tokens)
local temperature = tonumber(fields.temperature)
local timeout = tonumber(fields.timeout)
if not max_tokens or max_tokens < 1 or max_tokens > 100000 then
core.chat_send_player(name, "[LLM] Error: max_tokens must be between 1 and 100000")
return true
end
if not temperature or temperature < 0 or temperature > 2 then
core.chat_send_player(name, "[LLM] Error: temperature must be between 0 and 2")
return true
end
if not timeout or timeout < 30 or timeout > 600 then
core.chat_send_player(name, "[LLM] Error: timeout must be between 30 and 600 seconds")
return true
end
local timeout_chat = tonumber(fields.timeout_chat) or 0
local timeout_ide = tonumber(fields.timeout_ide) or 0
local timeout_we = tonumber(fields.timeout_we) or 0
for _, t in ipairs({timeout_chat, timeout_ide, timeout_we}) do
if t ~= 0 and (t < 30 or t > 600) then
core.chat_send_player(name, "[LLM] Error: per-mode timeouts must be 0 or between 30-600")
return true
end
end
llm_api.set_config({
api_key = fields.api_key or "",
api_url = fields.api_url or "",
model = fields.model or "",
max_tokens = max_tokens,
temperature = temperature,
timeout = timeout,
timeout_chat = timeout_chat,
timeout_ide = timeout_ide,
timeout_we = timeout_we,
})
core.chat_send_player(name, "[LLM] Configuration updated (runtime only)")
core.log("action", "[llm_connect] Config updated by " .. name)
M.show(name)
return true
elseif fields.reload then
llm_api.reload_config()
core.chat_send_player(name, "[LLM] Configuration reloaded from settings")
core.log("action", "[llm_connect] Config reloaded by " .. name)
M.show(name)
return true
elseif fields.test then
-- Test LLM connection with a simple request
core.chat_send_player(name, "[LLM] Testing connection...")
local messages = {
{role = "user", content = "Reply with just the word 'OK' if you can read this."}
}
llm_api.request(messages, function(result)
if result.success then
core.chat_send_player(name, "[LLM] ✓ Connection test successful!")
core.chat_send_player(name, "[LLM] Response: " .. (result.content or "No content"))
else
core.chat_send_player(name, "[LLM] ✗ Connection test failed!")
core.chat_send_player(name, "[LLM] Error: " .. (result.error or "Unknown error"))
end
end, {timeout = 30})
return true
elseif fields.close or fields.quit then
-- Zurueck zur chat_gui
if _G.chat_gui then
_G.chat_gui.show(name)
else
core.close_formspec(name, "llm_connect:config")
end
return true
end
return true
end
return M