Feat: props table instead of form
This commit is contained in:
18
bot.go
18
bot.go
@@ -32,6 +32,8 @@ var (
|
|||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
logLevel = new(slog.LevelVar)
|
logLevel = new(slog.LevelVar)
|
||||||
|
)
|
||||||
|
var (
|
||||||
activeChatName string
|
activeChatName string
|
||||||
chunkChan = make(chan string, 10)
|
chunkChan = make(chan string, 10)
|
||||||
openAIToolChan = make(chan string, 10)
|
openAIToolChan = make(chan string, 10)
|
||||||
@@ -65,6 +67,22 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetLogLevel returns the current log level as a string
|
||||||
|
func GetLogLevel() string {
|
||||||
|
level := logLevel.Level()
|
||||||
|
switch level {
|
||||||
|
case slog.LevelDebug:
|
||||||
|
return "Debug"
|
||||||
|
case slog.LevelInfo:
|
||||||
|
return "Info"
|
||||||
|
case slog.LevelWarn:
|
||||||
|
return "Warn"
|
||||||
|
default:
|
||||||
|
// For any other values, return "Info" as default
|
||||||
|
return "Info"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createClient(connectTimeout time.Duration) *http.Client {
|
func createClient(connectTimeout time.Duration) *http.Client {
|
||||||
// Custom transport with connection timeout
|
// Custom transport with connection timeout
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{
|
||||||
|
|||||||
12
helpfuncs.go
12
helpfuncs.go
@@ -152,10 +152,16 @@ func setLogLevel(sl string) {
|
|||||||
|
|
||||||
func listRolesWithUser() []string {
|
func listRolesWithUser() []string {
|
||||||
roles := chatBody.ListRoles()
|
roles := chatBody.ListRoles()
|
||||||
if !strInSlice(cfg.UserRole, roles) {
|
// Remove user role if it exists in the list (to avoid duplicates and ensure it's at position 0)
|
||||||
roles = append(roles, cfg.UserRole)
|
filteredRoles := make([]string, 0, len(roles))
|
||||||
|
for _, role := range roles {
|
||||||
|
if role != cfg.UserRole {
|
||||||
|
filteredRoles = append(filteredRoles, role)
|
||||||
}
|
}
|
||||||
return roles
|
}
|
||||||
|
// Prepend user role to the beginning of the list
|
||||||
|
result := append([]string{cfg.UserRole}, filteredRoles...)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImage() {
|
func loadImage() {
|
||||||
|
|||||||
292
props_table.go
Normal file
292
props_table.go
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define constants for cell types
|
||||||
|
const (
|
||||||
|
CellTypeCheckbox = "checkbox"
|
||||||
|
CellTypeDropdown = "dropdown"
|
||||||
|
CellTypeInput = "input"
|
||||||
|
CellTypeHeader = "header"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CellData holds additional data for each cell
|
||||||
|
type CellData struct {
|
||||||
|
Type string
|
||||||
|
Options []string
|
||||||
|
OnChange interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// makePropsTable creates a table-based alternative to the props form
|
||||||
|
// This allows for better key bindings and immediate effect of changes
|
||||||
|
func makePropsTable(props map[string]float32) *tview.Table {
|
||||||
|
// Create a new table
|
||||||
|
table := tview.NewTable().
|
||||||
|
SetBorders(true).
|
||||||
|
SetSelectable(true, false) // Allow row selection but not column selection
|
||||||
|
|
||||||
|
table.SetTitle("Properties Configuration (Press 'x' to exit)").
|
||||||
|
SetTitleAlign(tview.AlignLeft)
|
||||||
|
|
||||||
|
row := 0
|
||||||
|
|
||||||
|
// Add a header or note row
|
||||||
|
headerCell := tview.NewTableCell("Props for llamacpp completion call").
|
||||||
|
SetTextColor(tcell.ColorYellow).
|
||||||
|
SetAlign(tview.AlignLeft).
|
||||||
|
SetSelectable(false)
|
||||||
|
table.SetCell(row, 0, headerCell)
|
||||||
|
table.SetCell(row, 1,
|
||||||
|
tview.NewTableCell("").
|
||||||
|
SetTextColor(tcell.ColorYellow).
|
||||||
|
SetSelectable(false))
|
||||||
|
row++
|
||||||
|
|
||||||
|
// Store cell data for later use in selection functions
|
||||||
|
cellData := make(map[string]*CellData)
|
||||||
|
|
||||||
|
// Helper function to add a checkbox-like row
|
||||||
|
addCheckboxRow := func(label string, initialValue bool, onChange func(bool)) {
|
||||||
|
table.SetCell(row, 0,
|
||||||
|
tview.NewTableCell(label).
|
||||||
|
SetTextColor(tcell.ColorWhite).
|
||||||
|
SetAlign(tview.AlignLeft).
|
||||||
|
SetSelectable(false))
|
||||||
|
|
||||||
|
valueText := "No"
|
||||||
|
if initialValue {
|
||||||
|
valueText = "Yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
valueCell := tview.NewTableCell(valueText).
|
||||||
|
SetTextColor(tcell.ColorGreen).
|
||||||
|
SetAlign(tview.AlignCenter)
|
||||||
|
table.SetCell(row, 1, valueCell)
|
||||||
|
|
||||||
|
// Store cell data
|
||||||
|
cellID := fmt.Sprintf("checkbox_%d", row)
|
||||||
|
cellData[cellID] = &CellData{
|
||||||
|
Type: CellTypeCheckbox,
|
||||||
|
OnChange: onChange,
|
||||||
|
}
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to add a dropdown-like row
|
||||||
|
addDropdownRow := func(label string, options []string, initialValue string, onChange func(string)) {
|
||||||
|
table.SetCell(row, 0,
|
||||||
|
tview.NewTableCell(label).
|
||||||
|
SetTextColor(tcell.ColorWhite).
|
||||||
|
SetAlign(tview.AlignLeft).
|
||||||
|
SetSelectable(false))
|
||||||
|
|
||||||
|
valueCell := tview.NewTableCell(initialValue).
|
||||||
|
SetTextColor(tcell.ColorGreen).
|
||||||
|
SetAlign(tview.AlignCenter)
|
||||||
|
table.SetCell(row, 1, valueCell)
|
||||||
|
|
||||||
|
// Store cell data
|
||||||
|
cellID := fmt.Sprintf("dropdown_%d", row)
|
||||||
|
cellData[cellID] = &CellData{
|
||||||
|
Type: CellTypeDropdown,
|
||||||
|
Options: options,
|
||||||
|
OnChange: onChange,
|
||||||
|
}
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to add an input field row
|
||||||
|
addInputRow := func(label string, initialValue string, onChange func(string)) {
|
||||||
|
table.SetCell(row, 0,
|
||||||
|
tview.NewTableCell(label).
|
||||||
|
SetTextColor(tcell.ColorWhite).
|
||||||
|
SetAlign(tview.AlignLeft).
|
||||||
|
SetSelectable(false))
|
||||||
|
|
||||||
|
valueCell := tview.NewTableCell(initialValue).
|
||||||
|
SetTextColor(tcell.ColorGreen).
|
||||||
|
SetAlign(tview.AlignCenter)
|
||||||
|
table.SetCell(row, 1, valueCell)
|
||||||
|
|
||||||
|
// Store cell data
|
||||||
|
cellID := fmt.Sprintf("input_%d", row)
|
||||||
|
cellData[cellID] = &CellData{
|
||||||
|
Type: CellTypeInput,
|
||||||
|
OnChange: onChange,
|
||||||
|
}
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add checkboxes
|
||||||
|
addCheckboxRow("Insert \U000F0E88 (/completion only)", cfg.ThinkUse, func(checked bool) {
|
||||||
|
cfg.ThinkUse = checked
|
||||||
|
})
|
||||||
|
|
||||||
|
addCheckboxRow("RAG use", cfg.RAGEnabled, func(checked bool) {
|
||||||
|
cfg.RAGEnabled = checked
|
||||||
|
})
|
||||||
|
|
||||||
|
addCheckboxRow("Inject role", injectRole, func(checked bool) {
|
||||||
|
injectRole = checked
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add dropdowns
|
||||||
|
logLevels := []string{"Debug", "Info", "Warn"}
|
||||||
|
addDropdownRow("Set log level", logLevels, GetLogLevel(), func(option string) {
|
||||||
|
setLogLevel(option)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Prepare API links dropdown - insert current API at the beginning
|
||||||
|
apiLinks := slices.Insert(cfg.ApiLinks, 0, cfg.CurrentAPI)
|
||||||
|
addDropdownRow("Select an api", apiLinks, cfg.CurrentAPI, func(option string) {
|
||||||
|
cfg.CurrentAPI = option
|
||||||
|
})
|
||||||
|
|
||||||
|
// Prepare model list dropdown
|
||||||
|
modelList := []string{chatBody.Model, "deepseek-chat", "deepseek-reasoner"}
|
||||||
|
modelList = append(modelList, ORFreeModels...)
|
||||||
|
addDropdownRow("Select a model", modelList, chatBody.Model, func(option string) {
|
||||||
|
chatBody.Model = option
|
||||||
|
})
|
||||||
|
|
||||||
|
// Role selection dropdown
|
||||||
|
addDropdownRow("Write next message as", listRolesWithUser(), cfg.WriteNextMsgAs, func(option string) {
|
||||||
|
cfg.WriteNextMsgAs = option
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add input fields
|
||||||
|
addInputRow("New char to write msg as", "", func(text string) {
|
||||||
|
if text != "" {
|
||||||
|
cfg.WriteNextMsgAs = text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
addInputRow("Username", cfg.UserRole, func(text string) {
|
||||||
|
if text != "" {
|
||||||
|
renameUser(cfg.UserRole, text)
|
||||||
|
cfg.UserRole = text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add property fields (the float32 values)
|
||||||
|
for propName, value := range props {
|
||||||
|
propName := propName // capture loop variable for closure
|
||||||
|
propValue := fmt.Sprintf("%v", value)
|
||||||
|
addInputRow(propName, propValue, func(text string) {
|
||||||
|
if val, err := strconv.ParseFloat(text, 32); err == nil {
|
||||||
|
props[propName] = float32(val)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set selection function to handle dropdown-like behavior
|
||||||
|
table.SetSelectedFunc(func(selectedRow, selectedCol int) {
|
||||||
|
// Only handle selection on the value column (column 1)
|
||||||
|
if selectedCol != 1 {
|
||||||
|
// If user selects the label column, move to the value column
|
||||||
|
if table.GetRowCount() > selectedRow && table.GetColumnCount() > 1 {
|
||||||
|
table.Select(selectedRow, 1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the cell and its corresponding data
|
||||||
|
cell := table.GetCell(selectedRow, selectedCol)
|
||||||
|
cellID := fmt.Sprintf("checkbox_%d", selectedRow)
|
||||||
|
|
||||||
|
// Check if it's a checkbox
|
||||||
|
if cellData[cellID] != nil && cellData[cellID].Type == CellTypeCheckbox {
|
||||||
|
data := cellData[cellID]
|
||||||
|
if onChange, ok := data.OnChange.(func(bool)); ok {
|
||||||
|
// Toggle the checkbox value
|
||||||
|
newValue := cell.Text == "No"
|
||||||
|
onChange(newValue)
|
||||||
|
if newValue {
|
||||||
|
cell.SetText("Yes")
|
||||||
|
} else {
|
||||||
|
cell.SetText("No")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for dropdown
|
||||||
|
dropdownCellID := fmt.Sprintf("dropdown_%d", selectedRow)
|
||||||
|
if cellData[dropdownCellID] != nil && cellData[dropdownCellID].Type == CellTypeDropdown {
|
||||||
|
data := cellData[dropdownCellID]
|
||||||
|
if onChange, ok := data.OnChange.(func(string)); ok && data.Options != nil {
|
||||||
|
// Find current option and cycle to next
|
||||||
|
currentValue := cell.Text
|
||||||
|
currentIndex := -1
|
||||||
|
for i, opt := range data.Options {
|
||||||
|
if opt == currentValue {
|
||||||
|
currentIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next option (cycle back to 0 if at end)
|
||||||
|
nextIndex := (currentIndex + 1) % len(data.Options)
|
||||||
|
newValue := data.Options[nextIndex]
|
||||||
|
|
||||||
|
onChange(newValue)
|
||||||
|
cell.SetText(newValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle input fields by creating an input modal on selection
|
||||||
|
inputCellID := fmt.Sprintf("input_%d", selectedRow)
|
||||||
|
if cellData[inputCellID] != nil && cellData[inputCellID].Type == CellTypeInput {
|
||||||
|
data := cellData[inputCellID]
|
||||||
|
if onChange, ok := data.OnChange.(func(string)); ok {
|
||||||
|
// Create an input modal
|
||||||
|
currentValue := cell.Text
|
||||||
|
inputFld := tview.NewInputField()
|
||||||
|
inputFld.SetLabel("Edit value: ")
|
||||||
|
inputFld.SetText(currentValue)
|
||||||
|
inputFld.SetDoneFunc(func(key tcell.Key) {
|
||||||
|
if key == tcell.KeyEnter {
|
||||||
|
newText := inputFld.GetText()
|
||||||
|
onChange(newText)
|
||||||
|
cell.SetText(newText) // Update the table cell
|
||||||
|
}
|
||||||
|
pages.RemovePage("editModal")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a simple modal with the input field
|
||||||
|
modalFlex := tview.NewFlex().
|
||||||
|
SetDirection(tview.FlexRow).
|
||||||
|
AddItem(tview.NewBox(), 0, 1, false). // Spacer
|
||||||
|
AddItem(tview.NewFlex().
|
||||||
|
AddItem(tview.NewBox(), 0, 1, false). // Spacer
|
||||||
|
AddItem(inputFld, 30, 1, true). // Input field
|
||||||
|
AddItem(tview.NewBox(), 0, 1, false), // Spacer
|
||||||
|
0, 1, true).
|
||||||
|
AddItem(tview.NewBox(), 0, 1, false) // Spacer
|
||||||
|
|
||||||
|
// Add modal page and make it visible
|
||||||
|
pages.AddPage("editModal", modalFlex, true, true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set input capture to handle 'x' key for exiting
|
||||||
|
table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
|
if event.Key() == tcell.KeyRune && event.Rune() == 'x' {
|
||||||
|
pages.RemovePage(propsPage)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return event
|
||||||
|
})
|
||||||
|
|
||||||
|
return table
|
||||||
|
}
|
||||||
4
tui.go
4
tui.go
@@ -912,8 +912,8 @@ func init() {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlP {
|
if event.Key() == tcell.KeyCtrlP {
|
||||||
propsForm := makePropsForm(defaultLCPProps)
|
propsTable := makePropsTable(defaultLCPProps)
|
||||||
pages.AddPage(propsPage, propsForm, true, true)
|
pages.AddPage(propsPage, propsTable, true, true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if event.Key() == tcell.KeyCtrlN {
|
if event.Key() == tcell.KeyCtrlN {
|
||||||
|
|||||||
Reference in New Issue
Block a user