Merge branch 'master' into doc/tutorial
This commit is contained in:
41
bot.go
41
bot.go
@@ -826,6 +826,30 @@ func charToStart(agentName string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func updateModelLists() {
|
||||
var err error
|
||||
if cfg.OpenRouterToken != "" {
|
||||
ORFreeModels, err = fetchORModels(true)
|
||||
if err != nil {
|
||||
logger.Warn("failed to fetch or models", "error", err)
|
||||
}
|
||||
}
|
||||
// if llama.cpp started after gf-lt?
|
||||
LocalModels, err = fetchLCPModels()
|
||||
if err != nil {
|
||||
logger.Warn("failed to fetch llama.cpp models", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func updateModelListsTicker() {
|
||||
updateModelLists() // run on the start
|
||||
ticker := time.NewTicker(time.Minute * 1)
|
||||
for {
|
||||
<-ticker.C
|
||||
updateModelLists()
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
cfg, err = config.LoadConfig("config.toml")
|
||||
@@ -878,22 +902,6 @@ func init() {
|
||||
playerOrder = []string{cfg.UserRole, cfg.AssistantRole, cfg.CluedoRole2}
|
||||
cluedoState = extra.CluedoPrepCards(playerOrder)
|
||||
}
|
||||
if cfg.OpenRouterToken != "" {
|
||||
go func() {
|
||||
ORModels, err := fetchORModels(true)
|
||||
if err != nil {
|
||||
logger.Error("failed to fetch or models", "error", err)
|
||||
} else {
|
||||
ORFreeModels = ORModels
|
||||
}
|
||||
}()
|
||||
}
|
||||
go func() {
|
||||
LocalModels, err = fetchLCPModels()
|
||||
if err != nil {
|
||||
logger.Error("failed to fetch llama.cpp models", "error", err)
|
||||
}
|
||||
}()
|
||||
choseChunkParser()
|
||||
httpClient = createClient(time.Second * 15)
|
||||
if cfg.TTS_ENABLED {
|
||||
@@ -902,4 +910,5 @@ func init() {
|
||||
if cfg.STT_ENABLED {
|
||||
asr = extra.NewSTT(logger, cfg)
|
||||
}
|
||||
go updateModelListsTicker()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
@@ -84,6 +86,13 @@ func LoadConfig(fn string) (*Config, error) {
|
||||
config.OpenRouterCompletionAPI: config.OpenRouterChatAPI,
|
||||
config.OpenRouterChatAPI: config.ChatAPI,
|
||||
}
|
||||
// check env if keys not in config
|
||||
if config.OpenRouterToken == "" {
|
||||
config.OpenRouterToken = os.Getenv("OPENROUTER_API_KEY")
|
||||
}
|
||||
if config.DeepSeekToken == "" {
|
||||
config.DeepSeekToken = os.Getenv("DEEPSEEK_API_KEY")
|
||||
}
|
||||
// Build ApiLinks slice with only non-empty API links
|
||||
// Only include DeepSeek APIs if DeepSeekToken is provided
|
||||
if config.DeepSeekToken != "" {
|
||||
@@ -94,7 +103,6 @@ func LoadConfig(fn string) (*Config, error) {
|
||||
config.ApiLinks = append(config.ApiLinks, config.DeepSeekCompletionAPI)
|
||||
}
|
||||
}
|
||||
|
||||
// Only include OpenRouter APIs if OpenRouterToken is provided
|
||||
if config.OpenRouterToken != "" {
|
||||
if config.OpenRouterChatAPI != "" {
|
||||
@@ -104,7 +112,6 @@ func LoadConfig(fn string) (*Config, error) {
|
||||
config.ApiLinks = append(config.ApiLinks, config.OpenRouterCompletionAPI)
|
||||
}
|
||||
}
|
||||
|
||||
// Always include basic APIs
|
||||
if config.ChatAPI != "" {
|
||||
config.ApiLinks = append(config.ApiLinks, config.ChatAPI)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
@@ -134,9 +135,16 @@ func makePropsTable(props map[string]float32) *tview.Table {
|
||||
addListPopupRow("Select an api", apiLinks, cfg.CurrentAPI, func(option string) {
|
||||
cfg.CurrentAPI = option
|
||||
})
|
||||
var modelList []string
|
||||
// INFO: modelList is chosen based on current api link
|
||||
if strings.Contains(cfg.CurrentAPI, "api.deepseek.com/") {
|
||||
modelList = []string{chatBody.Model, "deepseek-chat", "deepseek-reasoner"}
|
||||
} else if strings.Contains(cfg.CurrentAPI, "opentouter.ai") {
|
||||
modelList = ORFreeModels
|
||||
} else { // would match on localhost but what if llama.cpp served non localy?
|
||||
modelList = LocalModels
|
||||
}
|
||||
// Prepare model list dropdown
|
||||
modelList := []string{chatBody.Model, "deepseek-chat", "deepseek-reasoner"}
|
||||
modelList = append(modelList, ORFreeModels...)
|
||||
addListPopupRow("Select a model", modelList, chatBody.Model, func(option string) {
|
||||
chatBody.Model = option
|
||||
})
|
||||
|
||||
198
tables.go
198
tables.go
@@ -33,11 +33,13 @@ func makeChatTable(chatMap map[string]models.Chat) *tview.Table {
|
||||
case 0:
|
||||
chatActTable.SetCell(r, c,
|
||||
tview.NewTableCell(chatList[r]).
|
||||
SetSelectable(false).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
case 1:
|
||||
chatActTable.SetCell(r, c,
|
||||
tview.NewTableCell(chatMap[chatList[r]].Msgs[len(chatMap[chatList[r]].Msgs)-30:]).
|
||||
SetSelectable(false).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
default:
|
||||
@@ -48,14 +50,11 @@ func makeChatTable(chatMap map[string]models.Chat) *tview.Table {
|
||||
}
|
||||
}
|
||||
}
|
||||
chatActTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
||||
chatActTable.Select(0, 0).SetSelectable(true, true).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') {
|
||||
pages.RemovePage(historyPage)
|
||||
return
|
||||
}
|
||||
if key == tcell.KeyEnter {
|
||||
chatActTable.SetSelectable(true, true)
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
tc := chatActTable.GetCell(row, column)
|
||||
tc.SetTextColor(tcell.ColorRed)
|
||||
@@ -192,21 +191,21 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
ragflex := tview.NewFlex().SetDirection(tview.FlexRow).
|
||||
AddItem(longStatusView, 0, 10, false).
|
||||
AddItem(fileTable, 0, 60, true)
|
||||
|
||||
// Add the exit option as the first row (row 0)
|
||||
fileTable.SetCell(0, 0,
|
||||
tview.NewTableCell("Exit RAG manager").
|
||||
SetTextColor(tcell.ColorWhite).
|
||||
SetAlign(tview.AlignCenter))
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
fileTable.SetCell(0, 1,
|
||||
tview.NewTableCell("(Close without action)").
|
||||
SetTextColor(tcell.ColorGray).
|
||||
SetAlign(tview.AlignCenter))
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
fileTable.SetCell(0, 2,
|
||||
tview.NewTableCell("exit").
|
||||
SetTextColor(tcell.ColorGray).
|
||||
SetAlign(tview.AlignCenter))
|
||||
|
||||
// Add the file rows starting from row 1
|
||||
for r := 0; r < rows; r++ {
|
||||
for c := 0; c < cols; c++ {
|
||||
@@ -215,8 +214,15 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
fileTable.SetCell(r+1, c, // +1 to account for the exit row at index 0
|
||||
tview.NewTableCell(fileList[r]).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
} else {
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else if c == 1 { // Action description column - not selectable
|
||||
fileTable.SetCell(r+1, c, // +1 to account for the exit row at index 0
|
||||
tview.NewTableCell("(Action)").
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else { // Action button column - selectable
|
||||
fileTable.SetCell(r+1, c, // +1 to account for the exit row at index 0
|
||||
tview.NewTableCell(actions[c-1]).
|
||||
SetTextColor(color).
|
||||
@@ -250,29 +256,32 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
}
|
||||
}
|
||||
}()
|
||||
fileTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') || key == tcell.KeyCtrlX {
|
||||
pages.RemovePage(RAGPage)
|
||||
fileTable.Select(0, 0).
|
||||
SetFixed(1, 1).
|
||||
SetSelectable(true, false).
|
||||
SetSelectedStyle(tcell.StyleDefault.Background(tcell.ColorGray).Foreground(tcell.ColorWhite)).
|
||||
SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') || key == tcell.KeyCtrlX {
|
||||
pages.RemovePage(RAGPage)
|
||||
return
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
// If user selects a non-actionable column (0 or 1), move to first action column (2)
|
||||
if column <= 1 {
|
||||
if fileTable.GetColumnCount() > 2 {
|
||||
fileTable.Select(row, 2) // Select first action column
|
||||
}
|
||||
return
|
||||
}
|
||||
if key == tcell.KeyEnter {
|
||||
fileTable.SetSelectable(true, true)
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
// defer pages.RemovePage(RAGPage)
|
||||
tc := fileTable.GetCell(row, column)
|
||||
tc.SetTextColor(tcell.ColorRed)
|
||||
fileTable.SetSelectable(false, false)
|
||||
|
||||
// Check if the selected row is the exit row (row 0) - do this first to avoid index issues
|
||||
if row == 0 {
|
||||
pages.RemovePage(RAGPage)
|
||||
return
|
||||
}
|
||||
|
||||
// For file rows, get the filename (row index - 1 because of the exit row at index 0)
|
||||
fpath := fileList[row-1] // -1 to account for the exit row at index 0
|
||||
|
||||
// notification := fmt.Sprintf("chat: %s; action: %s", fpath, tc.Text)
|
||||
switch tc.Text {
|
||||
case "load":
|
||||
@@ -303,7 +312,6 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// Add input capture to the flex container to handle 'x' key for closing
|
||||
ragflex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyRune && event.Rune() == 'x' {
|
||||
@@ -312,7 +320,6 @@ func makeRAGTable(fileList []string) *tview.Flex {
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
return ragflex
|
||||
}
|
||||
|
||||
@@ -331,21 +338,21 @@ func makeLoadedRAGTable(fileList []string) *tview.Flex {
|
||||
ragflex := tview.NewFlex().SetDirection(tview.FlexRow).
|
||||
AddItem(longStatusView, 0, 10, false).
|
||||
AddItem(fileTable, 0, 60, true)
|
||||
|
||||
// Add the exit option as the first row (row 0)
|
||||
fileTable.SetCell(0, 0,
|
||||
tview.NewTableCell("Exit Loaded Files manager").
|
||||
SetTextColor(tcell.ColorWhite).
|
||||
SetAlign(tview.AlignCenter))
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
fileTable.SetCell(0, 1,
|
||||
tview.NewTableCell("(Close without action)").
|
||||
SetTextColor(tcell.ColorGray).
|
||||
SetAlign(tview.AlignCenter))
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
fileTable.SetCell(0, 2,
|
||||
tview.NewTableCell("exit").
|
||||
SetTextColor(tcell.ColorGray).
|
||||
SetAlign(tview.AlignCenter))
|
||||
|
||||
// Add the file rows starting from row 1
|
||||
for r := 0; r < rows; r++ {
|
||||
for c := 0; c < cols; c++ {
|
||||
@@ -354,8 +361,15 @@ func makeLoadedRAGTable(fileList []string) *tview.Flex {
|
||||
fileTable.SetCell(r+1, c, // +1 to account for the exit row at index 0
|
||||
tview.NewTableCell(fileList[r]).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
} else {
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else if c == 1 { // Action description column - not selectable
|
||||
fileTable.SetCell(r+1, c, // +1 to account for the exit row at index 0
|
||||
tview.NewTableCell("(Action)").
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else { // Action button column - selectable
|
||||
fileTable.SetCell(r+1, c, // +1 to account for the exit row at index 0
|
||||
tview.NewTableCell(actions[c-1]).
|
||||
SetTextColor(color).
|
||||
@@ -363,29 +377,33 @@ func makeLoadedRAGTable(fileList []string) *tview.Flex {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') || key == tcell.KeyCtrlX {
|
||||
pages.RemovePage(RAGLoadedPage)
|
||||
fileTable.Select(0, 0).
|
||||
SetFixed(1, 1).
|
||||
SetSelectable(true, false).
|
||||
SetSelectedStyle(tcell.StyleDefault.Background(tcell.ColorGray).Foreground(tcell.ColorWhite)).
|
||||
SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') || key == tcell.KeyCtrlX {
|
||||
pages.RemovePage(RAGLoadedPage)
|
||||
return
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
// If user selects a non-actionable column (0 or 1), move to first action column (2)
|
||||
if column <= 1 {
|
||||
if fileTable.GetColumnCount() > 2 {
|
||||
fileTable.Select(row, 2) // Select first action column
|
||||
}
|
||||
return
|
||||
}
|
||||
if key == tcell.KeyEnter {
|
||||
fileTable.SetSelectable(true, true)
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
|
||||
tc := fileTable.GetCell(row, column)
|
||||
tc.SetTextColor(tcell.ColorRed)
|
||||
fileTable.SetSelectable(false, false)
|
||||
|
||||
// Check if the selected row is the exit row (row 0) - do this first to avoid index issues
|
||||
if row == 0 {
|
||||
pages.RemovePage(RAGLoadedPage)
|
||||
return
|
||||
}
|
||||
|
||||
// For file rows, get the filename (row index - 1 because of the exit row at index 0)
|
||||
fpath := fileList[row-1] // -1 to account for the exit row at index 0
|
||||
|
||||
switch tc.Text {
|
||||
case "delete":
|
||||
if err := ragger.RemoveFile(fpath); err != nil {
|
||||
@@ -403,7 +421,6 @@ func makeLoadedRAGTable(fileList []string) *tview.Flex {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// Add input capture to the flex container to handle 'x' key for closing
|
||||
ragflex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyRune && event.Rune() == 'x' {
|
||||
@@ -412,7 +429,6 @@ func makeLoadedRAGTable(fileList []string) *tview.Flex {
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
return ragflex
|
||||
}
|
||||
|
||||
@@ -428,8 +444,9 @@ func makeAgentTable(agentList []string) *tview.Table {
|
||||
chatActTable.SetCell(r, c,
|
||||
tview.NewTableCell(agentList[r]).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
} else {
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else if c == 1 {
|
||||
if actions[c-1] == "filepath" {
|
||||
cc, ok := sysMap[agentList[r]]
|
||||
if !ok {
|
||||
@@ -438,28 +455,41 @@ func makeAgentTable(agentList []string) *tview.Table {
|
||||
chatActTable.SetCell(r, c,
|
||||
tview.NewTableCell(cc.FilePath).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
continue
|
||||
}
|
||||
chatActTable.SetCell(r, c,
|
||||
tview.NewTableCell(actions[c-1]).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
} else {
|
||||
chatActTable.SetCell(r, c,
|
||||
tview.NewTableCell(actions[c-1]).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
}
|
||||
}
|
||||
}
|
||||
chatActTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') {
|
||||
pages.RemovePage(agentPage)
|
||||
chatActTable.Select(0, 0).
|
||||
SetFixed(1, 1).
|
||||
SetSelectable(true, false).
|
||||
SetSelectedStyle(tcell.StyleDefault.Background(tcell.ColorGray).Foreground(tcell.ColorWhite)).
|
||||
SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') {
|
||||
pages.RemovePage(agentPage)
|
||||
return
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
// If user selects a non-actionable column (0 or 1), move to first action column (2)
|
||||
if column <= 1 {
|
||||
if chatActTable.GetColumnCount() > 2 {
|
||||
chatActTable.Select(row, 2) // Select first action column
|
||||
}
|
||||
return
|
||||
}
|
||||
if key == tcell.KeyEnter {
|
||||
chatActTable.SetSelectable(true, true)
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
|
||||
tc := chatActTable.GetCell(row, column)
|
||||
tc.SetTextColor(tcell.ColorRed)
|
||||
chatActTable.SetSelectable(false, false)
|
||||
selected := agentList[row]
|
||||
// notification := fmt.Sprintf("chat: %s; action: %s", selectedChat, tc.Text)
|
||||
switch tc.Text {
|
||||
@@ -528,7 +558,8 @@ func makeCodeBlockTable(codeBlocks []string) *tview.Table {
|
||||
table.SetCell(r, c,
|
||||
tview.NewTableCell(codeBlocks[r][:previewLen]).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else {
|
||||
table.SetCell(r, c,
|
||||
tview.NewTableCell(actions[c-1]).
|
||||
@@ -537,18 +568,25 @@ func makeCodeBlockTable(codeBlocks []string) *tview.Table {
|
||||
}
|
||||
}
|
||||
}
|
||||
table.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') {
|
||||
pages.RemovePage(codeBlockPage)
|
||||
table.Select(0, 0).
|
||||
SetFixed(1, 1).
|
||||
SetSelectable(true, false).
|
||||
SetSelectedStyle(tcell.StyleDefault.Background(tcell.ColorGray).Foreground(tcell.ColorWhite)).
|
||||
SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') {
|
||||
pages.RemovePage(codeBlockPage)
|
||||
return
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
// If user selects a non-actionable column (0), move to first action column (1)
|
||||
if column == 0 {
|
||||
if table.GetColumnCount() > 1 {
|
||||
table.Select(row, 1) // Select first action column
|
||||
}
|
||||
return
|
||||
}
|
||||
if key == tcell.KeyEnter {
|
||||
table.SetSelectable(true, true)
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
|
||||
tc := table.GetCell(row, column)
|
||||
tc.SetTextColor(tcell.ColorRed)
|
||||
table.SetSelectable(false, false)
|
||||
selected := codeBlocks[row]
|
||||
// notification := fmt.Sprintf("chat: %s; action: %s", selectedChat, tc.Text)
|
||||
switch tc.Text {
|
||||
@@ -592,7 +630,8 @@ func makeImportChatTable(filenames []string) *tview.Table {
|
||||
chatActTable.SetCell(r, c,
|
||||
tview.NewTableCell(filenames[r]).
|
||||
SetTextColor(color).
|
||||
SetAlign(tview.AlignCenter))
|
||||
SetAlign(tview.AlignCenter).
|
||||
SetSelectable(false))
|
||||
} else {
|
||||
chatActTable.SetCell(r, c,
|
||||
tview.NewTableCell(actions[c-1]).
|
||||
@@ -601,18 +640,25 @@ func makeImportChatTable(filenames []string) *tview.Table {
|
||||
}
|
||||
}
|
||||
}
|
||||
chatActTable.Select(0, 0).SetFixed(1, 1).SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') {
|
||||
pages.RemovePage(historyPage)
|
||||
chatActTable.Select(0, 0).
|
||||
SetFixed(1, 1).
|
||||
SetSelectable(true, false).
|
||||
SetSelectedStyle(tcell.StyleDefault.Background(tcell.ColorGray).Foreground(tcell.ColorWhite)).
|
||||
SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyF1 || key == tcell.Key('x') {
|
||||
pages.RemovePage(historyPage)
|
||||
return
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
// If user selects a non-actionable column (0), move to first action column (1)
|
||||
if column == 0 {
|
||||
if chatActTable.GetColumnCount() > 1 {
|
||||
chatActTable.Select(row, 1) // Select first action column
|
||||
}
|
||||
return
|
||||
}
|
||||
if key == tcell.KeyEnter {
|
||||
chatActTable.SetSelectable(true, true)
|
||||
}
|
||||
}).SetSelectedFunc(func(row int, column int) {
|
||||
|
||||
tc := chatActTable.GetCell(row, column)
|
||||
tc.SetTextColor(tcell.ColorRed)
|
||||
chatActTable.SetSelectable(false, false)
|
||||
selected := filenames[row]
|
||||
// notification := fmt.Sprintf("chat: %s; action: %s", selectedChat, tc.Text)
|
||||
switch tc.Text {
|
||||
|
||||
Reference in New Issue
Block a user