diff options
| author | Grail Finder <wohilas@gmail.com> | 2026-02-10 11:05:09 +0300 |
|---|---|---|
| committer | Grail Finder <wohilas@gmail.com> | 2026-02-10 11:05:09 +0300 |
| commit | 875de679cf4732bbd70361d826d65d9d8b190add (patch) | |
| tree | e4ad90c2bdde747ad057e4e7d6e850c3173018fd /popups.go | |
| parent | 3b542421e35180db9b9f972ee973befc42f1a46f (diff) | |
| parent | 37b98ad36cd7e63d96a190017b78fe35143a2e6a (diff) | |
Merge branch 'feat/char-secrets'
Diffstat (limited to 'popups.go')
| -rw-r--r-- | popups.go | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/popups.go b/popups.go new file mode 100644 index 0000000..9a1a278 --- /dev/null +++ b/popups.go @@ -0,0 +1,314 @@ +package main + +import ( + "slices" + "strings" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +// showModelSelectionPopup creates a modal popup to select a model +func showModelSelectionPopup() { + // Helper function to get model list for a given API + getModelListForAPI := func(api string) []string { + if strings.Contains(api, "api.deepseek.com/") { + return []string{"deepseek-chat", "deepseek-reasoner"} + } else if strings.Contains(api, "openrouter.ai") { + return ORFreeModels + } + // Assume local llama.cpp + updateModelLists() + return LocalModels + } + // Get the current model list based on the API + modelList := getModelListForAPI(cfg.CurrentAPI) + // Check for empty options list + if len(modelList) == 0 { + logger.Warn("empty model list for", "api", cfg.CurrentAPI, "localModelsLen", len(LocalModels), "orModelsLen", len(ORFreeModels)) + var message string + switch { + case strings.Contains(cfg.CurrentAPI, "openrouter.ai"): + message = "No OpenRouter models available. Check token and connection." + case strings.Contains(cfg.CurrentAPI, "api.deepseek.com"): + message = "DeepSeek models should be available. Please report bug." + default: + message = "No llama.cpp models loaded. Ensure llama.cpp server is running with models." + } + if err := notifyUser("Empty list", message); err != nil { + logger.Error("failed to send notification", "error", err) + } + return + } + // Create a list primitive + modelListWidget := tview.NewList().ShowSecondaryText(false). + SetSelectedBackgroundColor(tcell.ColorGray) + modelListWidget.SetTitle("Select Model").SetBorder(true) + // Find the current model index to set as selected + currentModelIndex := -1 + for i, model := range modelList { + if model == chatBody.Model { + currentModelIndex = i + } + modelListWidget.AddItem(model, "", 0, nil) + } + // Set the current selection if found + if currentModelIndex != -1 { + modelListWidget.SetCurrentItem(currentModelIndex) + } + modelListWidget.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) { + // Update the model in both chatBody and config + chatBody.Model = mainText + cfg.CurrentModel = chatBody.Model + // Remove the popup page + pages.RemovePage("modelSelectionPopup") + // Update the status line to reflect the change + updateStatusLine() + }) + modelListWidget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEscape { + pages.RemovePage("modelSelectionPopup") + return nil + } + return event + }) + modal := func(p tview.Primitive, width, height int) tview.Primitive { + return tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(p, height, 1, true). + AddItem(nil, 0, 1, false), width, 1, true). + AddItem(nil, 0, 1, false) + } + // Add modal page and make it visible + pages.AddPage("modelSelectionPopup", modal(modelListWidget, 80, 20), true, true) + app.SetFocus(modelListWidget) +} + +// showAPILinkSelectionPopup creates a modal popup to select an API link +func showAPILinkSelectionPopup() { + // Prepare API links dropdown - ensure current API is in the list, avoid duplicates + apiLinks := make([]string, 0, len(cfg.ApiLinks)+1) + // Add current API first if it's not already in ApiLinks + foundCurrentAPI := false + for _, api := range cfg.ApiLinks { + if api == cfg.CurrentAPI { + foundCurrentAPI = true + } + apiLinks = append(apiLinks, api) + } + // If current API is not in the list, add it at the beginning + if !foundCurrentAPI { + apiLinks = make([]string, 0, len(cfg.ApiLinks)+1) + apiLinks = append(apiLinks, cfg.CurrentAPI) + apiLinks = append(apiLinks, cfg.ApiLinks...) + } + // Check for empty options list + if len(apiLinks) == 0 { + logger.Warn("no API links available for selection") + message := "No API links available. Please configure API links in your config file." + if err := notifyUser("Empty list", message); err != nil { + logger.Error("failed to send notification", "error", err) + } + return + } + // Create a list primitive + apiListWidget := tview.NewList().ShowSecondaryText(false). + SetSelectedBackgroundColor(tcell.ColorGray) + apiListWidget.SetTitle("Select API Link").SetBorder(true) + // Find the current API index to set as selected + currentAPIIndex := -1 + for i, api := range apiLinks { + if api == cfg.CurrentAPI { + currentAPIIndex = i + } + apiListWidget.AddItem(api, "", 0, nil) + } + // Set the current selection if found + if currentAPIIndex != -1 { + apiListWidget.SetCurrentItem(currentAPIIndex) + } + apiListWidget.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) { + // Update the API in config + cfg.CurrentAPI = mainText + // Update model list based on new API + // Helper function to get model list for a given API (same as in props_table.go) + getModelListForAPI := func(api string) []string { + if strings.Contains(api, "api.deepseek.com/") { + return []string{"deepseek-chat", "deepseek-reasoner"} + } else if strings.Contains(api, "openrouter.ai") { + return ORFreeModels + } + // Assume local llama.cpp + refreshLocalModelsIfEmpty() + localModelsMu.RLock() + defer localModelsMu.RUnlock() + return LocalModels + } + newModelList := getModelListForAPI(cfg.CurrentAPI) + // Ensure chatBody.Model is in the new list; if not, set to first available model + if len(newModelList) > 0 && !slices.Contains(newModelList, chatBody.Model) { + chatBody.Model = newModelList[0] + cfg.CurrentModel = chatBody.Model + } + // Remove the popup page + pages.RemovePage("apiLinkSelectionPopup") + // Update the parser and status line to reflect the change + choseChunkParser() + updateStatusLine() + }) + apiListWidget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEscape { + pages.RemovePage("apiLinkSelectionPopup") + return nil + } + return event + }) + modal := func(p tview.Primitive, width, height int) tview.Primitive { + return tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(p, height, 1, true). + AddItem(nil, 0, 1, false), width, 1, true). + AddItem(nil, 0, 1, false) + } + // Add modal page and make it visible + pages.AddPage("apiLinkSelectionPopup", modal(apiListWidget, 80, 20), true, true) + app.SetFocus(apiListWidget) +} + +// showUserRoleSelectionPopup creates a modal popup to select a user role +func showUserRoleSelectionPopup() { + // Get the list of available roles + roles := listRolesWithUser() + // Check for empty options list + if len(roles) == 0 { + logger.Warn("no roles available for selection") + message := "No roles available for selection." + if err := notifyUser("Empty list", message); err != nil { + logger.Error("failed to send notification", "error", err) + } + return + } + // Create a list primitive + roleListWidget := tview.NewList().ShowSecondaryText(false). + SetSelectedBackgroundColor(tcell.ColorGray) + roleListWidget.SetTitle("Select User Role").SetBorder(true) + // Find the current role index to set as selected + currentRole := cfg.UserRole + if cfg.WriteNextMsgAs != "" { + currentRole = cfg.WriteNextMsgAs + } + currentRoleIndex := -1 + for i, role := range roles { + if strings.EqualFold(role, currentRole) { + currentRoleIndex = i + } + roleListWidget.AddItem(role, "", 0, nil) + } + // Set the current selection if found + if currentRoleIndex != -1 { + roleListWidget.SetCurrentItem(currentRoleIndex) + } + roleListWidget.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) { + // Update the user role in config + cfg.WriteNextMsgAs = mainText + // role got switch, update textview with character specific context for user + filtered := filterMessagesForCharacter(chatBody.Messages, mainText) + textView.SetText(chatToText(filtered, cfg.ShowSys)) + // Remove the popup page + pages.RemovePage("userRoleSelectionPopup") + // Update the status line to reflect the change + updateStatusLine() + colorText() + }) + roleListWidget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEscape { + pages.RemovePage("userRoleSelectionPopup") + return nil + } + return event + }) + modal := func(p tview.Primitive, width, height int) tview.Primitive { + return tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(p, height, 1, true). + AddItem(nil, 0, 1, false), width, 1, true). + AddItem(nil, 0, 1, false) + } + // Add modal page and make it visible + pages.AddPage("userRoleSelectionPopup", modal(roleListWidget, 80, 20), true, true) + app.SetFocus(roleListWidget) +} + +// showBotRoleSelectionPopup creates a modal popup to select a bot role +func showBotRoleSelectionPopup() { + // Get the list of available roles + roles := listChatRoles() + if len(roles) == 0 { + logger.Warn("empty roles in chat") + } + if !strInSlice(cfg.AssistantRole, roles) { + roles = append(roles, cfg.AssistantRole) + } + // Check for empty options list + if len(roles) == 0 { + logger.Warn("no roles available for selection") + message := "No roles available for selection." + if err := notifyUser("Empty list", message); err != nil { + logger.Error("failed to send notification", "error", err) + } + return + } + // Create a list primitive + roleListWidget := tview.NewList().ShowSecondaryText(false). + SetSelectedBackgroundColor(tcell.ColorGray) + roleListWidget.SetTitle("Select Bot Role").SetBorder(true) + // Find the current role index to set as selected + currentRole := cfg.AssistantRole + if cfg.WriteNextMsgAsCompletionAgent != "" { + currentRole = cfg.WriteNextMsgAsCompletionAgent + } + currentRoleIndex := -1 + for i, role := range roles { + if strings.EqualFold(role, currentRole) { + currentRoleIndex = i + } + roleListWidget.AddItem(role, "", 0, nil) + } + // Set the current selection if found + if currentRoleIndex != -1 { + roleListWidget.SetCurrentItem(currentRoleIndex) + } + roleListWidget.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) { + // Update the bot role in config + cfg.WriteNextMsgAsCompletionAgent = mainText + // Remove the popup page + pages.RemovePage("botRoleSelectionPopup") + // Update the status line to reflect the change + updateStatusLine() + }) + roleListWidget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyEscape { + pages.RemovePage("botRoleSelectionPopup") + return nil + } + return event + }) + modal := func(p tview.Primitive, width, height int) tview.Primitive { + return tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(p, height, 1, true). + AddItem(nil, 0, 1, false), width, 1, true). + AddItem(nil, 0, 1, false) + } + // Add modal page and make it visible + pages.AddPage("botRoleSelectionPopup", modal(roleListWidget, 80, 20), true, true) + app.SetFocus(roleListWidget) +} |
