diff --git a/cmd/verstak-gui/bindings_plugins.go b/cmd/verstak-gui/bindings_plugins.go index 4839978..268248d 100644 --- a/cmd/verstak-gui/bindings_plugins.go +++ b/cmd/verstak-gui/bindings_plugins.go @@ -1,10 +1,12 @@ package main import ( + "encoding/json" "fmt" "log" "os" "path/filepath" + "strings" "verstak/internal/core/config" @@ -202,16 +204,80 @@ func (a *App) ListSystemViewsWithPlugins() []SystemViewDTO { return base } -// CallPluginAction invokes a named Lua hook on a specific plugin. -func (a *App) CallPluginAction(pluginName, action string, paramsJSON string) (string, error) { +// CallPluginFunction calls a global Lua function on an active plugin. +// The funcName can use dots: "calendar.create_event" → _G.calendar.create_event +// Returns JSON string or error. +func (a *App) CallPluginFunction(pluginName, funcName string, paramsJSON string) (string, error) { if a.plugins == nil { return "", fmt.Errorf("plugin manager not ready") } - result, err := a.plugins.CallPluginHook(pluginName, action, lua.LString(paramsJSON)) - if err != nil { - return "", fmt.Errorf("plugin call %s: %w", action, err) + for _, p := range a.plugins.Plugins() { + if p.Meta.Name != pluginName || !p.Active { + continue + } + vm := p.VM() + if vm == nil { + continue + } + // Resolve dotted path: "calendar.create_event" → _G.calendar.create_event + parts := strings.SplitN(funcName, ".", 2) + var luaFn string + if len(parts) == 2 { + luaFn = fmt.Sprintf("_G.%s['%s']", parts[0], parts[1]) + } else { + luaFn = fmt.Sprintf("_G['%s']", funcName) + } + + // Parse params + var params interface{} + if paramsJSON != "" && paramsJSON != "{}" { + if err := json.Unmarshal([]byte(paramsJSON), ¶ms); err != nil { + params = paramsJSON + } + } + + // Convert params to Lua value + var luaArg lua.LValue + switch v := params.(type) { + case nil: + luaArg = lua.LNil + case string: + luaArg = lua.LString(v) + case float64: + luaArg = lua.LNumber(v) + case bool: + luaArg = lua.LBool(v) + case map[string]interface{}: + tbl := vm.LState().NewTable() + for key, val := range v { + switch sv := val.(type) { + case string: + tbl.RawSetString(key, lua.LString(sv)) + case float64: + tbl.RawSetString(key, lua.LNumber(sv)) + case bool: + tbl.RawSetString(key, lua.LBool(sv)) + } + } + luaArg = tbl + default: + luaArg = lua.LString(paramsJSON) + } + + var script string + if luaArg == lua.LNil { + script = fmt.Sprintf("return %s()", luaFn) + } else { + script = fmt.Sprintf("return %s(%s)", luaFn, luaArg.String()) + } + + result, err := vm.DoString(script) + if err != nil { + return "", fmt.Errorf("call %s: %w", funcName, err) + } + return result, nil } - return result.String(), nil + return "", fmt.Errorf("plugin %q not active or not found", pluginName) } // ReloadPlugins re-scans the plugins directory and re-initializes runtimes. diff --git a/frontend/src/lib/PluginPage.svelte b/frontend/src/lib/PluginPage.svelte index 49bf3fd..fc67087 100644 --- a/frontend/src/lib/PluginPage.svelte +++ b/frontend/src/lib/PluginPage.svelte @@ -1,6 +1,5 @@
@@ -40,11 +201,12 @@
{#if loading} -

{t('common.loading')}

+

Загрузка…

{:else if error}

{error}

{:else if htmlPanel}