Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var Version = "dev"
Version is set dynamically via ldflags during build.
Functions ¶
func AutoInstall ¶
func AutoInstall() error
AutoInstall attempts to install Claude CLI globally via official script. It uses curl/bash on macOS/Linux and powershell on Windows.
func IsInstalled ¶
func IsInstalled() bool
IsInstalled returns true if the 'claude' CLI executable is found in system PATH.
func RunConfSet ¶ added in v1.1.1
func RunInstallerScript ¶
func RunInstallerScript() error
Types ¶
type AdvancedConfigModel ¶ added in v1.2.0
type AdvancedConfigModel struct {
// Page 4
IsActiveChosen bool
// contains filtered or unexported fields
}
func NewAdvancedConfigModel ¶ added in v1.2.0
func NewAdvancedConfigModel(p *provider.Provider) *AdvancedConfigModel
func (*AdvancedConfigModel) Init ¶ added in v1.2.0
func (m *AdvancedConfigModel) Init() tea.Cmd
func (*AdvancedConfigModel) View ¶ added in v1.2.0
func (m *AdvancedConfigModel) View() tea.View
func (m *AdvancedConfigModel) View() tea.View {
var body strings.Builder
protoLabel := protoBadgeStyle.Render("Protocol: " + m.getProtocol())
switch m.page {
case 0:
// ==================== PAGE 0: 凭据配置 ====================
body.WriteString(titleStyle.Render(locale.T("基础凭据配置", "Base Credentials")) + badgeStyle.Render("Credentials") + protoLabel + "\n\n")
prefURL := " "
if m.cursor == 0 {
prefURL = selectedStyle.Render("> ")
}
body.WriteString(fmt.Sprintf("%s%-14s: %s\n", prefURL, purpleText.Render("Endpoint URL"), m.urlInput.View()))
prefKey := " "
if m.cursor == 1 {
prefKey = selectedStyle.Render("> ")
}
body.WriteString(fmt.Sprintf("%s%-14s: %s\n", prefKey, purpleText.Render("API Key"), m.keyInput.View()))
body.WriteString(renderBottomButtons(m.page, m.cursor, 2, 3))
body.WriteString(grayText.Render(locale.T("↑↓ 切换焦点 · ←→ 切换按钮 · enter 确认", "↑↓ Switch · ←→ Toggle Buttons · enter confirm")))
case 1:
// ==================== PAGE 1: 槽位映射配置 ====================
if !m.filterInput.Focused() {
body.WriteString(titleStyle.Render(locale.T("Claude Slot 映射配置", "Claude Slot Mapping")) + badgeStyle.Render("Slot List") + protoLabel + "\n\n")
renderRow := func(idx int, label, val string) {
prefix := " "
labelStr := purpleText.Render(label)
if m.cursor == idx {
prefix = selectedStyle.Render("> ")
labelStr = selectedStyle.Render(label) + grayText.Render(" ("+locale.T("enter 筛选", "enter to list")+")")
}
modelStr := cyanText.Render(val)
if val == "" {
modelStr = grayText.Render(locale.T("(未设置)", "(unset)"))
}
body.WriteString(fmt.Sprintf("%s%-6s – %s\n", prefix, labelStr, modelStr))
}
renderRow(0, "Opus", m.p.OpusModel)
renderRow(1, "Sonnet", m.p.SonnetModel)
renderRow(2, "Haiku", m.p.HaikuModel)
renderRow(3, "Custom", m.p.LockModel)
body.WriteString(renderBottomButtons(m.page, m.cursor, 4, 5))
body.WriteString(grayText.Render(locale.T("↑↓ 移动光标 · ←→ 切换按钮 · enter 选择编辑/跳转", "↑↓ Move · ←→ Toggle Buttons · enter select")))
} else {
slotName := []string{"Opus", "Sonnet", "Haiku", "Custom"}[m.activeSlot]
body.WriteString(titleStyle.Render(fmt.Sprintf(locale.T("配置槽位 [%s] 模型筛选", "Select Model for Slot [%s]"), slotName)))
body.WriteString("\n" + filterStyle.Render(locale.T("🔍 过滤模型: ", "🔍 Filter model: ")) + m.filterInput.View() + "\n")
for i, mod := range m.filteredPool {
prefix := " "
line := grayText.Render(mod)
if i == m.slotListCursor {
prefix = selectedStyle.Render(" > ")
line = selectedStyle.Render(mod)
}
body.WriteString(prefix + line + "\n")
}
body.WriteString(selectedStyle.Render(fmt.Sprintf(" %d/%d", m.slotListCursor+1, len(m.filteredPool))) + "\n\n" + grayText.Render(locale.T("键盘输入任意字符快速过滤 · ↑↓ 选择 · enter 锁定 · esc 取消", "Type to filter · ↑↓ Scroll · enter lock · esc cancel")) + "\n")
}
case 2:
// ==================== PAGE 2: 1M 上下文配置页 (精简) ====================
body.WriteString(titleStyle.Render(locale.T("1M 上下文配置", "1M Context")) + badgeStyle.Render("MultiSelect") + protoLabel + "\n")
body.WriteString(grayText.Render(locale.T("enter 切换开关状态", "enter Toggle Slot Status")) + "\n\n")
renderMultiRow := func(idx int, label, modelVal string) {
box := "[ ]"
slotKey := []string{"opus", "sonnet", "haiku", "custom"}[idx]
if m.oneMSlots[slotKey] {
box = "[x]"
}
// ✅ 核心修复:将游标和方括号合并渲染,避免 \x1b[0m[ 贴在一起导致终端解析错误
var indicator string
if m.cursor == idx {
indicator = selectedStyle.Render("> " + box)
} else {
indicator = " " + box
}
itemText := grayText.Render(label)
if m.cursor == idx {
itemText = titleStyle.Render(label)
}
modelStr := cyanText.Render(modelVal)
if modelVal == "" {
modelStr = grayText.Render(locale.T("(未设置)", "(unset)"))
}
suffix := ""
if m.oneMSlots[slotKey] {
suffix = " " + lightning
}
// 这里改用组合好的 indicator 替换原先的 %s%s
body.WriteString(fmt.Sprintf("%s %-14s – %s%s\n", indicator, itemText, modelStr, suffix))
}
renderMultiRow(0, "Opus Config", m.p.OpusModel)
renderMultiRow(1, "Sonnet Config", m.p.SonnetModel)
renderMultiRow(2, "Haiku Config", m.p.HaikuModel)
renderMultiRow(3, "Custom Config", m.p.LockModel)
body.WriteString(renderBottomButtons(m.page, m.cursor, 4, 5))
body.WriteString(grayText.Render(locale.T("enter 切换 · ↑↓ 移动 · ←→ 切换按钮", "enter Toggle · ↑↓ Move · ←→ Toggle Buttons")))
case 3:
// ==================== PAGE 3: Reasoning Effort 思考流配置 ====================
body.WriteString(titleStyle.Render(locale.T("Reasoning Effort 思考流配置", "Reasoning Effort")) + badgeStyle.Render("Effort") + protoLabel + "\n\n")
for i, level := range m.effortLevels {
prefix := " "
if m.cursor == i {
prefix = selectedStyle.Render("> ")
}
radio := "( )"
if m.effortCursor == i {
radio = purpleText.Render("(●)")
}
itemText := grayText.Render(level)
if m.cursor == i {
itemText = titleStyle.Render(level)
}
body.WriteString(fmt.Sprintf("%s%s %s\n", prefix, radio, itemText))
}
body.WriteString(renderBottomButtons(m.page, m.cursor, 6, 7))
body.WriteString(grayText.Render(locale.T("↑↓ 移动选择级别 · ←→ 切换按钮 · enter 前进/后退", "↑↓ Move · ←→ Toggle Buttons · enter next/back")))
case 4:
// ==================== PAGE 4: 核对并应用此配置 ====================
body.WriteString(titleStyle.Render(locale.T("核对并应用此 Provider 配置", "Review & Apply")) + badgeStyle.Render("Confirm") + "\n\n")
body.WriteString(fmt.Sprintf(" • %-12s: %s\n", "Endpoint", cyanText.Render(m.p.Endpoint)))
body.WriteString(fmt.Sprintf(" • %-12s: %s\n", "Protocol", purpleText.Render(m.getProtocol())))
body.WriteString(fmt.Sprintf(" • %-12s: %s\n", "Effort Level", purpleText.Render(m.effortLevels[m.effortCursor])))
body.WriteString("\n " + locale.T("是否将该 Provider 设为当前激活配置?", "Set as active provider right now?") + "\n\n")
yesStr, noStr := " "+locale.T("是", "Yes")+" ", " "+locale.T("否", "No")+" "
if m.IsActiveChosen {
yesStr = selectedStyle.Render(" > " + locale.T("是", "Yes") + " <")
noStr = grayText.Render(" " + locale.T("否", "No") + " ")
} else {
yesStr = grayText.Render(" " + locale.T("是", "Yes") + " ")
noStr = selectedStyle.Render(" > " + locale.T("否", "No") + " <")
}
if m.cursor == 0 || m.cursor == 1 {
body.WriteString(" " + yesStr + " " + noStr + "\n")
} else {
body.WriteString(" " + strings.TrimSpace(yesStr) + " " + strings.TrimSpace(noStr) + "\n")
}
saveStr := " " + locale.T("完成并保存", "Save & Finish")
if m.cursor == 2 {
saveStr = selectedStyle.Render("> " + locale.T("完成并保存", "Save & Finish"))
}
body.WriteString("\n" + saveStr + "\n\n")
body.WriteString(grayText.Render(locale.T("←→ 切换激活选项 · ↑↓ 移动 · enter 保存", "←→ Toggle · ↑↓ Move · enter save")))
}
// 指示器
dots := []string{grayText.Render("⚫"), grayText.Render("⚫"), grayText.Render("⚫"), grayText.Render("⚫"), grayText.Render("⚫")}
activeColors := []string{"🔵", "🟣", "🟢", "🟡", "🔴"}
dots[m.page] = activeColors[m.page]
pager := fmt.Sprintf("\n\n%s", lipgloss.NewStyle().Width(70).Align(lipgloss.Center).Render(strings.Join(dots, " ")))
langTipMsg := locale.T(
"💡 提示: 想要更改终端显示语言?使用 `ccl lang` 即可轻松修改",
"💡 Tip: Want to change TUI display language? Use `ccl lang` to modify it",
)
langTipBanner := "\n" + lipgloss.NewStyle().Width(70).Align(lipgloss.Center).Render(langTipStyle.Render(langTipMsg))
finalStr := windowStyle.Render(body.String()) + pager + langTipBanner
return tea.NewView(finalStr)
}
Click to show internal directories.
Click to hide internal directories.