From cec10210c284a17cd341b48ee205bcfd278205bd Mon Sep 17 00:00:00 2001 From: Grail Finder Date: Wed, 3 Dec 2025 13:29:57 +0300 Subject: Feat: props table instead of form --- props_table.go | 292 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 props_table.go (limited to 'props_table.go') diff --git a/props_table.go b/props_table.go new file mode 100644 index 0000000..c3dd7b2 --- /dev/null +++ b/props_table.go @@ -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 +} \ No newline at end of file -- cgit v1.2.3