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 cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ func runNormalMode(ctx context.Context) error {
isOAuth = true
}
}

usageTracker := ui.NewUsageTracker(modelInfo, provider, 80, isOAuth) // Will be updated with actual width
cli.SetUsageTracker(usageTracker)
}
Expand Down
18 changes: 9 additions & 9 deletions internal/models/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,30 @@ const (
// resolveModelAlias resolves model aliases to their full names using the registry
func resolveModelAlias(provider, modelName string) string {
registry := GetGlobalRegistry()

// Common alias patterns for Anthropic models - using Claude 4 as the latest/default
aliasMap := map[string]string{
// Claude 4 models (latest and most capable)
"claude-opus-latest": "claude-opus-4-20250514",
"claude-sonnet-latest": "claude-sonnet-4-20250514",
"claude-4-opus-latest": "claude-opus-4-20250514",
"claude-4-sonnet-latest": "claude-sonnet-4-20250514",
"claude-opus-latest": "claude-opus-4-20250514",
"claude-sonnet-latest": "claude-sonnet-4-20250514",
"claude-4-opus-latest": "claude-opus-4-20250514",
"claude-4-sonnet-latest": "claude-sonnet-4-20250514",

// Claude 3.x models for backward compatibility
"claude-3-5-haiku-latest": "claude-3-5-haiku-20241022",
"claude-3-5-sonnet-latest": "claude-3-5-sonnet-20241022",
"claude-3-5-sonnet-latest": "claude-3-5-sonnet-20241022",
"claude-3-7-sonnet-latest": "claude-3-7-sonnet-20250219",
"claude-3-opus-latest": "claude-3-opus-20240229",
}

// Check if it's a known alias
if resolved, exists := aliasMap[modelName]; exists {
// Verify the resolved model exists in the registry
if _, err := registry.ValidateModel(provider, resolved); err == nil {
return resolved
}
}

// Return original if no alias found or resolved model doesn't exist
return modelName
}
Expand Down
178 changes: 178 additions & 0 deletions internal/ui/block_renderer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package ui

import (
"github.com/charmbracelet/lipgloss"
)

// blockRenderer handles rendering of content blocks with configurable options
type blockRenderer struct {
align *lipgloss.Position
borderColor *lipgloss.AdaptiveColor
fullWidth bool
paddingTop int
paddingBottom int
paddingLeft int
paddingRight int
marginTop int
marginBottom int
width int
}

// renderingOption configures block rendering
type renderingOption func(*blockRenderer)

// WithFullWidth makes the block take full available width
func WithFullWidth() renderingOption {
return func(c *blockRenderer) {
c.fullWidth = true
}
}

// WithAlign sets the horizontal alignment of the block
func WithAlign(align lipgloss.Position) renderingOption {
return func(c *blockRenderer) {
c.align = &align
}
}

// WithBorderColor sets the border color
func WithBorderColor(color lipgloss.AdaptiveColor) renderingOption {
return func(c *blockRenderer) {
c.borderColor = &color
}
}

// WithMarginTop sets the top margin
func WithMarginTop(margin int) renderingOption {
return func(c *blockRenderer) {
c.marginTop = margin
}
}

// WithMarginBottom sets the bottom margin
func WithMarginBottom(margin int) renderingOption {
return func(c *blockRenderer) {
c.marginBottom = margin
}
}

// WithPaddingLeft sets the left padding
func WithPaddingLeft(padding int) renderingOption {
return func(c *blockRenderer) {
c.paddingLeft = padding
}
}

// WithPaddingRight sets the right padding
func WithPaddingRight(padding int) renderingOption {
return func(c *blockRenderer) {
c.paddingRight = padding
}
}

// WithPaddingTop sets the top padding
func WithPaddingTop(padding int) renderingOption {
return func(c *blockRenderer) {
c.paddingTop = padding
}
}

// WithPaddingBottom sets the bottom padding
func WithPaddingBottom(padding int) renderingOption {
return func(c *blockRenderer) {
c.paddingBottom = padding
}
}

// WithWidth sets a specific width for the block
func WithWidth(width int) renderingOption {
return func(c *blockRenderer) {
c.width = width
}
}

// renderContentBlock renders content with configurable styling options
func renderContentBlock(content string, containerWidth int, options ...renderingOption) string {
renderer := &blockRenderer{
fullWidth: false,
paddingTop: 1,
paddingBottom: 1,
paddingLeft: 2,
paddingRight: 2,
width: containerWidth,
}

for _, option := range options {
option(renderer)
}

theme := GetTheme()
style := lipgloss.NewStyle().
PaddingTop(renderer.paddingTop).
PaddingBottom(renderer.paddingBottom).
PaddingLeft(renderer.paddingLeft).
PaddingRight(renderer.paddingRight).
Foreground(theme.Text).
BorderStyle(lipgloss.ThickBorder())

align := lipgloss.Left
if renderer.align != nil {
align = *renderer.align
}

// Default to transparent/no border color
borderColor := lipgloss.AdaptiveColor{Light: "", Dark: ""}
if renderer.borderColor != nil {
borderColor = *renderer.borderColor
}

// Very muted color for the opposite border
mutedOppositeBorder := lipgloss.AdaptiveColor{
Light: "#F3F4F6", // Very light gray, barely visible
Dark: "#1F2937", // Very dark gray, barely visible
}

switch align {
case lipgloss.Left:
style = style.
BorderLeft(true).
BorderRight(true).
AlignHorizontal(align).
BorderLeftForeground(borderColor).
BorderRightForeground(mutedOppositeBorder)
case lipgloss.Right:
style = style.
BorderRight(true).
BorderLeft(true).
AlignHorizontal(align).
BorderRightForeground(borderColor).
BorderLeftForeground(mutedOppositeBorder)
}

if renderer.fullWidth {
style = style.Width(renderer.width)
}

content = style.Render(content)

// Place the content horizontally with proper background
content = lipgloss.PlaceHorizontal(
renderer.width,
align,
content,
)

// Add margins
if renderer.marginTop > 0 {
for range renderer.marginTop {
content = "\n" + content
}
}
if renderer.marginBottom > 0 {
for range renderer.marginBottom {
content = content + "\n"
}
}

return content
}
32 changes: 24 additions & 8 deletions internal/ui/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,27 @@ func (c *CLI) GetPrompt() (string, error) {
if c.usageTracker != nil {
usageInfo := c.usageTracker.RenderUsageInfo()
if usageInfo != "" {
fmt.Print(usageInfo)
paddedUsage := lipgloss.NewStyle().
PaddingLeft(2).
Render(usageInfo)
fmt.Print(paddedUsage)
}
}

// Create a divider before the input
// Create an enhanced divider with gradient effect
theme := GetTheme()
dividerStyle := lipgloss.NewStyle().
Width(c.width).
BorderTop(true).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(mutedColor).
BorderStyle(lipgloss.Border{
Top: "━",
}).
BorderForeground(theme.Border).
MarginTop(1).
MarginBottom(1)
MarginBottom(1).
PaddingLeft(2)

// Render the divider
// Render the enhanced input section
fmt.Print(dividerStyle.Render(""))

var prompt string
Expand Down Expand Up @@ -312,7 +319,14 @@ func (c *CLI) ClearMessages() {
func (c *CLI) displayContainer() {
// Clear screen and display messages
fmt.Print("\033[2J\033[H") // Clear screen and move cursor to top
fmt.Print(c.messageContainer.Render())

// Add left padding to the entire container
content := c.messageContainer.Render()
paddedContent := lipgloss.NewStyle().
PaddingLeft(2).
Render(content)

fmt.Print(paddedContent)
}

// UpdateUsage updates the usage tracker with token counts and costs
Expand Down Expand Up @@ -393,7 +407,9 @@ func (c *CLI) updateSize() {
return
}

c.width = width
// Add left and right padding (4 characters total: 2 on each side)
paddingTotal := 4
c.width = width - paddingTotal
c.height = height

// Update renderers if they exist
Expand Down
Loading