Skip to content

feat: improve config provider TUI interaction#213

Open
5wxwxwxw5 wants to merge 6 commits into
alibaba:mainfrom
5wxwxwxw5:feat/improve-config-provider-ui
Open

feat: improve config provider TUI interaction#213
5wxwxwxw5 wants to merge 6 commits into
alibaba:mainfrom
5wxwxwxw5:feat/improve-config-provider-ui

Conversation

@5wxwxwxw5

Copy link
Copy Markdown
Contributor

Description

  • Support wrap-around navigation in provider, custom provider, and model lists (up at first item jumps to last, down at last item jumps to first)
  • Add d key to delete custom models in model selection step with confirmation prompt
  • Add protocol selection (anthropic/openai) to manual configuration flow and respect UseAnthropic setting in saved config
  • Preserve manual protocol selection when re-entering the form
  • Fix manual form input fields losing focus after returning from next step
  • Use safe slice removal in removeFromSlice to avoid backing array corruption

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring (no functional changes)
  • Documentation update
  • CI / Build / Tooling

How Has This Been Tested?

  • make test passes locally
  • Manual testing (describe below)
image image image image image

Checklist

  • My code follows the project's coding style (go fmt, go vet)
  • I have performed a self-review of my code
  • I have added tests that prove my fix is effective or my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly (if applicable)
  • I have signed the CLA

Related Issues

- Support wrap-around navigation in provider, custom provider, and model lists
  (up at first item jumps to last, down at last item jumps to first)
- Add d key to delete custom models in model selection step with confirmation prompt
- Add protocol selection (anthropic/openai) to manual configuration flow
  and respect UseAnthropic setting in saved config
- Preserve manual protocol selection when re-entering the form
- Fix manual form input fields losing focus after returning from next step
- Use safe slice removal in removeFromSlice to avoid backing array corruption

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 OpenCodeReview found 4 issue(s) in this PR.

  • ✅ 4 posted as inline comment(s)
  • 📝 0 posted as summary

Comment on lines +761 to 765
m.manualStep = manualStepProtocol
return m, nil
case manualStepProtocol:
m.manualStep = manualStepModel
return m, m.manualModelInput.Focus()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: When transitioning from manualStepURL to manualStepProtocol, the URL input is never blurred. The old code called m.manualURLInput.Blur() before advancing, but this was removed. Since the protocol step has no input widget and focusManualStep() returns nil for it, the URL text input retains focus state. This can cause visual artifacts (e.g., cursor still blinking in the URL field) while the protocol selector is displayed.

Similarly, the transition from manualStepModel to manualStepAuthToken also lost its m.manualModelInput.Blur() call.

You should blur the current step's input before advancing to the next step.

Suggestion:

Suggested change
m.manualStep = manualStepProtocol
return m, nil
case manualStepProtocol:
m.manualStep = manualStepModel
return m, m.manualModelInput.Focus()
m.manualURLInput.Blur()
m.manualStep = manualStepProtocol
return m, nil
case manualStepProtocol:
m.manualStep = manualStepModel
return m, m.manualModelInput.Focus()

Comment on lines 770 to 771
m.manualStep = manualStepAuthToken
return m, m.manualTokenInput.Focus()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same issue: the model input should be blurred before transitioning to the auth token step, otherwise the model input may retain visual focus artifacts.

Suggestion:

Suggested change
m.manualStep = manualStepAuthToken
return m, m.manualTokenInput.Focus()
m.manualModelInput.Blur()
m.manualStep = manualStepAuthToken
return m, m.manualTokenInput.Focus()

Comment thread cmd/opencodereview/provider_tui.go Outdated
Comment on lines 882 to 884
} else {
m.officialIdx = len(m.providers) - 1
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Potential issue: If len(m.providers) is 0 (e.g., llm.ListProviders() returns empty), then len(m.providers) - 1 evaluates to -1, setting officialIdx to an invalid negative index. While unlikely in practice, adding a guard would make this more robust. The same applies to customListCount() and modelCount() which always return at least 1 due to the +1 sentinel item, so those are safe.

Suggestion:

Suggested change
} else {
m.officialIdx = len(m.providers) - 1
}
} else if len(m.providers) > 0 {
m.officialIdx = len(m.providers) - 1
}

Comment on lines +1285 to +1295
for i, proto := range cpProtocols {
cur := " "
if i == m.manualProtocolIdx {
cur = " " + tuiCursorStyle.Render(tuiCursor) + " "
}
if i == m.manualProtocolIdx {
s.WriteString(cur + tuiSelectedItemStyle.Render(proto) + "\n")
} else {
s.WriteString(cur + tuiItemStyle.Render(proto) + "\n")
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Minor readability issue: The condition i == m.manualProtocolIdx is checked twice in succession — once to set the cursor prefix and again to choose the rendering style. These can be consolidated into a single if/else block for clarity.

Suggestion:

Suggested change
for i, proto := range cpProtocols {
cur := " "
if i == m.manualProtocolIdx {
cur = " " + tuiCursorStyle.Render(tuiCursor) + " "
}
if i == m.manualProtocolIdx {
s.WriteString(cur + tuiSelectedItemStyle.Render(proto) + "\n")
} else {
s.WriteString(cur + tuiItemStyle.Render(proto) + "\n")
}
}
for i, proto := range cpProtocols {
if i == m.manualProtocolIdx {
cur := " " + tuiCursorStyle.Render(tuiCursor) + " "
s.WriteString(cur + tuiSelectedItemStyle.Render(proto) + "\n")
} else {
s.WriteString(" " + tuiItemStyle.Render(proto) + "\n")
}
}

- Restore Blur() calls in handleManualFormEnter to prevent visual focus artifacts
  when transitioning between manual form steps
- Add empty-providers guard in handleUp/handleDown to avoid negative index
- Consolidate redundant protocolIdx checks into single if/else in viewManualTab
  and viewCustomProviderForm for clarity
…ting

- Track deleted custom models in TUI and apply them on exit
  (both confirm and cancel paths) to keep config.json in sync
- Clear custom provider's active Model field when that model is deleted,
  preventing stale "model" reference in the provider list label
- Add masked display for manual config Auth Token to allow easy re-entry,
  matching the official provider flow (any key clears and starts fresh)
Update TestProviderTUI_ManualFormPrefilledValues,
TestProviderTUI_ManualFormEscRestoresOriginalValues, and
TestProviderTUI_ManualFormPrefilledWhenProviderSet to assert
the new masked display state (manualTokenMasked + manualTokenOriginal)
instead of the raw token value.
Custom provider interactions:
- Simplify create/edit form to Name → Protocol → URL → API Key → Auth Header
- After create or edit save, jump straight into the model list for that provider
- Support comma-separated model names in the custom model input
- Show masked API key in edit form; empty Auth Header defaults to (Authorization)
- Populate edit form from existingCfg to avoid stale list data after in-session saves
Model selection and highlight:
- Green highlight follows the persisted active model, not the cursor position
- Prefer provider entry.model over global cfg.model when resolving active model
- Deleting a non-active model keeps the current green highlight unchanged
Delete and navigation:
- Delete custom providers (d) and custom models (d) with confirmation prompts
- Persist create/edit/model-select/add/delete changes to disk during the session
- Fix custom provider deletion not surviving Esc exe-entry
Tests:
- Add coverage for model highlight, delete-model behavior, and create→model-list flow
- Update manual/custom form tests for masked token and session save behavior
@lizhengfeng101

Copy link
Copy Markdown
Collaborator

Code Review Findings

Hi! I spotted a few things while reviewing — please take a look and let me know if you agree they're worth addressing.

1. Indentation / brace alignment in updateDeleteModelConfirm (potential correctness risk)

The indentation in this function is inconsistent, making it hard to verify the brace matching is correct:

			m.savedInSession = true
		if len(m.models()) > 0 {          // ← indent level drops unexpectedly
			if remaining := len(m.models()); m.modelIdx >= remaining {
				...
			}
		}
	}
		m.confirmingDeleteModel = false    // ← indent inconsistent with surrounding code
		return m, nil

Go uses braces (not indentation) for scoping, so this may compile fine — but the formatting makes it very difficult to audit correctness. Could you run gofmt on this function and verify the logic matches your intent?

2. Misplaced doc comment above cloneProviderEntry

The comment block describing applyEditCustomProviderSave sits directly above cloneProviderEntry:

// applyEditCustomProviderSave persists the edited provider to disk. Returns
// true if a hard validation error stopped the save ...
func cloneProviderEntry(v ProviderEntry) ProviderEntry {

Looks like the comment should be moved to the actual applyEditCustomProviderSave method.

3. removeFromSlice appears to be dead code

The PR description mentions "Use safe slice removal in removeFromSlice to avoid backing array corruption," but removeFromSlice is defined and never called anywhere — model deletion uses removeModels() instead. Should this function be removed, or is there a missing call site?

4. Redundant provider deletion (double save)

In updateDeleteConfirm, a deleted provider is both saved to disk immediately (via saveConfig) and appended to deletedProviders. Then in the confirmed path of runConfigProvider, applyProviderDeletions attempts to delete and save again. Since delete(map, key) is idempotent this isn't a bug, but the double-write adds unnecessary complexity. Consider either skipping the deletedProviders append when saving in-session, or guarding the post-TUI path.

5. m.models() called multiple times in updateDeleteModelConfirm

m.models() (which includes sorting) is called 3 times in sequence:

if len(m.models()) > 0 {
    if remaining := len(m.models()); m.modelIdx >= remaining {

Caching the result in a local variable would be cleaner and avoids redundant work:

updated := m.models()
if len(updated) > 0 && m.modelIdx >= len(updated) {
    m.modelIdx = len(updated) - 1
}

- Remove dead helper removeFromSlice (superseded by removeModels)
- Move misplaced doc comment to applyEditCustomProviderSave
- Drop redundant applyProviderDeletions post-TUI call so provider
  deletions rely solely on the in-session save
- Fix brace/indent drift in updateDeleteModelConfirm and cache the
  model list instead of recomputing m.models() twice
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants