chore: Modularize the questions and prompts
This commit is contained in:
parent
a1c7f9348d
commit
8cec1bfd1e
2 changed files with 616 additions and 0 deletions
373
pkg/setup/prompts.go
Normal file
373
pkg/setup/prompts.go
Normal file
|
|
@ -0,0 +1,373 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StepDef describes a single prompt step in the setup wizard.
|
||||||
|
type StepDef struct {
|
||||||
|
Kind string // "text", "select", "yesno"
|
||||||
|
Prompt string
|
||||||
|
Info string
|
||||||
|
Options []string // for select/yesno
|
||||||
|
ID string // logical identifier for mapping answers into config
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildDefs creates the per-step tui definitions based on the current config.
|
||||||
|
// It returns a modular list of steps for workspace, provider, model, and channel setup.
|
||||||
|
func (s *Setup) BuildDefs() []StepDef {
|
||||||
|
defs := []StepDef{}
|
||||||
|
|
||||||
|
// Step 1: Workspace setup
|
||||||
|
defs = append(defs, StepDef{ID: "workspace", Kind: "text", Prompt: "1. Workspace path", Info: "Directory for agent workspace files"})
|
||||||
|
defs = append(defs, StepDef{ID: "restrict_workspace", Kind: "select", Prompt: "1b. Restrict to workspace?", Options: []string{"yes", "no"}})
|
||||||
|
|
||||||
|
// Step 2: Provider setup (ordered by popularity)
|
||||||
|
defs = append(defs, buildProviderSelectStep(s.Cfg)...)
|
||||||
|
|
||||||
|
// Step 3: Model selection (popular suggestions + custom option)
|
||||||
|
defs = append(defs, buildModelSelectStep(s.Cfg)...)
|
||||||
|
|
||||||
|
// Step 4: Channel setup
|
||||||
|
defs = append(defs, buildChannelSelectStep(s.Cfg)...)
|
||||||
|
|
||||||
|
// Final confirmation step
|
||||||
|
defs = append(defs, StepDef{ID: "confirm", Kind: "yesno", Prompt: "6. Confirm and save configuration?", Options: []string{"yes", "no"}})
|
||||||
|
|
||||||
|
return defs
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildProviderSelectStep returns provider selection step and credential prompts for missing required fields.
|
||||||
|
func buildProviderSelectStep(cfg *config.Config) []StepDef {
|
||||||
|
var steps []StepDef
|
||||||
|
|
||||||
|
provInfo := config.GetProvidersInfo(cfg)
|
||||||
|
ordered := config.GetOrderedProviderNames()
|
||||||
|
|
||||||
|
provOptions := []string{}
|
||||||
|
added := map[string]struct{}{}
|
||||||
|
|
||||||
|
// Merge ordered list with available provider info
|
||||||
|
for _, name := range ordered {
|
||||||
|
if _, ok := provInfoLookup(provInfo, name); ok {
|
||||||
|
provOptions = append(provOptions, name)
|
||||||
|
added[name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add any remaining providers not in priority list
|
||||||
|
for _, p := range provInfo {
|
||||||
|
if _, ok := added[p.Name]; !ok {
|
||||||
|
provOptions = append(provOptions, p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider selection
|
||||||
|
steps = append(steps, StepDef{ID: "provider", Kind: "select", Prompt: "2. Choose provider", Options: provOptions})
|
||||||
|
|
||||||
|
// Determine selected provider (from config or first in list)
|
||||||
|
selProvider := cfg.Agents.Defaults.Provider
|
||||||
|
if selProvider == "" && len(provOptions) > 0 {
|
||||||
|
selProvider = provOptions[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add credential prompts for missing required fields
|
||||||
|
if selProvider != "" {
|
||||||
|
provInfoItem, ok := provInfoLookup(provInfo, selProvider)
|
||||||
|
if ok {
|
||||||
|
for _, cred := range provInfoItem.RequiredCredentials {
|
||||||
|
if !ProviderCredentialPresent(cfg, selProvider, cred) {
|
||||||
|
steps = append(steps, StepDef{
|
||||||
|
ID: "provider_" + cred,
|
||||||
|
Kind: "text",
|
||||||
|
Prompt: fmt.Sprintf("2b. %s for %s", cred, selProvider),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildModelSelectStep returns model selection step and custom model input.
|
||||||
|
func buildModelSelectStep(cfg *config.Config) []StepDef {
|
||||||
|
var steps []StepDef
|
||||||
|
|
||||||
|
selProvider := cfg.Agents.Defaults.Provider
|
||||||
|
modelSuggestions := config.GetPopularModels(selProvider)
|
||||||
|
|
||||||
|
modelOptions := make([]string, len(modelSuggestions)+1)
|
||||||
|
copy(modelOptions, modelSuggestions)
|
||||||
|
modelOptions[len(modelSuggestions)] = "custom"
|
||||||
|
|
||||||
|
steps = append(steps, StepDef{
|
||||||
|
ID: "model_select",
|
||||||
|
Kind: "select",
|
||||||
|
Prompt: "3. Choose model",
|
||||||
|
Options: modelOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
steps = append(steps, StepDef{
|
||||||
|
ID: "default_model",
|
||||||
|
Kind: "text",
|
||||||
|
Prompt: "3b. Enter custom model name",
|
||||||
|
})
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildChannelSelectStep returns channel selection step and credential prompts.
|
||||||
|
func buildChannelSelectStep(cfg *config.Config) []StepDef {
|
||||||
|
var steps []StepDef
|
||||||
|
|
||||||
|
channels := config.GetAllChannelNames()
|
||||||
|
|
||||||
|
steps = append(steps, StepDef{
|
||||||
|
ID: "channel_select",
|
||||||
|
Kind: "select",
|
||||||
|
Prompt: "4. Choose channel",
|
||||||
|
Options: channels,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add placeholder for channel credentials (filled dynamically based on selection)
|
||||||
|
steps = append(steps, StepDef{
|
||||||
|
ID: "channel_token",
|
||||||
|
Kind: "text",
|
||||||
|
Prompt: "4b. Channel credential/token",
|
||||||
|
})
|
||||||
|
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
|
||||||
|
// provInfoLookup returns (ProviderInfo, true) if provider name exists in list.
|
||||||
|
func provInfoLookup(list []config.ProviderInfo, name string) (config.ProviderInfo, bool) {
|
||||||
|
for _, p := range list {
|
||||||
|
if p.Name == name {
|
||||||
|
return p, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config.ProviderInfo{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderCredentialPresent checks whether a given provider credential is present
|
||||||
|
// in the legacy ProvidersConfig or within any ModelConfig for that provider.
|
||||||
|
func ProviderCredentialPresent(c *config.Config, provider, cred string) bool {
|
||||||
|
if c == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
p := strings.ToLower(provider)
|
||||||
|
switch p {
|
||||||
|
case "anthropic":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Anthropic.APIKey != ""
|
||||||
|
}
|
||||||
|
if cred == "api_base" {
|
||||||
|
return c.Providers.Anthropic.APIBase != ""
|
||||||
|
}
|
||||||
|
case "openai":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.OpenAI.APIKey != ""
|
||||||
|
}
|
||||||
|
if cred == "api_base" {
|
||||||
|
return c.Providers.OpenAI.APIBase != ""
|
||||||
|
}
|
||||||
|
case "openrouter":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.OpenRouter.APIKey != ""
|
||||||
|
}
|
||||||
|
if cred == "api_base" {
|
||||||
|
return c.Providers.OpenRouter.APIBase != ""
|
||||||
|
}
|
||||||
|
case "groq":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Groq.APIKey != ""
|
||||||
|
}
|
||||||
|
case "zhipu":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Zhipu.APIKey != ""
|
||||||
|
}
|
||||||
|
case "vllm":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.VLLM.APIKey != ""
|
||||||
|
}
|
||||||
|
case "gemini":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Gemini.APIKey != ""
|
||||||
|
}
|
||||||
|
case "nvidia":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Nvidia.APIKey != ""
|
||||||
|
}
|
||||||
|
case "ollama":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Ollama.APIKey != ""
|
||||||
|
}
|
||||||
|
case "moonshot":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Moonshot.APIKey != ""
|
||||||
|
}
|
||||||
|
case "shengsuanyun":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.ShengSuanYun.APIKey != ""
|
||||||
|
}
|
||||||
|
case "deepseek":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.DeepSeek.APIKey != ""
|
||||||
|
}
|
||||||
|
case "cerebras":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Cerebras.APIKey != ""
|
||||||
|
}
|
||||||
|
case "volcengine":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.VolcEngine.APIKey != ""
|
||||||
|
}
|
||||||
|
case "github_copilot":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.GitHubCopilot.APIKey != ""
|
||||||
|
}
|
||||||
|
case "antigravity":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Antigravity.APIKey != ""
|
||||||
|
}
|
||||||
|
case "qwen":
|
||||||
|
if cred == "api_key" {
|
||||||
|
return c.Providers.Qwen.APIKey != ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check model_list entries for provider credentials
|
||||||
|
for _, m := range c.ModelList {
|
||||||
|
pfx := config.ParseProtocol(m.Model)
|
||||||
|
if pfx == "" {
|
||||||
|
pfx = "openai"
|
||||||
|
}
|
||||||
|
if pfx == p {
|
||||||
|
if cred == "api_key" && m.APIKey != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if cred == "api_base" && m.APIBase != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProviderCredential sets a credential value for the named provider into the
|
||||||
|
// appropriate place in the config (legacy ProvidersConfig or model_list fallback).
|
||||||
|
func SetProviderCredential(c *config.Config, provider, cred, value string) {
|
||||||
|
if c == nil || provider == "" || cred == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := strings.ToLower(provider)
|
||||||
|
switch p {
|
||||||
|
case "anthropic":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Anthropic.APIKey = value
|
||||||
|
} else if cred == "api_base" {
|
||||||
|
c.Providers.Anthropic.APIBase = value
|
||||||
|
}
|
||||||
|
case "openai":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.OpenAI.APIKey = value
|
||||||
|
} else if cred == "api_base" {
|
||||||
|
c.Providers.OpenAI.APIBase = value
|
||||||
|
}
|
||||||
|
case "openrouter":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.OpenRouter.APIKey = value
|
||||||
|
} else if cred == "api_base" {
|
||||||
|
c.Providers.OpenRouter.APIBase = value
|
||||||
|
}
|
||||||
|
case "groq":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Groq.APIKey = value
|
||||||
|
}
|
||||||
|
case "zhipu":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Zhipu.APIKey = value
|
||||||
|
}
|
||||||
|
case "vllm":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.VLLM.APIKey = value
|
||||||
|
}
|
||||||
|
case "gemini":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Gemini.APIKey = value
|
||||||
|
}
|
||||||
|
case "nvidia":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Nvidia.APIKey = value
|
||||||
|
}
|
||||||
|
case "ollama":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Ollama.APIKey = value
|
||||||
|
}
|
||||||
|
case "moonshot":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Moonshot.APIKey = value
|
||||||
|
}
|
||||||
|
case "shengsuanyun":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.ShengSuanYun.APIKey = value
|
||||||
|
}
|
||||||
|
case "deepseek":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.DeepSeek.APIKey = value
|
||||||
|
}
|
||||||
|
case "cerebras":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Cerebras.APIKey = value
|
||||||
|
}
|
||||||
|
case "volcengine":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.VolcEngine.APIKey = value
|
||||||
|
}
|
||||||
|
case "github_copilot":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.GitHubCopilot.APIKey = value
|
||||||
|
}
|
||||||
|
case "antigravity":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Antigravity.APIKey = value
|
||||||
|
}
|
||||||
|
case "qwen":
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.Providers.Qwen.APIKey = value
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// If provider not in legacy ProvidersConfig, try to set on a matching ModelConfig
|
||||||
|
for i := range c.ModelList {
|
||||||
|
pfx := config.ParseProtocol(c.ModelList[i].Model)
|
||||||
|
if pfx == "" {
|
||||||
|
pfx = "openai"
|
||||||
|
}
|
||||||
|
if pfx == p {
|
||||||
|
if cred == "api_key" {
|
||||||
|
c.ModelList[i].APIKey = value
|
||||||
|
} else if cred == "api_base" {
|
||||||
|
c.ModelList[i].APIBase = value
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildSummary returns a human-readable summary of the current configuration.
|
||||||
|
func BuildSummary(cfg *config.Config) []string {
|
||||||
|
summary := []string{}
|
||||||
|
summary = append(summary, "Configuration summary:")
|
||||||
|
summary = append(summary, fmt.Sprintf("Workspace: %s", cfg.Agents.Defaults.Workspace))
|
||||||
|
summary = append(summary, fmt.Sprintf("Restrict to workspace: %v", cfg.Agents.Defaults.RestrictToWorkspace))
|
||||||
|
summary = append(summary, fmt.Sprintf("Provider: %s", cfg.Agents.Defaults.Provider))
|
||||||
|
if cfg.Agents.Defaults.Model != "" {
|
||||||
|
summary = append(summary, fmt.Sprintf("Model: %s", cfg.Agents.Defaults.Model))
|
||||||
|
}
|
||||||
|
return summary
|
||||||
|
}
|
||||||
243
pkg/setup/questions.go
Normal file
243
pkg/setup/questions.go
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QuestionType defines the type of response expected for a question.
|
||||||
|
type QuestionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
QuestionTypeText QuestionType = "text"
|
||||||
|
QuestionTypeSelect QuestionType = "select"
|
||||||
|
QuestionTypeYesNo QuestionType = "yesno"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Question represents a single question in the setup wizard.
|
||||||
|
type Question struct {
|
||||||
|
ID string // unique identifier for mapping answer to config
|
||||||
|
Type QuestionType // response type: text, select, yesno
|
||||||
|
Prompt string // question text shown to user
|
||||||
|
Info string // optional helper text
|
||||||
|
Options []string // options for select/yesno types
|
||||||
|
DefaultValue string // default value to prefill
|
||||||
|
DependsOn string // question ID this depends on (optional)
|
||||||
|
DependsValue string // value that must be matched (optional)
|
||||||
|
ConfigPath string // dot-notation path to config field (e.g. "Agents.Defaults.Workspace")
|
||||||
|
Transformer string // optional: "lowercase", "bool_yesno", "channel_enable"
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuestionGroup represents a group of related questions.
|
||||||
|
type QuestionGroup struct {
|
||||||
|
Name string
|
||||||
|
Questions []Question
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllQuestions returns the complete list of all possible questions in the setup wizard.
|
||||||
|
// This is the declarative data structure - questions are defined here, not built procedurally.
|
||||||
|
var AllQuestions = []QuestionGroup{
|
||||||
|
{
|
||||||
|
Name: "Workspace",
|
||||||
|
Questions: []Question{
|
||||||
|
{
|
||||||
|
ID: "workspace",
|
||||||
|
Type: QuestionTypeText,
|
||||||
|
Prompt: "1. Workspace path",
|
||||||
|
Info: "Directory for agent workspace files",
|
||||||
|
ConfigPath: "Agents.Defaults.Workspace",
|
||||||
|
DefaultValue: "~/.picoclaw/workspace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "restrict_workspace",
|
||||||
|
Type: QuestionTypeYesNo,
|
||||||
|
Prompt: "1b. Restrict to workspace?",
|
||||||
|
ConfigPath: "Agents.Defaults.RestrictToWorkspace",
|
||||||
|
Transformer: "bool_yesno",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Provider",
|
||||||
|
Questions: []Question{
|
||||||
|
{
|
||||||
|
ID: "provider",
|
||||||
|
Type: QuestionTypeSelect,
|
||||||
|
Prompt: "2. Choose provider",
|
||||||
|
ConfigPath: "Agents.Defaults.Provider",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "provider_api_key",
|
||||||
|
Type: QuestionTypeText,
|
||||||
|
Prompt: "2b. API key for provider",
|
||||||
|
ConfigPath: "Providers.{provider}.APIKey",
|
||||||
|
DependsOn: "provider",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "provider_api_base",
|
||||||
|
Type: QuestionTypeText,
|
||||||
|
Prompt: "2c. API base URL (optional)",
|
||||||
|
ConfigPath: "Providers.{provider}.APIBase",
|
||||||
|
DependsOn: "provider",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Model",
|
||||||
|
Questions: []Question{
|
||||||
|
{
|
||||||
|
ID: "model_select",
|
||||||
|
Type: QuestionTypeSelect,
|
||||||
|
Prompt: "3. Choose model",
|
||||||
|
ConfigPath: "Agents.Defaults.Model",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "custom_model",
|
||||||
|
Type: QuestionTypeText,
|
||||||
|
Prompt: "3b. Enter custom model name",
|
||||||
|
ConfigPath: "Agents.Defaults.Model",
|
||||||
|
DependsOn: "model_select",
|
||||||
|
DependsValue: "custom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Channel",
|
||||||
|
Questions: []Question{
|
||||||
|
{
|
||||||
|
ID: "channel_select",
|
||||||
|
Type: QuestionTypeSelect,
|
||||||
|
Prompt: "4. Choose channel",
|
||||||
|
ConfigPath: "channel_enabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "channel_token",
|
||||||
|
Type: QuestionTypeText,
|
||||||
|
Prompt: "4b. Channel token/credential",
|
||||||
|
ConfigPath: "channel.{channel}.token",
|
||||||
|
DependsOn: "channel_select",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Confirmation",
|
||||||
|
Questions: []Question{
|
||||||
|
{
|
||||||
|
ID: "confirm",
|
||||||
|
Type: QuestionTypeYesNo,
|
||||||
|
Prompt: "5. Confirm and save configuration?",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuestionRegistry holds resolved questions for a specific config state.
|
||||||
|
type QuestionRegistry struct {
|
||||||
|
Questions []Question
|
||||||
|
Defaults map[string]string // questionID -> default value
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildQuestionRegistry creates the question registry based on current config.
|
||||||
|
// It resolves dynamic options (provider list, channel list, model suggestions) from config.
|
||||||
|
func BuildQuestionRegistry(cfg *config.Config) QuestionRegistry {
|
||||||
|
registry := QuestionRegistry{
|
||||||
|
Questions: []Question{},
|
||||||
|
Defaults: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect provider options
|
||||||
|
provInfo := config.GetProvidersInfo(cfg)
|
||||||
|
ordered := config.GetOrderedProviderNames()
|
||||||
|
provOptions := []string{}
|
||||||
|
added := map[string]struct{}{}
|
||||||
|
for _, name := range ordered {
|
||||||
|
if _, ok := provInfoLookup(provInfo, name); ok {
|
||||||
|
provOptions = append(provOptions, name)
|
||||||
|
added[name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, p := range provInfo {
|
||||||
|
if _, ok := added[p.Name]; !ok {
|
||||||
|
provOptions = append(provOptions, p.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect channel options
|
||||||
|
channelOptions := config.GetAllChannelNames()
|
||||||
|
|
||||||
|
// Collect model options based on selected provider
|
||||||
|
selProvider := cfg.Agents.Defaults.Provider
|
||||||
|
modelSuggestions := config.GetPopularModels(selProvider)
|
||||||
|
modelOptions := make([]string, len(modelSuggestions)+1)
|
||||||
|
copy(modelOptions, modelSuggestions)
|
||||||
|
modelOptions[len(modelSuggestions)] = "custom"
|
||||||
|
|
||||||
|
// Build flat list of questions with resolved options
|
||||||
|
for _, group := range AllQuestions {
|
||||||
|
for _, q := range group.Questions {
|
||||||
|
question := q
|
||||||
|
|
||||||
|
// Set options based on question type
|
||||||
|
if question.Type == QuestionTypeSelect {
|
||||||
|
switch question.ID {
|
||||||
|
case "provider":
|
||||||
|
question.Options = provOptions
|
||||||
|
case "channel_select":
|
||||||
|
question.Options = channelOptions
|
||||||
|
case "model_select":
|
||||||
|
question.Options = modelOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults from config
|
||||||
|
if question.DefaultValue != "" {
|
||||||
|
registry.Defaults[question.ID] = question.DefaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.Questions = append(registry.Questions, question)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return registry
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQuestionByID returns a question by its ID.
|
||||||
|
func (r *QuestionRegistry) GetQuestionByID(id string) *Question {
|
||||||
|
for i := range r.Questions {
|
||||||
|
if r.Questions[i].ID == id {
|
||||||
|
return &r.Questions[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDependentQuestions returns questions that depend on a specific question.
|
||||||
|
func (r *QuestionRegistry) GetDependentQuestions(dependsOn string) []Question {
|
||||||
|
var deps []Question
|
||||||
|
for _, q := range r.Questions {
|
||||||
|
if q.DependsOn == dependsOn {
|
||||||
|
deps = append(deps, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deps
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldShowQuestion checks if a question should be shown based on its dependencies.
|
||||||
|
func (r *QuestionRegistry) ShouldShowQuestion(questionID string, answers map[string]string) bool {
|
||||||
|
q := r.GetQuestionByID(questionID)
|
||||||
|
if q == nil || q.DependsOn == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
depValue, exists := answers[q.DependsOn]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If DependsValue is set, check for exact match
|
||||||
|
if q.DependsValue != "" {
|
||||||
|
return depValue == q.DependsValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise show if dependency has any value
|
||||||
|
return depValue != ""
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue