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
2 changes: 1 addition & 1 deletion .github/workflows/rogue.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,5 @@ jobs:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
with:
evaluated_agent_url: "http://localhost:10001"
judge_llm: "openai/gpt-4.1-mini"
judge_llm: "openai/gpt-4.1"
workdir: "./examples/tshirt_store_agent/.rogue"
2 changes: 1 addition & 1 deletion examples/js/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
}
}
2 changes: 1 addition & 1 deletion examples/js/langgraph-js-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
}
}
2 changes: 1 addition & 1 deletion examples/js/vercel-ai-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@
"uuid": "^11.1.0",
"zod": "^3.24.1"
}
}
}
17 changes: 9 additions & 8 deletions examples/tshirt_store_agent/tshirt_store_agent_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async def _upsert_session(self, session_id: str):
if session is None:
logger.error(
f"Critical error: Session is None even after "
f"create_session for session_id: {session_id}"
f"create_session for session_id: {session_id}",
)
raise RuntimeError(
f"Failed to get or create session: {session_id}",
Expand All @@ -151,15 +151,16 @@ def convert_a2a_part_to_genai(part: Part) -> types.Part:
if isinstance(part.file, FileWithUri):
return types.Part(
file_data=types.FileData(
file_uri=part.file.uri, mime_type=part.file.mimeType
)
file_uri=part.file.uri,
mime_type=part.file.mimeType,
),
)
if isinstance(part.file, FileWithBytes):
return types.Part(
inline_data=types.Blob(
data=base64.b64decode(part.file.bytes),
mime_type=part.file.mimeType,
)
),
)
raise ValueError(f"Unsupported file type: {type(part.file)}")
raise ValueError(f"Unsupported part type: {type(part)}")
Expand All @@ -185,8 +186,8 @@ def convert_genai_part_to_a2a(part: types.Part) -> Part:
file=FileWithUri(
uri=part.file_data.file_uri or "",
mimeType=part.file_data.mime_type,
)
)
),
),
)
if part.inline_data:
return Part(
Expand All @@ -196,7 +197,7 @@ def convert_genai_part_to_a2a(part: types.Part) -> Part:
part.inline_data.data, # type: ignore
).decode(),
mimeType=part.inline_data.mime_type,
)
)
),
),
)
raise ValueError(f"Unsupported part type: {part}")
2 changes: 1 addition & 1 deletion packages/tui/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ require (
)

require (
github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a // indirect
github.com/charmbracelet/x/input v0.3.7 // indirect
github.com/charmbracelet/x/windows v0.2.1 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
Expand All @@ -23,6 +22,7 @@ require (

require (
github.com/alecthomas/chroma/v2 v2.20.0
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1
github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.14-0.20250505150409-97991a1f17d1 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions packages/tui/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 h1:swACzss0FjnyPz1enfX56GKkLiuKg5FlyVmOLIlU2kE=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4 h1:UgUuKKvBwgqm2ZEL+sKv/OLeavrUb4gfHgdxe6oIOno=
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4/go.mod h1:0wWFRpsgF7vHsCukVZ5LAhZkiR4j875H6KEM2/tFQmA=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
Expand Down
6 changes: 3 additions & 3 deletions packages/tui/internal/components/llm_config_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ func (d LLMConfigDialog) handleEnter() (LLMConfigDialog, tea.Cmd) {
// handlePaste handles clipboard paste operation for API key input
func (d LLMConfigDialog) handlePaste() (LLMConfigDialog, tea.Cmd) {
// Get clipboard content based on the operating system
clipboardText, err := getClipboardContent()
clipboardText, err := GetClipboardContent()
if err != nil {
// If clipboard reading fails, just return without error
return d, nil
Expand All @@ -569,8 +569,8 @@ func (d LLMConfigDialog) handlePaste() (LLMConfigDialog, tea.Cmd) {
return d, nil
}

// getClipboardContent reads content from the system clipboard
func getClipboardContent() (string, error) {
// GetClipboardContent reads content from the system clipboard
func GetClipboardContent() (string, error) {
var cmd *exec.Cmd

switch runtime.GOOS {
Expand Down
2 changes: 1 addition & 1 deletion packages/tui/internal/theme/themes/vesper.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,4 @@
}
}
}


165 changes: 148 additions & 17 deletions packages/tui/internal/tui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"strings"
"time"

"github.com/charmbracelet/bubbles/v2/table"
tea "github.com/charmbracelet/bubbletea/v2"

"github.com/pelletier/go-toml/v2"
"github.com/rogue/tui/internal/components"
"github.com/rogue/tui/internal/theme"
Expand Down Expand Up @@ -84,8 +86,52 @@ func (m *Model) summaryGenerationCmd() tea.Cmd {
// Create a context with longer timeout for summary generation
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
parsedAPIKey := &m.config.QualifireAPIKey
if m.config.QualifireEnabled == false {
parsedAPIKey = nil
}
structuredSummary, err := sdk.GenerateSummary(
ctx,
m.evalState.JobID,
judgeModel,
apiKey,
parsedAPIKey,
m.evalState.DeepTest,
judgeModel,
)

if err != nil {
return SummaryGeneratedMsg{
Summary: "",
Err: err,
}
}

m.evalState.StructuredSummary = structuredSummary.Summary

overallSummary := structuredSummary.Summary.OverallSummary
keyFindings := structuredSummary.Summary.KeyFindings
parsedKeyFindings := ""
for _, finding := range keyFindings {
parsedKeyFindings += "- " + finding + "\n"
}
recommendations := structuredSummary.Summary.Recommendations
parsedRecommendations := ""
for _, recommendation := range recommendations {
parsedRecommendations += "- " + recommendation + "\n"
}

detailedBreakdown := structuredSummary.Summary.DetailedBreakdown
parsedDetailedBreakdown := ""
for _, breakdown := range detailedBreakdown {
parsedDetailedBreakdown += "- " + breakdown.Scenario + " - " + breakdown.Status + " - " + breakdown.Outcome + "\n"
}

summary := "## Overall Summary\n\n" + overallSummary +
"\n\n" + "## Key Findings\n\n" + parsedKeyFindings +
"\n\n" + "## Recommendations\n\n" + parsedRecommendations +
"\n\n" + "## Detailed Breakdown\n\n" + parsedDetailedBreakdown

summary, err := sdk.GenerateSummary(ctx, m.evalState.JobID, judgeModel, apiKey)
return SummaryGeneratedMsg{
Summary: summary,
Err: err,
Expand Down Expand Up @@ -131,20 +177,21 @@ type App struct {

// Model represents the main application state
type Model struct {
currentScreen Screen
width int
height int
input string
cursor int
evaluations []Evaluation
scenarios []Scenario
config Config
version string
commandInput components.CommandInput
dialog *components.Dialog
dialogStack []components.Dialog
llmDialog *components.LLMConfigDialog
scenarioEditor components.ScenarioEditor
currentScreen Screen
width int
height int
input string
cursor int
evaluations []Evaluation
scenarios []Scenario
config Config
version string
commandInput components.CommandInput
dialog *components.Dialog
dialogStack []components.Dialog
llmDialog *components.LLMConfigDialog
scenarioEditor components.ScenarioEditor
detailedBreakdown []table.Row

// Spinners for loading states
healthSpinner components.Spinner
Expand Down Expand Up @@ -238,7 +285,7 @@ func (a *App) Run() error {
// Initialize viewports
eventsViewport: components.NewViewport(1, 80, 20),
summaryViewport: components.NewViewport(2, 80, 20),
reportViewport: components.NewViewport(3, 80, 20),
reportViewport: components.NewViewport(3, 80, 15),
focusedViewport: 0, // Start with events viewport focused
eventsAutoScroll: true, // Start with auto-scroll enabled
}
Expand Down Expand Up @@ -285,6 +332,25 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, tea.Batch(cmds...)
}

if m.dialog != nil {
clipboardText, err := components.GetClipboardContent()
if err != nil {
// If clipboard reading fails, just return without error
return m, nil
}

// Clean the clipboard text (remove newlines and trim whitespace)
cleanText := strings.TrimSpace(strings.ReplaceAll(clipboardText, "\n", ""))

if cleanText == "" {
return m, nil
}

m.dialog.Input += cleanText
m.dialog.InputCursor = len(m.dialog.Input)
return m, nil
}
case components.SpinnerTickMsg:
// Update spinners
m.healthSpinner, cmd = m.healthSpinner.Update(msg)
Expand Down Expand Up @@ -485,6 +551,71 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Handle dialog closure
if m.dialog != nil {
switch msg.Action {
case "save_qualifire_and_report":
// Handle Qualifire API key save and report persistence
if m.dialog != nil && m.dialog.Title == "Configure Qualifire API Key" {
// Save the API key to config (allow empty to clear the key)
m.config.QualifireAPIKey = msg.Input
// Only enable integration if there's an API key
if msg.Input != "" {
m.config.QualifireEnabled = true
if m.configState != nil {
m.configState.QualifireEnabled = true
m.configState.HasChanges = true
}
}

// immediately report the summary
if m.evalState != nil && m.evalState.Completed {
parsedAPIKey := m.config.QualifireAPIKey
if m.config.QualifireEnabled == false {
parsedAPIKey = ""
}

sdk := NewRogueSDK(m.config.ServerURL)
err := sdk.ReportSummary(
context.Background(),
m.evalState.JobID,
m.evalState.StructuredSummary,
m.evalState.DeepTest,
m.evalState.JudgeModel,
parsedAPIKey,
)
if err != nil {
// Show error dialog
errorDialog := components.ShowErrorDialog(
"Report Summary Error",
fmt.Sprintf("Failed to report summary: %v", err),
)
m.dialog = &errorDialog
}

err = m.saveConfig()
if err != nil {
// Show error dialog
errorDialog := components.ShowErrorDialog(
"Configuration Error",
fmt.Sprintf("Failed to save Qualifire configuration: %v", err),
)
m.dialog = &errorDialog
return m, nil
} else {
// Show appropriate success dialog
var message string
if msg.Input != "" {
message = "Qualifire API key has been successfully saved and integration is now enabled. Your evaluation report will now be automatically persisted."
} else {
message = "Qualifire API key has been cleared and integration is now disabled."
}
successDialog := components.NewInfoDialog(
"Qualifire Configured",
message,
)
m.dialog = &successDialog
return m, nil
}
}
}
case "save_qualifire":
// Handle Qualifire API key save
if m.dialog != nil && m.dialog.Title == "Configure Qualifire API Key" {
Expand Down Expand Up @@ -543,7 +674,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
)
// Customize the buttons for this specific use case
dialog.Buttons = []components.DialogButton{
{Label: "Save", Action: "save_qualifire", Style: components.PrimaryButton},
{Label: "Save", Action: "save_qualifire_and_report", Style: components.PrimaryButton},
}
// Position cursor at end of existing key if there is one
dialog.InputCursor = len(m.config.QualifireAPIKey)
Expand Down
Loading
Loading