Enha: popups from the main window
no longer user has to go to the props table to get a pleasant popup to choose an option
This commit is contained in:
315
popups.go
Normal file
315
popups.go
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
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
|
||||||
|
refreshLocalModelsIfEmpty()
|
||||||
|
localModelsMu.RLock()
|
||||||
|
defer localModelsMu.RUnlock()
|
||||||
|
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))
|
||||||
|
message := "No models available for selection"
|
||||||
|
if strings.Contains(cfg.CurrentAPI, "openrouter.ai") {
|
||||||
|
message = "No OpenRouter models available. Check token and connection."
|
||||||
|
} else if strings.Contains(cfg.CurrentAPI, "api.deepseek.com") {
|
||||||
|
message = "DeepSeek models should be available. Please report bug."
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -53,7 +52,6 @@ func makePropsTable(props map[string]float32) *tview.Table {
|
|||||||
row++
|
row++
|
||||||
// Store cell data for later use in selection functions
|
// Store cell data for later use in selection functions
|
||||||
cellData := make(map[string]*CellData)
|
cellData := make(map[string]*CellData)
|
||||||
var modelCellID string // will be set for the model selection row
|
|
||||||
// Helper function to add a checkbox-like row
|
// Helper function to add a checkbox-like row
|
||||||
addCheckboxRow := func(label string, initialValue bool, onChange func(bool)) {
|
addCheckboxRow := func(label string, initialValue bool, onChange func(bool)) {
|
||||||
table.SetCell(row, 0,
|
table.SetCell(row, 0,
|
||||||
@@ -161,52 +159,6 @@ func makePropsTable(props map[string]float32) *tview.Table {
|
|||||||
defer localModelsMu.RUnlock()
|
defer localModelsMu.RUnlock()
|
||||||
return LocalModels
|
return LocalModels
|
||||||
}
|
}
|
||||||
var modelRowIndex int // will be set before model row is added
|
|
||||||
// Prepare API links dropdown - ensure current API is first, avoid duplicates
|
|
||||||
apiLinks := make([]string, 0, len(cfg.ApiLinks)+1)
|
|
||||||
apiLinks = append(apiLinks, cfg.CurrentAPI)
|
|
||||||
for _, api := range cfg.ApiLinks {
|
|
||||||
if api != cfg.CurrentAPI {
|
|
||||||
apiLinks = append(apiLinks, api)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addListPopupRow("Select an api", apiLinks, cfg.CurrentAPI, func(option string) {
|
|
||||||
cfg.CurrentAPI = option
|
|
||||||
// Update model list based on new API
|
|
||||||
newModelList := getModelListForAPI(cfg.CurrentAPI)
|
|
||||||
if modelCellID != "" {
|
|
||||||
if data := cellData[modelCellID]; data != nil {
|
|
||||||
data.Options = newModelList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
// Update the displayed cell text - need to find model row
|
|
||||||
// Search for model row by label
|
|
||||||
for r := 0; r < table.GetRowCount(); r++ {
|
|
||||||
if cell := table.GetCell(r, 0); cell != nil && cell.Text == "Select a model" {
|
|
||||||
if valueCell := table.GetCell(r, 1); valueCell != nil {
|
|
||||||
valueCell.SetText(chatBody.Model)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Prepare model list dropdown
|
|
||||||
modelRowIndex = row
|
|
||||||
modelCellID = fmt.Sprintf("listpopup_%d", modelRowIndex)
|
|
||||||
modelList := getModelListForAPI(cfg.CurrentAPI)
|
|
||||||
addListPopupRow("Select a model", modelList, chatBody.Model, func(option string) {
|
|
||||||
chatBody.Model = option
|
|
||||||
cfg.CurrentModel = chatBody.Model
|
|
||||||
})
|
|
||||||
// Role selection dropdown
|
|
||||||
addListPopupRow("Write next message as", listRolesWithUser(), cfg.WriteNextMsgAs, func(option string) {
|
|
||||||
cfg.WriteNextMsgAs = option
|
|
||||||
})
|
|
||||||
// Add input fields
|
// Add input fields
|
||||||
addInputRow("New char to write msg as", "", func(text string) {
|
addInputRow("New char to write msg as", "", func(text string) {
|
||||||
if text != "" {
|
if text != "" {
|
||||||
|
|||||||
108
tui.go
108
tui.go
@@ -77,16 +77,16 @@ var (
|
|||||||
[yellow]Ctrl+n[white]: start a new chat
|
[yellow]Ctrl+n[white]: start a new chat
|
||||||
[yellow]Ctrl+o[white]: open image file picker
|
[yellow]Ctrl+o[white]: open image file picker
|
||||||
[yellow]Ctrl+p[white]: props edit form (min-p, dry, etc.)
|
[yellow]Ctrl+p[white]: props edit form (min-p, dry, etc.)
|
||||||
[yellow]Ctrl+v[white]: switch between /completion and /chat api (if provided in config)
|
[yellow]Ctrl+v[white]: show API link selection popup to choose current API
|
||||||
[yellow]Ctrl+r[white]: start/stop recording from your microphone (needs stt server or whisper binary)
|
[yellow]Ctrl+r[white]: start/stop recording from your microphone (needs stt server or whisper binary)
|
||||||
[yellow]Ctrl+t[white]: remove thinking (<think>) and tool messages from context (delete from chat)
|
[yellow]Ctrl+t[white]: remove thinking (<think>) and tool messages from context (delete from chat)
|
||||||
[yellow]Ctrl+l[white]: rotate through free OpenRouter models (if openrouter api) or update connected model name (llamacpp)
|
[yellow]Ctrl+l[white]: show model selection popup to choose current model
|
||||||
[yellow]Ctrl+k[white]: switch tool use (recommend tool use to llm after user msg)
|
[yellow]Ctrl+k[white]: switch tool use (recommend tool use to llm after user msg)
|
||||||
[yellow]Ctrl+a[white]: interrupt tts (needs tts server)
|
[yellow]Ctrl+a[white]: interrupt tts (needs tts server)
|
||||||
[yellow]Ctrl+g[white]: open RAG file manager (load files for context retrieval)
|
[yellow]Ctrl+g[white]: open RAG file manager (load files for context retrieval)
|
||||||
[yellow]Ctrl+y[white]: list loaded RAG files (view and manage loaded files)
|
[yellow]Ctrl+y[white]: list loaded RAG files (view and manage loaded files)
|
||||||
[yellow]Ctrl+q[white]: cycle through mentioned chars in chat, to pick persona to send next msg as
|
[yellow]Ctrl+q[white]: show user role selection popup to choose who sends next msg as
|
||||||
[yellow]Ctrl+x[white]: cycle through mentioned chars in chat, to pick persona to send next msg as (for llm)
|
[yellow]Ctrl+x[white]: show bot role selection popup to choose which agent responds next
|
||||||
[yellow]Alt+1[white]: toggle shell mode (execute commands locally)
|
[yellow]Alt+1[white]: toggle shell mode (execute commands locally)
|
||||||
[yellow]Alt+2[white]: toggle auto-scrolling (for reading while LLM types)
|
[yellow]Alt+2[white]: toggle auto-scrolling (for reading while LLM types)
|
||||||
[yellow]Alt+3[white]: summarize chat history and start new chat with summary as tool response
|
[yellow]Alt+3[white]: summarize chat history and start new chat with summary as tool response
|
||||||
@@ -1026,30 +1026,8 @@ func init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlL {
|
if event.Key() == tcell.KeyCtrlL {
|
||||||
// Check if the current API is an OpenRouter API
|
// Show model selection popup instead of rotating models
|
||||||
if strings.Contains(cfg.CurrentAPI, "openrouter.ai/api/v1/") {
|
showModelSelectionPopup()
|
||||||
// Rotate through OpenRouter free models
|
|
||||||
if len(ORFreeModels) > 0 {
|
|
||||||
currentORModelIndex = (currentORModelIndex + 1) % len(ORFreeModels)
|
|
||||||
chatBody.Model = ORFreeModels[currentORModelIndex]
|
|
||||||
cfg.CurrentModel = chatBody.Model
|
|
||||||
}
|
|
||||||
updateStatusLine()
|
|
||||||
} else {
|
|
||||||
localModelsMu.RLock()
|
|
||||||
if len(LocalModels) > 0 {
|
|
||||||
currentLocalModelIndex = (currentLocalModelIndex + 1) % len(LocalModels)
|
|
||||||
chatBody.Model = LocalModels[currentLocalModelIndex]
|
|
||||||
cfg.CurrentModel = chatBody.Model
|
|
||||||
}
|
|
||||||
localModelsMu.RUnlock()
|
|
||||||
updateStatusLine()
|
|
||||||
// // For non-OpenRouter APIs, use the old logic
|
|
||||||
// go func() {
|
|
||||||
// fetchLCPModelName() // blocks
|
|
||||||
// updateStatusLine()
|
|
||||||
// }()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlT {
|
if event.Key() == tcell.KeyCtrlT {
|
||||||
@@ -1061,29 +1039,8 @@ func init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlV {
|
if event.Key() == tcell.KeyCtrlV {
|
||||||
// switch between API links using index-based rotation
|
// Show API link selection popup instead of rotating APIs
|
||||||
if len(cfg.ApiLinks) == 0 {
|
showAPILinkSelectionPopup()
|
||||||
// No API links to rotate through
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Find current API in the list to get the current index
|
|
||||||
currentIndex := -1
|
|
||||||
for i, api := range cfg.ApiLinks {
|
|
||||||
if api == cfg.CurrentAPI {
|
|
||||||
currentIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If current API is not in the list, start from beginning
|
|
||||||
// Otherwise, advance to next API in the list (with wrap-around)
|
|
||||||
if currentIndex == -1 {
|
|
||||||
currentAPIIndex = 0
|
|
||||||
} else {
|
|
||||||
currentAPIIndex = (currentIndex + 1) % len(cfg.ApiLinks)
|
|
||||||
}
|
|
||||||
cfg.CurrentAPI = cfg.ApiLinks[currentAPIIndex]
|
|
||||||
choseChunkParser()
|
|
||||||
updateStatusLine()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlS {
|
if event.Key() == tcell.KeyCtrlS {
|
||||||
@@ -1179,54 +1136,13 @@ func init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlQ {
|
if event.Key() == tcell.KeyCtrlQ {
|
||||||
persona := cfg.UserRole
|
// Show user role selection popup instead of cycling through roles
|
||||||
if cfg.WriteNextMsgAs != "" {
|
showUserRoleSelectionPopup()
|
||||||
persona = cfg.WriteNextMsgAs
|
|
||||||
}
|
|
||||||
roles := listRolesWithUser()
|
|
||||||
for i, role := range roles {
|
|
||||||
if strings.EqualFold(role, persona) {
|
|
||||||
if i == len(roles)-1 {
|
|
||||||
cfg.WriteNextMsgAs = roles[0] // reached last, get first
|
|
||||||
persona = cfg.WriteNextMsgAs
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cfg.WriteNextMsgAs = roles[i+1] // get next role
|
|
||||||
persona = cfg.WriteNextMsgAs
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// role got switch, update textview with character specific context for user
|
|
||||||
filtered := filterMessagesForCharacter(chatBody.Messages, persona)
|
|
||||||
textView.SetText(chatToText(filtered, cfg.ShowSys))
|
|
||||||
updateStatusLine()
|
|
||||||
colorText()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlX {
|
if event.Key() == tcell.KeyCtrlX {
|
||||||
botPersona := cfg.AssistantRole
|
// Show bot role selection popup instead of cycling through roles
|
||||||
if cfg.WriteNextMsgAsCompletionAgent != "" {
|
showBotRoleSelectionPopup()
|
||||||
botPersona = cfg.WriteNextMsgAsCompletionAgent
|
|
||||||
}
|
|
||||||
// roles := chatBody.ListRoles()
|
|
||||||
roles := listChatRoles()
|
|
||||||
if len(roles) == 0 {
|
|
||||||
logger.Warn("empty roles in chat")
|
|
||||||
}
|
|
||||||
if !strInSlice(cfg.AssistantRole, roles) {
|
|
||||||
roles = append(roles, cfg.AssistantRole)
|
|
||||||
}
|
|
||||||
for i, role := range roles {
|
|
||||||
if strings.EqualFold(role, botPersona) {
|
|
||||||
if i == len(roles)-1 {
|
|
||||||
cfg.WriteNextMsgAsCompletionAgent = roles[0] // reached last, get first
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cfg.WriteNextMsgAsCompletionAgent = roles[i+1] // get next role
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateStatusLine()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlG {
|
if event.Key() == tcell.KeyCtrlG {
|
||||||
|
|||||||
Reference in New Issue
Block a user