11package index
22
33import (
4+ "context"
45 "errors"
56 "fmt"
7+ "strings"
68
79 "github.com/MakeNowJust/heredoc"
810 "github.com/pinecone-io/cli/internal/pkg/utils/docslinks"
@@ -12,6 +14,7 @@ import (
1214 indexpresenters "github.com/pinecone-io/cli/internal/pkg/utils/index/presenters"
1315 "github.com/pinecone-io/cli/internal/pkg/utils/interactive"
1416 "github.com/pinecone-io/cli/internal/pkg/utils/log"
17+ "github.com/pinecone-io/cli/internal/pkg/utils/models"
1518 "github.com/pinecone-io/cli/internal/pkg/utils/msg"
1619 "github.com/pinecone-io/cli/internal/pkg/utils/pcio"
1720 "github.com/pinecone-io/cli/internal/pkg/utils/sdk"
@@ -24,6 +27,7 @@ import (
2427type createIndexOptions struct {
2528 CreateOptions index.CreateOptions
2629 json bool
30+ interactive bool
2731}
2832
2933func NewCreateIndexCmd () * cobra.Command {
@@ -43,10 +47,14 @@ func NewCreateIndexCmd() *cobra.Command {
4347
4448 The CLI will try to automatically infer missing settings from those provided.
4549
50+ Use the %s flag to enable interactive mode, which will guide you through configuring
51+ the index settings step by step.
52+
4653 For detailed documentation, see:
4754 %s
4855 ` , style .Code ("pc index create" ),
4956 style .Emphasis ("--model" ),
57+ style .Code ("--interactive" ),
5058 style .URL (docslinks .DocsIndexCreate )),
5159 Example : heredoc .Doc (`
5260 # create default index (serverless)
@@ -67,6 +75,9 @@ func NewCreateIndexCmd() *cobra.Command {
6775 # create a serverless index with the default sparse model
6876 $ pc index create my-index --model sparse --cloud aws --region us-east-1
6977
78+ # create an index using interactive mode
79+ $ pc index create my-index --interactive
80+
7081 ` ),
7182 Args : index .ValidateIndexNameArgs ,
7283 SilenceUsage : true ,
@@ -110,13 +121,265 @@ func NewCreateIndexCmd() *cobra.Command {
110121 cmd .Flags ().StringToStringVar (& options .CreateOptions .Tags .Value , "tags" , map [string ]string {}, "Custom user tags to add to an index" )
111122
112123 cmd .Flags ().BoolVar (& options .json , "json" , false , "Output as JSON" )
124+ cmd .Flags ().BoolVarP (& options .interactive , "interactive" , "i" , false , "Enable interactive mode to configure index settings step by step" )
113125
114126 return cmd
115127}
116128
129+ func collectInteractiveConfiguration (ctx context.Context , options index.CreateOptions ) (index.CreateOptions , bool ) {
130+ pcio .Println (style .InfoMsg ("Interactive mode: Let's configure your index step by step.\n " ))
131+ pcio .Println (style .Hint ("Press Esc or Ctrl+C at any time to exit interactive mode.\n " ))
132+
133+ // Variables for model data (will be populated when needed)
134+ var availableModels []models.ModelInfo
135+ var modelsErr error
136+
137+ // Index type selection - determine default based on existing flags
138+ var defaultChoice string
139+ if options .Serverless .Value {
140+ defaultChoice = "Serverless"
141+ } else if options .Pod .Value {
142+ defaultChoice = "Pod"
143+ } else {
144+ defaultChoice = "Serverless"
145+ }
146+
147+ choice , exit := interactive .GetChoice (
148+ "Select index type" ,
149+ []string {
150+ "Serverless" ,
151+ "Pod" ,
152+ },
153+ defaultChoice )
154+
155+ if exit {
156+ return options , true
157+ }
158+
159+ switch choice {
160+ case "Serverless" :
161+ options .Serverless .Value = true
162+ options .Pod .Value = false
163+ case "Pod" :
164+ options .Serverless .Value = false
165+ options .Pod .Value = true
166+ }
167+
168+ // Serverless configuration
169+ if options .Serverless .Value {
170+ // Fetch available models for serverless
171+ availableModels , modelsErr = models .GetModels (ctx , true )
172+ if modelsErr != nil {
173+ pcio .Println (style .WarnMsg ("Warning: Could not fetch available models!" ))
174+ }
175+
176+ // Model selection
177+ if modelsErr != nil {
178+ options .Model .Value = "llama-text-embed-v2"
179+ } else {
180+ // Create model choices
181+ modelChoices := make ([]string , 0 , len (availableModels )+ 1 )
182+ modelChoices = append (modelChoices , "None (custom vectors)" )
183+
184+ for _ , model := range availableModels {
185+ modelChoices = append (modelChoices , model .Model )
186+ }
187+
188+ // Determine default model choice
189+ var defaultModelChoice string
190+ if options .Model .Value != "" {
191+ defaultModelChoice = options .Model .Value
192+ } else {
193+ defaultModelChoice = "llama-text-embed-v2" // Default dense model
194+ }
195+
196+ modelChoice , exit := interactive .GetChoice ("Select inference model" , modelChoices , defaultModelChoice )
197+ if exit {
198+ return options , true
199+ }
200+
201+ if modelChoice == "None (custom vectors)" {
202+ options .Model .Value = ""
203+ } else {
204+ options .Model .Value = modelChoice
205+ }
206+ }
207+
208+ // Cloud and region
209+ cloud , exit := interactive .GetInput ("Cloud provider (aws, gcp, azure)" , options .Cloud .Value )
210+ if exit {
211+ return options , true
212+ }
213+ options .Cloud .Value = cloud
214+
215+ region , exit := interactive .GetInput ("Region (e.g., us-east-1)" , options .Region .Value )
216+ if exit {
217+ return options , true
218+ }
219+ options .Region .Value = region
220+
221+ // Vector type (only for serverless without model)
222+ if options .Model .Value == "" {
223+ vectorType , exit := interactive .GetChoice ("Vector type" , []string {"dense" , "sparse" }, options .VectorType .Value )
224+ if exit {
225+ return options , true
226+ }
227+ options .VectorType .Value = vectorType
228+ }
229+ }
230+
231+ // Environment (for pod)
232+ if options .Pod .Value {
233+ environment , exit := interactive .GetInput ("Environment" , options .Environment .Value )
234+ if exit {
235+ return options , true
236+ }
237+ options .Environment .Value = environment
238+
239+ podType , exit := interactive .GetInput ("Pod type" , options .PodType .Value )
240+ if exit {
241+ return options , true
242+ }
243+ options .PodType .Value = podType
244+
245+ shards , exit := interactive .GetIntInput ("Number of shards" , int (options .Shards .Value ))
246+ if exit {
247+ return options , true
248+ }
249+ options .Shards .Value = int32 (shards )
250+
251+ replicas , exit := interactive .GetIntInput ("Number of replicas" , int (options .Replicas .Value ))
252+ if exit {
253+ return options , true
254+ }
255+ options .Replicas .Value = int32 (replicas )
256+ }
257+
258+ // Common settings
259+
260+ // Handle dimension based on vector type
261+ // Sparse models always use dimension 0, dense models may support multiple dimensions
262+ isSparse := false
263+ if options .Model .Value != "" {
264+ // Check if the selected model is sparse
265+ if modelsErr == nil {
266+ for _ , model := range availableModels {
267+ if model .Model == options .Model .Value && model .VectorType != nil && * model .VectorType == "sparse" {
268+ isSparse = true
269+ break
270+ }
271+ }
272+ }
273+ } else {
274+ // For custom vectors, check the vector type
275+ isSparse = options .VectorType .Value == "sparse"
276+ }
277+
278+ if isSparse {
279+ // Sparse vectors always use dimension 0
280+ options .Dimension .Value = 0
281+ } else {
282+ // Ask for dimension for dense models and custom vectors
283+ dimension , exit := interactive .GetIntInput ("Dimension (0 for auto)" , int (options .Dimension .Value ))
284+ if exit {
285+ return options , true
286+ }
287+ options .Dimension .Value = int32 (dimension )
288+ }
289+
290+ // Only ask for metric if not sparse (sparse always uses dotproduct)
291+ // isSparse is already determined above
292+
293+ if ! isSparse {
294+ metric , exit := interactive .GetChoice ("Metric" , []string {"cosine" , "euclidean" , "dotproduct" }, options .Metric .Value )
295+ if exit {
296+ return options , true
297+ }
298+ options .Metric .Value = metric
299+ } else {
300+ // Sparse vectors always use dotproduct
301+ options .Metric .Value = "dotproduct"
302+ }
303+
304+ // Set default deletion protection to disabled if not already set
305+ defaultDeletionProtection := options .DeletionProtection .Value
306+ if defaultDeletionProtection == "" {
307+ defaultDeletionProtection = "disabled"
308+ }
309+
310+ deletionProtection , exit := interactive .GetChoice ("Deletion protection" , []string {"enabled" , "disabled" }, defaultDeletionProtection )
311+ if exit {
312+ return options , true
313+ }
314+ options .DeletionProtection .Value = deletionProtection
315+
316+ // Tags
317+ useTags := interactive .GetConfirmation ("Add custom tags?" )
318+
319+ if useTags {
320+ // Initialize tags map if it doesn't exist
321+ if options .Tags .Value == nil {
322+ options .Tags .Value = make (map [string ]string )
323+ }
324+
325+ pcio .Println ("Enter tags in key=value format. Press Enter with empty input to finish." )
326+
327+ for {
328+ // Show current tags
329+ if len (options .Tags .Value ) > 0 {
330+ for k , v := range options .Tags .Value {
331+ pcio .Printf (" %s=%s\n " , style .Emphasis (k ), style .ResourceName (v ))
332+ }
333+ pcio .Println ()
334+ }
335+
336+ tagInput , exit := interactive .GetInput ("Tag (key=value)" , "" )
337+ if exit {
338+ return options , true
339+ }
340+
341+ // Empty input means done adding tags
342+ if strings .TrimSpace (tagInput ) == "" {
343+ break
344+ }
345+
346+ // Parse key=value format
347+ parts := strings .SplitN (tagInput , "=" , 2 )
348+ if len (parts ) != 2 {
349+ pcio .Println (style .FailMsg ("Invalid format. Please use key=value format." ))
350+ continue
351+ }
352+
353+ key := strings .TrimSpace (parts [0 ])
354+ value := strings .TrimSpace (parts [1 ])
355+
356+ if key == "" || value == "" {
357+ pcio .Println (style .FailMsg ("Both key and value must be non-empty." ))
358+ continue
359+ }
360+
361+ // Add the tag
362+ options .Tags .Value [key ] = value
363+ }
364+ }
365+
366+ pcio .Println (style .SuccessMsg ("\n Configuration complete!" ))
367+ return options , false
368+ }
369+
117370func runCreateIndexCmd (options createIndexOptions , cmd * cobra.Command , args []string ) {
118371 ctx := cmd .Context ()
119372
373+ // If interactive mode is enabled, collect configuration interactively
374+ if options .interactive {
375+ var exit bool
376+ options .CreateOptions , exit = collectInteractiveConfiguration (ctx , options .CreateOptions )
377+ if exit {
378+ pcio .Println (style .InfoMsg ("Interactive mode cancelled." ))
379+ return
380+ }
381+ }
382+
120383 // validationErrors := index.ValidateCreateOptions(options.CreateOptions)
121384 // if len(validationErrors) > 0 {
122385 // msg.FailMsgMultiLine(validationErrors...)
0 commit comments