Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,6 @@ dist/
.adk/
**/.rogue
bin/

# Go TUI
packages/tui/cmd/rogue/__debug*
8 changes: 8 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@
],
"envFile": "${workspaceFolder}/.env"
},
{
"name": "Rogue TUI",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/packages/tui/cmd/rogue/main.go",
"console": "integratedTerminal"
},
{
"name": "Rogue AIO",
"type": "debugpy",
Expand Down
2 changes: 1 addition & 1 deletion examples/mcp/tshirt_store_langgraph_mcp/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"--transport",
"transport",
default="streamable-http",
choices=["streamable-http", "sse"],
type=click.Choice(["streamable-http", "sse"]),
help="Transport to use for the mcp server",
)
def main(host: str, port: int, transport: Literal["streamable-http", "sse"]) -> None:
Expand Down
41 changes: 21 additions & 20 deletions packages/tui/internal/tui/common_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,17 @@ func (m Model) handlePasteMsg(msg tea.PasteMsg) (Model, tea.Cmd) {
}

// Only paste into text fields (Agent URL and Judge Model)
if m.evalState.currentField <= 1 {
switch m.evalState.currentField {
case 0: // Agent URL
// Insert at cursor position
runes := []rune(m.evalState.AgentURL)
m.evalState.AgentURL = string(runes[:m.evalState.cursorPos]) + cleanText + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos += len([]rune(cleanText))
case 1: // Judge Model
// Insert at cursor position
runes := []rune(m.evalState.JudgeModel)
m.evalState.JudgeModel = string(runes[:m.evalState.cursorPos]) + cleanText + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos += len([]rune(cleanText))
}
switch m.evalState.currentField {
case 0: // Agent URL
// Insert at cursor position
runes := []rune(m.evalState.AgentURL)
m.evalState.AgentURL = string(runes[:m.evalState.cursorPos]) + cleanText + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos += len([]rune(cleanText))
case 3: // Judge Model
// Insert at cursor position
runes := []rune(m.evalState.JudgeModel)
m.evalState.JudgeModel = string(runes[:m.evalState.cursorPos]) + cleanText + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos += len([]rune(cleanText))
}
return m, nil
}
Expand Down Expand Up @@ -202,14 +200,17 @@ func (m Model) handleCommandSelectedMsg(msg components.CommandSelectedMsg) (Mode
// Use the configured model in provider/model format
judgeModel = m.config.SelectedProvider + "/" + m.config.SelectedModel
}
// TODO read agent url and protocol .rogue/user_config.json
m.evalState = &EvaluationViewState{
ServerURL: m.config.ServerURL,
AgentURL: "http://localhost:10001",
JudgeModel: judgeModel,
ParallelRuns: 1,
DeepTest: false,
Scenarios: loadScenariosFromWorkdir(),
cursorPos: len([]rune("http://localhost:10001")), // Set cursor to end of Agent URL
ServerURL: m.config.ServerURL,
AgentURL: "http://localhost:10001",
AgentProtocol: ProtocolA2A,
AgentTransport: TransportHTTP,
JudgeModel: judgeModel,
ParallelRuns: 1,
DeepTest: false,
Scenarios: loadScenariosFromWorkdir(),
cursorPos: len([]rune("http://localhost:10001")), // Set cursor to end of Agent URL
}
case "configure_models":
// Open LLM configuration dialog
Expand Down
94 changes: 87 additions & 7 deletions packages/tui/internal/tui/eval_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func (m Model) renderNewEvaluation() string {

title := titleStyle.Render("πŸ§ͺ New Evaluation")

// Helper function to render a field with inline label and value
renderField := func(fieldIndex int, label, value string) string {
// Helper function to render a text field with inline label and value
renderTextField := func(fieldIndex int, label, value string) string {
active := m.evalState.currentField == fieldIndex

labelStyle := lipgloss.NewStyle().
Expand Down Expand Up @@ -83,8 +83,86 @@ func (m Model) renderNewEvaluation() string {
return fieldContainer.Render(fieldContent)
}

// Helper function to render a dropdown field with indicators
renderDropdownField := func(fieldIndex int, label, value string) string {
active := m.evalState.currentField == fieldIndex

labelStyle := lipgloss.NewStyle().
Foreground(t.TextMuted()).
Background(t.Background()).
Width(20).
Align(lipgloss.Right)

valueStyle := lipgloss.NewStyle().
Foreground(t.Text()).
Background(t.Background()).
Padding(0, 1)

if active {
labelStyle = labelStyle.Foreground(t.Primary()).Bold(true)
valueStyle = valueStyle.
Foreground(t.Primary()).
Background(t.Background()).
Bold(true)
// Add dropdown indicators
value = "β—€ " + value + " β–Ά"
}

// Create a full-width container for the field
fieldContainer := lipgloss.NewStyle().
Width(m.width-4).
Background(t.Background()).
Padding(0, 2)

fieldContent := lipgloss.JoinHorizontal(lipgloss.Left,
labelStyle.Render(label),
valueStyle.Render(value),
)

return fieldContainer.Render(fieldContent)
}

// Helper function to render a toggle field
renderToggleField := func(fieldIndex int, label, value string) string {
active := m.evalState.currentField == fieldIndex

labelStyle := lipgloss.NewStyle().
Foreground(t.TextMuted()).
Background(t.Background()).
Width(20).
Align(lipgloss.Right)

valueStyle := lipgloss.NewStyle().
Foreground(t.Text()).
Background(t.Background()).
Padding(0, 1)

if active {
labelStyle = labelStyle.Foreground(t.Primary()).Bold(true)
valueStyle = valueStyle.
Foreground(t.Primary()).
Background(t.Background()).
Bold(true)
}

// Create a full-width container for the field
fieldContainer := lipgloss.NewStyle().
Width(m.width-4).
Background(t.Background()).
Padding(0, 2)

fieldContent := lipgloss.JoinHorizontal(lipgloss.Left,
labelStyle.Render(label),
valueStyle.Render(value),
)

return fieldContainer.Render(fieldContent)
}

// Prepare field values
agent := m.evalState.AgentURL
protocol := string(m.evalState.AgentProtocol)
transport := string(m.evalState.AgentTransport)
judge := m.evalState.JudgeModel
deep := "❌"
if m.evalState.DeepTest {
Expand All @@ -93,7 +171,7 @@ func (m Model) renderNewEvaluation() string {

// Helper function to render the start button
renderStartButton := func() string {
active := m.evalState.currentField == 3
active := m.evalState.currentField == 5
var buttonText string

if m.evalSpinner.IsActive() {
Expand Down Expand Up @@ -151,9 +229,11 @@ func (m Model) renderNewEvaluation() string {

// Build the content sections
formSection := lipgloss.JoinVertical(lipgloss.Left,
renderField(0, "Agent URL:", agent),
renderField(1, "Judge LLM:", judge),
renderField(2, "Deep Test:", deep),
renderTextField(0, "Agent URL:", agent),
renderDropdownField(1, "Protocol:", protocol),
renderDropdownField(2, "Transport:", transport),
renderTextField(3, "Judge LLM:", judge),
renderToggleField(4, "Deep Test:", deep),
)

var infoLines []string
Expand All @@ -168,7 +248,7 @@ func (m Model) renderNewEvaluation() string {

buttonSection := renderStartButton()

helpText := helpStyle.Render("t Test Server ↑/↓ switch fields ←/β†’ move cursor Space toggle Enter activate Esc Back")
helpText := helpStyle.Render("t Test Server ↑/↓ switch fields ←/β†’ move cursor/cycle dropdown Space toggle Enter activate Esc Back")

// Calculate content area height (excluding title and help)
contentHeight := m.height - 6 // title(3) + help(1) + margins(2)
Expand Down
54 changes: 31 additions & 23 deletions packages/tui/internal/tui/eval_form_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (m Model) handleEvalFormInput(msg tea.KeyMsg) (Model, tea.Cmd) {
switch m.evalState.currentField {
case 0:
m.evalState.cursorPos = len([]rune(m.evalState.AgentURL))
case 1:
case 3:
m.evalState.cursorPos = len([]rune(m.evalState.JudgeModel))
default:
m.evalState.cursorPos = 0
Expand All @@ -35,13 +35,13 @@ func (m Model) handleEvalFormInput(msg tea.KeyMsg) (Model, tea.Cmd) {
return m, nil

case "down":
if m.evalState.currentField < 3 { // Now includes start button (0-3)
if m.evalState.currentField < 5 { // 0-5 fields (AgentURL, Protocol, Transport, JudgeModel, DeepTest, StartButton)
m.evalState.currentField++
// Set cursor to end of field content when switching fields
switch m.evalState.currentField {
case 0:
m.evalState.cursorPos = len([]rune(m.evalState.AgentURL))
case 1:
case 3:
m.evalState.cursorPos = len([]rune(m.evalState.JudgeModel))
default:
m.evalState.cursorPos = 0
Expand All @@ -50,60 +50,68 @@ func (m Model) handleEvalFormInput(msg tea.KeyMsg) (Model, tea.Cmd) {
return m, nil

case "left":
if m.evalState.currentField <= 1 && m.evalState.cursorPos > 0 { // Text fields 0-2
m.evalState.cursorPos--
switch m.evalState.currentField {
case 0, 3: // Text fields: AgentURL, JudgeModel
if m.evalState.cursorPos > 0 {
m.evalState.cursorPos--
}
case 1: // Protocol dropdown
m.evalState.cycleProtocol(true) // cycle backwards
case 2: // Transport dropdown
m.evalState.cycleTransport(true) // cycle backwards
}
return m, nil

case "right":
if m.evalState.currentField <= 1 { // Text fields 0-1
// Get current field length to limit cursor
var fieldLen int
switch m.evalState.currentField {
case 0:
fieldLen = len(m.evalState.AgentURL)
case 1:
fieldLen = len(m.evalState.JudgeModel)
case 2:
fieldLen = len(fmt.Sprintf("%d", m.evalState.ParallelRuns))
switch m.evalState.currentField {
case 0: // AgentURL text field
fieldLen := len(m.evalState.AgentURL)
if m.evalState.cursorPos < fieldLen {
m.evalState.cursorPos++
}
case 1: // Protocol dropdown
m.evalState.cycleProtocol(false) // cycle forwards
case 2: // Transport dropdown
m.evalState.cycleTransport(false) // cycle forwards
case 3: // JudgeModel text field
fieldLen := len(m.evalState.JudgeModel)
if m.evalState.cursorPos < fieldLen {
m.evalState.cursorPos++
}
}
return m, nil

case "space":
if m.evalState.currentField == 2 { // DeepTest field is now index 2
if m.evalState.currentField == 4 { // DeepTest field is now index 2
m.evalState.DeepTest = !m.evalState.DeepTest
return m, nil
}
Comment on lines 84 to 88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Update comment for DeepTest index.

Keep comment consistent with actual index 4.

- if m.evalState.currentField == 4 { // DeepTest field is now index 2
+ if m.evalState.currentField == 4 { // DeepTest field index
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case "space":
if m.evalState.currentField == 2 { // DeepTest field is now index 2
if m.evalState.currentField == 4 { // DeepTest field is now index 2
m.evalState.DeepTest = !m.evalState.DeepTest
return m, nil
}
case "space":
if m.evalState.currentField == 4 { // DeepTest field index
m.evalState.DeepTest = !m.evalState.DeepTest
return m, nil
}
πŸ€– Prompt for AI Agents
In packages/tui/internal/tui/eval_form_controller.go around lines 84 to 88, the
inline comment stating "DeepTest field is now index 2" is incorrect and should
reflect the actual index 4; update the comment to "DeepTest field is now index
4" (or remove the outdated comment) so it matches the code and avoids confusion.


case "tab":
// Open LLM config dialog when on Judge Model field
if m.evalState.currentField == 1 { // JudgeModel field
if m.evalState.currentField == 3 { // JudgeModel field
llmDialog := components.NewLLMConfigDialog(m.config.APIKeys, m.config.SelectedProvider, m.config.SelectedModel)
m.llmDialog = &llmDialog
return m, nil
}

case "backspace":
// Handle backspace for text fields
if m.evalState.currentField <= 1 && m.evalState.cursorPos > 0 {
if m.evalState.currentField > 0 {
switch m.evalState.currentField {
case 0: // AgentURL
runes := []rune(m.evalState.AgentURL)
if m.evalState.cursorPos <= len(runes) {
m.evalState.AgentURL = string(runes[:m.evalState.cursorPos-1]) + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos--
}
case 1: // JudgeModel
case 3: // JudgeModel
runes := []rune(m.evalState.JudgeModel)
if m.evalState.cursorPos <= len(runes) {
m.evalState.JudgeModel = string(runes[:m.evalState.cursorPos-1]) + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos--
}
case 2: // ParallelRuns (special handling for numbers)
case 6: // ParallelRuns (special handling for numbers)
if m.evalState.ParallelRuns >= 10 {
m.evalState.ParallelRuns /= 10
m.evalState.cursorPos--
Expand All @@ -118,17 +126,17 @@ func (m Model) handleEvalFormInput(msg tea.KeyMsg) (Model, tea.Cmd) {
default:
// insert character into text fields
s := msg.String()
if len(s) == 1 && m.evalState.currentField <= 2 { // Text fields 0-2
if len(s) == 1 {
switch m.evalState.currentField {
case 0: // AgentURL
runes := []rune(m.evalState.AgentURL)
m.evalState.AgentURL = string(runes[:m.evalState.cursorPos]) + s + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos++
case 1: // JudgeModel
case 3: // JudgeModel
runes := []rune(m.evalState.JudgeModel)
m.evalState.JudgeModel = string(runes[:m.evalState.cursorPos]) + s + string(runes[m.evalState.cursorPos:])
m.evalState.cursorPos++
case 2: // ParallelRuns (numeric only)
case 6: // ParallelRuns (numeric only)
if s[0] >= '0' && s[0] <= '9' {
numStr := fmt.Sprintf("%d", m.evalState.ParallelRuns)
runes := []rune(numStr)
Expand Down
Loading
Loading