From 52902a83838096f5862e3fac46a84b870ce3cfe1 Mon Sep 17 00:00:00 2001 From: Hein Date: Sat, 11 Apr 2026 23:03:34 +0200 Subject: [PATCH] feat(onboard): add interactive extra maps configuration step --- cmd/vecna/onboard.go | 145 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 6 deletions(-) diff --git a/cmd/vecna/onboard.go b/cmd/vecna/onboard.go index fe49f50..140c251 100644 --- a/cmd/vecna/onboard.go +++ b/cmd/vecna/onboard.go @@ -34,7 +34,7 @@ func runOnboard(_ *cobra.Command, _ []string) error { // ── Step 1: Discover ────────────────────────────────────────────────────── - step(1, 5, "Discover embedding servers") + step(1, 6, "Discover embedding servers") fmt.Println("Scanning (Ollama, LM Studio, vLLM, LocalAI, Jan, Kobold, Tabby)...") servers := discovery.Scan(context.Background()) @@ -89,7 +89,7 @@ func runOnboard(_ *cobra.Command, _ []string) error { // ── Step 2: Detect dimensions ───────────────────────────────────────────── - step(2, 5, "Detect model dimensions") + step(2, 6, "Detect model dimensions") for i := range targets { fmt.Printf("Probing %s / %s ... ", targets[i].endpoint, targets[i].model) @@ -106,7 +106,7 @@ func runOnboard(_ *cobra.Command, _ []string) error { // ── Step 3: Configure adapter ───────────────────────────────────────────── - step(3, 5, "Configure dimension adapter") + step(3, 6, "Configure dimension adapter") // Use the first target's detected dim as the source dimension default firstDim := 0 @@ -156,7 +156,7 @@ func runOnboard(_ *cobra.Command, _ []string) error { // ── Step 4: Configure vecna server ──────────────────────────────────────── - step(4, 5, "Configure vecna server") + step(4, 6, "Configure vecna server") portRaw, err := promptString(in, "Bind port [8080]: ", "8080") if err != nil { @@ -189,9 +189,44 @@ func runOnboard(_ *cobra.Command, _ []string) error { } fmt.Println() - // ── Step 5: Test & write ────────────────────────────────────────────────── + // ── Step 5: Extra maps ──────────────────────────────────────────────────── - step(5, 5, "Test connections and write config") + step(5, 6, "Configure extra maps (optional)") + + extraMaps := map[string]config.ExtraMapConfig{} + + addMaps, err := promptBool(in, "Add extra dimension maps (/map/{key}/v1/embeddings)?", false) + if err != nil { + return err + } + if addMaps { + // Build the list of target names from the already-collected targets. + targetNames := make([]string, 0, len(targets)) + for _, t := range targets { + targetNames = append(targetNames, t.name) + } + + for { + mc, mapErr := collectExtraMap(in, sourceDim, targetDim, adapterType, truncateMode, padMode, targetNames) + if mapErr != nil { + return mapErr + } + extraMaps[mc.key] = mc.cfg + + another, promptErr := promptBool(in, "Add another extra map?", false) + if promptErr != nil { + return promptErr + } + if !another { + break + } + } + } + fmt.Println() + + // ── Step 6: Test & write ────────────────────────────────────────────────── + + step(6, 6, "Test connections and write config") allPassed := true for _, t := range targets { @@ -260,6 +295,7 @@ func runOnboard(_ *cobra.Command, _ []string) error { TruncateMode: truncateMode, PadMode: padMode, }, + ExtraMaps: extraMaps, } defaultCfgPath := config.ResolveFile(cfgFile) @@ -467,6 +503,103 @@ func mustParseInt(s string, fallback int) int { return n } +// pendingExtraMap pairs the map key with its collected config. +type pendingExtraMap struct { + key string + cfg config.ExtraMapConfig +} + +// collectExtraMap interactively collects one extra_map entry. +// Global adapter values are shown as defaults; the user may override any field. +func collectExtraMap( + in *bufio.Reader, + globalSourceDim, globalTargetDim int, + globalAdapterType, globalTruncateMode, globalPadMode string, + targetNames []string, +) (pendingExtraMap, error) { + key, err := promptString(in, "Map key (used in URL path, e.g. \"512\"): ", "") + if err != nil || key == "" { + return pendingExtraMap{}, fmt.Errorf("map key is required") + } + + mc := config.ExtraMapConfig{} + + // Target dimension (required — always shown) + targetDimRaw, err := promptString(in, + fmt.Sprintf("Target dimension [%d]: ", globalTargetDim), fmt.Sprintf("%d", globalTargetDim)) + if err != nil { + return pendingExtraMap{}, err + } + mc.TargetDim = mustParseInt(targetDimRaw, globalTargetDim) + + // Forward target override + if len(targetNames) > 0 { + fmt.Println("Available forward targets: " + strings.Join(targetNames, ", ")) + ftRaw, err := promptString(in, "Forward target override (leave empty to use global default): ", "") + if err != nil { + return pendingExtraMap{}, err + } + mc.ForwardTarget = strings.TrimSpace(ftRaw) + } + + // Adapter type override + adapterTypeRaw, err := promptString(in, + fmt.Sprintf("Adapter type override (truncate/random/projection) [%s]: ", globalAdapterType), globalAdapterType) + if err != nil { + return pendingExtraMap{}, err + } + if adapterTypeRaw != globalAdapterType { + mc.Type = adapterTypeRaw + } + effectiveType := coalesce(mc.Type, globalAdapterType) + + // Source dim override + sourceDimRaw, err := promptString(in, + fmt.Sprintf("Source dimension override (leave empty to use global %d): ", globalSourceDim), "") + if err != nil { + return pendingExtraMap{}, err + } + if sourceDimRaw != "" { + mc.SourceDim = mustParseInt(sourceDimRaw, globalSourceDim) + } + + // Truncate/pad mode overrides — only for truncate type + if effectiveType == "truncate" { + tmRaw, err := promptString(in, + fmt.Sprintf("Truncate mode override (from_end/from_start) [%s]: ", globalTruncateMode), globalTruncateMode) + if err != nil { + return pendingExtraMap{}, err + } + if tmRaw != globalTruncateMode { + mc.TruncateMode = tmRaw + } + + pmRaw, err := promptString(in, + fmt.Sprintf("Pad mode override (at_end/at_start) [%s]: ", globalPadMode), globalPadMode) + if err != nil { + return pendingExtraMap{}, err + } + if pmRaw != globalPadMode { + mc.PadMode = pmRaw + } + } + + // Seed — only for random type + if effectiveType == "random" { + seedRaw, err := promptString(in, "Seed (0 = time-based): ", "0") + if err != nil { + return pendingExtraMap{}, err + } + var seed int64 + if _, err := fmt.Sscanf(seedRaw, "%d", &seed); err != nil { + seed = 0 + } + mc.Seed = seed + } + + return pendingExtraMap{key: key, cfg: mc}, nil +} + func writeFullConfig(path string, cfg config.Config) error { // If file already exists, preserve any targets not touched by onboard // by using SaveTarget for each new target; otherwise write the whole file.