mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-30 17:56:44 +00:00
Context Window Warning (#152)
* context window warning & compact command * auto compact * fix permissions * update readme * fix 3.5 context window * small update * remove unused interface * remove unused msg
This commit is contained in:
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
type StatusCmp interface {
|
||||
tea.Model
|
||||
SetHelpWidgetMsg(string)
|
||||
}
|
||||
|
||||
type statusCmp struct {
|
||||
@@ -74,11 +73,9 @@ func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var helpWidget = ""
|
||||
|
||||
// getHelpWidget returns the help widget with current theme colors
|
||||
func getHelpWidget(helpText string) string {
|
||||
func getHelpWidget() string {
|
||||
t := theme.CurrentTheme()
|
||||
if helpText == "" {
|
||||
helpText = "ctrl+? help"
|
||||
}
|
||||
helpText := "ctrl+? help"
|
||||
|
||||
return styles.Padded().
|
||||
Background(t.TextMuted()).
|
||||
@@ -87,7 +84,7 @@ func getHelpWidget(helpText string) string {
|
||||
Render(helpText)
|
||||
}
|
||||
|
||||
func formatTokensAndCost(tokens int64, cost float64) string {
|
||||
func formatTokensAndCost(tokens, contextWindow int64, cost float64) string {
|
||||
// Format tokens in human-readable format (e.g., 110K, 1.2M)
|
||||
var formattedTokens string
|
||||
switch {
|
||||
@@ -110,32 +107,48 @@ func formatTokensAndCost(tokens int64, cost float64) string {
|
||||
// Format cost with $ symbol and 2 decimal places
|
||||
formattedCost := fmt.Sprintf("$%.2f", cost)
|
||||
|
||||
return fmt.Sprintf("Tokens: %s, Cost: %s", formattedTokens, formattedCost)
|
||||
percentage := (float64(tokens) / float64(contextWindow)) * 100
|
||||
if percentage > 80 {
|
||||
// add the warning icon and percentage
|
||||
formattedTokens = fmt.Sprintf("%s(%d%%)", styles.WarningIcon, int(percentage))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Context: %s, Cost: %s", formattedTokens, formattedCost)
|
||||
}
|
||||
|
||||
func (m statusCmp) View() string {
|
||||
t := theme.CurrentTheme()
|
||||
modelID := config.Get().Agents[config.AgentCoder].Model
|
||||
model := models.SupportedModels[modelID]
|
||||
|
||||
// Initialize the help widget
|
||||
status := getHelpWidget("")
|
||||
status := getHelpWidget()
|
||||
|
||||
tokenInfoWidth := 0
|
||||
if m.session.ID != "" {
|
||||
tokens := formatTokensAndCost(m.session.PromptTokens+m.session.CompletionTokens, m.session.Cost)
|
||||
totalTokens := m.session.PromptTokens + m.session.CompletionTokens
|
||||
tokens := formatTokensAndCost(totalTokens, model.ContextWindow, m.session.Cost)
|
||||
tokensStyle := styles.Padded().
|
||||
Background(t.Text()).
|
||||
Foreground(t.BackgroundSecondary()).
|
||||
Render(tokens)
|
||||
status += tokensStyle
|
||||
Foreground(t.BackgroundSecondary())
|
||||
percentage := (float64(totalTokens) / float64(model.ContextWindow)) * 100
|
||||
if percentage > 80 {
|
||||
tokensStyle = tokensStyle.Background(t.Warning())
|
||||
}
|
||||
tokenInfoWidth = lipgloss.Width(tokens) + 2
|
||||
status += tokensStyle.Render(tokens)
|
||||
}
|
||||
|
||||
diagnostics := styles.Padded().
|
||||
Background(t.BackgroundDarker()).
|
||||
Render(m.projectDiagnostics())
|
||||
|
||||
availableWidht := max(0, m.width-lipgloss.Width(helpWidget)-lipgloss.Width(m.model())-lipgloss.Width(diagnostics)-tokenInfoWidth)
|
||||
|
||||
if m.info.Msg != "" {
|
||||
infoStyle := styles.Padded().
|
||||
Foreground(t.Background()).
|
||||
Width(m.availableFooterMsgWidth(diagnostics))
|
||||
Width(availableWidht)
|
||||
|
||||
switch m.info.Type {
|
||||
case util.InfoTypeInfo:
|
||||
@@ -146,18 +159,18 @@ func (m statusCmp) View() string {
|
||||
infoStyle = infoStyle.Background(t.Error())
|
||||
}
|
||||
|
||||
infoWidth := availableWidht - 10
|
||||
// Truncate message if it's longer than available width
|
||||
msg := m.info.Msg
|
||||
availWidth := m.availableFooterMsgWidth(diagnostics) - 10
|
||||
if len(msg) > availWidth && availWidth > 0 {
|
||||
msg = msg[:availWidth] + "..."
|
||||
if len(msg) > infoWidth && infoWidth > 0 {
|
||||
msg = msg[:infoWidth] + "..."
|
||||
}
|
||||
status += infoStyle.Render(msg)
|
||||
} else {
|
||||
status += styles.Padded().
|
||||
Foreground(t.Text()).
|
||||
Background(t.BackgroundSecondary()).
|
||||
Width(m.availableFooterMsgWidth(diagnostics)).
|
||||
Width(availableWidht).
|
||||
Render("")
|
||||
}
|
||||
|
||||
@@ -245,12 +258,10 @@ func (m *statusCmp) projectDiagnostics() string {
|
||||
return strings.Join(diagnostics, " ")
|
||||
}
|
||||
|
||||
func (m statusCmp) availableFooterMsgWidth(diagnostics string) int {
|
||||
tokens := ""
|
||||
func (m statusCmp) availableFooterMsgWidth(diagnostics, tokenInfo string) int {
|
||||
tokensWidth := 0
|
||||
if m.session.ID != "" {
|
||||
tokens = formatTokensAndCost(m.session.PromptTokens+m.session.CompletionTokens, m.session.Cost)
|
||||
tokensWidth = lipgloss.Width(tokens) + 2
|
||||
tokensWidth = lipgloss.Width(tokenInfo) + 2
|
||||
}
|
||||
return max(0, m.width-lipgloss.Width(helpWidget)-lipgloss.Width(m.model())-lipgloss.Width(diagnostics)-tokensWidth)
|
||||
}
|
||||
@@ -272,14 +283,8 @@ func (m statusCmp) model() string {
|
||||
Render(model.Name)
|
||||
}
|
||||
|
||||
func (m statusCmp) SetHelpWidgetMsg(s string) {
|
||||
// Update the help widget text using the getHelpWidget function
|
||||
helpWidget = getHelpWidget(s)
|
||||
}
|
||||
|
||||
func NewStatusCmp(lspClients map[string]*lsp.Client) StatusCmp {
|
||||
// Initialize the help widget with default text
|
||||
helpWidget = getHelpWidget("")
|
||||
helpWidget = getHelpWidget()
|
||||
|
||||
return &statusCmp{
|
||||
messageTTL: 10 * time.Second,
|
||||
|
||||
@@ -302,11 +302,8 @@ func (f *filepickerCmp) View() string {
|
||||
}
|
||||
if file.IsDir() {
|
||||
filename = filename + "/"
|
||||
} else if isExtSupported(file.Name()) {
|
||||
filename = filename
|
||||
} else {
|
||||
filename = filename
|
||||
}
|
||||
// No need to reassign filename if it's not changing
|
||||
|
||||
files = append(files, itemStyle.Padding(0, 1).Render(filename))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package dialog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
@@ -13,7 +15,6 @@ import (
|
||||
"github.com/opencode-ai/opencode/internal/tui/styles"
|
||||
"github.com/opencode-ai/opencode/internal/tui/theme"
|
||||
"github.com/opencode-ai/opencode/internal/tui/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PermissionAction string
|
||||
@@ -150,7 +151,7 @@ func (p *permissionDialogCmp) selectCurrentOption() tea.Cmd {
|
||||
func (p *permissionDialogCmp) renderButtons() string {
|
||||
t := theme.CurrentTheme()
|
||||
baseStyle := styles.BaseStyle()
|
||||
|
||||
|
||||
allowStyle := baseStyle
|
||||
allowSessionStyle := baseStyle
|
||||
denyStyle := baseStyle
|
||||
@@ -196,7 +197,7 @@ func (p *permissionDialogCmp) renderButtons() string {
|
||||
func (p *permissionDialogCmp) renderHeader() string {
|
||||
t := theme.CurrentTheme()
|
||||
baseStyle := styles.BaseStyle()
|
||||
|
||||
|
||||
toolKey := baseStyle.Foreground(t.TextMuted()).Bold(true).Render("Tool")
|
||||
toolValue := baseStyle.
|
||||
Foreground(t.Text()).
|
||||
@@ -229,9 +230,36 @@ func (p *permissionDialogCmp) renderHeader() string {
|
||||
case tools.BashToolName:
|
||||
headerParts = append(headerParts, baseStyle.Foreground(t.TextMuted()).Width(p.width).Bold(true).Render("Command"))
|
||||
case tools.EditToolName:
|
||||
headerParts = append(headerParts, baseStyle.Foreground(t.TextMuted()).Width(p.width).Bold(true).Render("Diff"))
|
||||
params := p.permission.Params.(tools.EditPermissionsParams)
|
||||
fileKey := baseStyle.Foreground(t.TextMuted()).Bold(true).Render("File")
|
||||
filePath := baseStyle.
|
||||
Foreground(t.Text()).
|
||||
Width(p.width - lipgloss.Width(fileKey)).
|
||||
Render(fmt.Sprintf(": %s", params.FilePath))
|
||||
headerParts = append(headerParts,
|
||||
lipgloss.JoinHorizontal(
|
||||
lipgloss.Left,
|
||||
fileKey,
|
||||
filePath,
|
||||
),
|
||||
baseStyle.Render(strings.Repeat(" ", p.width)),
|
||||
)
|
||||
|
||||
case tools.WriteToolName:
|
||||
headerParts = append(headerParts, baseStyle.Foreground(t.TextMuted()).Width(p.width).Bold(true).Render("Diff"))
|
||||
params := p.permission.Params.(tools.WritePermissionsParams)
|
||||
fileKey := baseStyle.Foreground(t.TextMuted()).Bold(true).Render("File")
|
||||
filePath := baseStyle.
|
||||
Foreground(t.Text()).
|
||||
Width(p.width - lipgloss.Width(fileKey)).
|
||||
Render(fmt.Sprintf(": %s", params.FilePath))
|
||||
headerParts = append(headerParts,
|
||||
lipgloss.JoinHorizontal(
|
||||
lipgloss.Left,
|
||||
fileKey,
|
||||
filePath,
|
||||
),
|
||||
baseStyle.Render(strings.Repeat(" ", p.width)),
|
||||
)
|
||||
case tools.FetchToolName:
|
||||
headerParts = append(headerParts, baseStyle.Foreground(t.TextMuted()).Width(p.width).Bold(true).Render("URL"))
|
||||
}
|
||||
@@ -242,13 +270,13 @@ func (p *permissionDialogCmp) renderHeader() string {
|
||||
func (p *permissionDialogCmp) renderBashContent() string {
|
||||
t := theme.CurrentTheme()
|
||||
baseStyle := styles.BaseStyle()
|
||||
|
||||
|
||||
if pr, ok := p.permission.Params.(tools.BashPermissionsParams); ok {
|
||||
content := fmt.Sprintf("```bash\n%s\n```", pr.Command)
|
||||
|
||||
// Use the cache for markdown rendering
|
||||
renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
|
||||
r := styles.GetMarkdownRenderer(p.width-10)
|
||||
r := styles.GetMarkdownRenderer(p.width - 10)
|
||||
s, err := r.Render(content)
|
||||
return styles.ForceReplaceBackgroundWithLipgloss(s, t.Background()), err
|
||||
})
|
||||
@@ -302,13 +330,13 @@ func (p *permissionDialogCmp) renderWriteContent() string {
|
||||
func (p *permissionDialogCmp) renderFetchContent() string {
|
||||
t := theme.CurrentTheme()
|
||||
baseStyle := styles.BaseStyle()
|
||||
|
||||
|
||||
if pr, ok := p.permission.Params.(tools.FetchPermissionsParams); ok {
|
||||
content := fmt.Sprintf("```bash\n%s\n```", pr.URL)
|
||||
|
||||
// Use the cache for markdown rendering
|
||||
renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
|
||||
r := styles.GetMarkdownRenderer(p.width-10)
|
||||
r := styles.GetMarkdownRenderer(p.width - 10)
|
||||
s, err := r.Render(content)
|
||||
return styles.ForceReplaceBackgroundWithLipgloss(s, t.Background()), err
|
||||
})
|
||||
@@ -325,12 +353,12 @@ func (p *permissionDialogCmp) renderFetchContent() string {
|
||||
func (p *permissionDialogCmp) renderDefaultContent() string {
|
||||
t := theme.CurrentTheme()
|
||||
baseStyle := styles.BaseStyle()
|
||||
|
||||
|
||||
content := p.permission.Description
|
||||
|
||||
// Use the cache for markdown rendering
|
||||
renderedContent := p.GetOrSetMarkdown(p.permission.ID, func() (string, error) {
|
||||
r := styles.GetMarkdownRenderer(p.width-10)
|
||||
r := styles.GetMarkdownRenderer(p.width - 10)
|
||||
s, err := r.Render(content)
|
||||
return styles.ForceReplaceBackgroundWithLipgloss(s, t.Background()), err
|
||||
})
|
||||
@@ -358,7 +386,7 @@ func (p *permissionDialogCmp) styleViewport() string {
|
||||
func (p *permissionDialogCmp) render() string {
|
||||
t := theme.CurrentTheme()
|
||||
baseStyle := styles.BaseStyle()
|
||||
|
||||
|
||||
title := baseStyle.
|
||||
Bold(true).
|
||||
Width(p.width - 4).
|
||||
|
||||
Reference in New Issue
Block a user