ollama source for Momentry Core verification
This commit is contained in:
393
cmd/tui/tui.go
Normal file
393
cmd/tui/tui.go
Normal file
@@ -0,0 +1,393 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/ollama/ollama/cmd/launch"
|
||||
"github.com/ollama/ollama/version"
|
||||
)
|
||||
|
||||
var (
|
||||
versionStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "243", Dark: "250"})
|
||||
|
||||
menuItemStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2)
|
||||
|
||||
menuSelectedItemStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Background(lipgloss.AdaptiveColor{Light: "254", Dark: "236"})
|
||||
|
||||
menuDescStyle = selectorDescStyle.
|
||||
PaddingLeft(4)
|
||||
|
||||
greyedStyle = menuItemStyle.
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "242", Dark: "246"})
|
||||
|
||||
greyedSelectedStyle = menuSelectedItemStyle.
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "242", Dark: "246"})
|
||||
|
||||
modelStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "243", Dark: "250"})
|
||||
|
||||
notInstalledStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "242", Dark: "246"}).
|
||||
Italic(true)
|
||||
)
|
||||
|
||||
type menuItem struct {
|
||||
title string
|
||||
description string
|
||||
integration string
|
||||
isRunModel bool
|
||||
isOthers bool
|
||||
}
|
||||
|
||||
const pinnedIntegrationCount = 4
|
||||
|
||||
var runModelMenuItem = menuItem{
|
||||
title: "Chat with a model",
|
||||
description: "Start an interactive chat with a model",
|
||||
isRunModel: true,
|
||||
}
|
||||
|
||||
var othersMenuItem = menuItem{
|
||||
title: "More...",
|
||||
description: "Show additional integrations",
|
||||
isOthers: true,
|
||||
}
|
||||
|
||||
type model struct {
|
||||
state *launch.LauncherState
|
||||
items []menuItem
|
||||
cursor int
|
||||
showOthers bool
|
||||
width int
|
||||
quitting bool
|
||||
selected bool
|
||||
action TUIAction
|
||||
}
|
||||
|
||||
func newModel(state *launch.LauncherState) model {
|
||||
m := model{
|
||||
state: state,
|
||||
}
|
||||
m.showOthers = shouldExpandOthers(state)
|
||||
m.items = buildMenuItems(state, m.showOthers)
|
||||
m.cursor = initialCursor(state, m.items)
|
||||
return m
|
||||
}
|
||||
|
||||
func shouldExpandOthers(state *launch.LauncherState) bool {
|
||||
if state == nil {
|
||||
return false
|
||||
}
|
||||
for _, item := range otherIntegrationItems(state) {
|
||||
if item.integration == state.LastSelection {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func buildMenuItems(state *launch.LauncherState, showOthers bool) []menuItem {
|
||||
items := []menuItem{runModelMenuItem}
|
||||
items = append(items, pinnedIntegrationItems(state)...)
|
||||
|
||||
otherItems := otherIntegrationItems(state)
|
||||
switch {
|
||||
case showOthers:
|
||||
items = append(items, otherItems...)
|
||||
case len(otherItems) > 0:
|
||||
items = append(items, othersMenuItem)
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func integrationMenuItem(state launch.LauncherIntegrationState) menuItem {
|
||||
description := state.Description
|
||||
if description == "" {
|
||||
description = "Open " + state.DisplayName + " integration"
|
||||
}
|
||||
return menuItem{
|
||||
title: "Launch " + state.DisplayName,
|
||||
description: description,
|
||||
integration: state.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func otherIntegrationItems(state *launch.LauncherState) []menuItem {
|
||||
ordered := orderedIntegrationItems(state)
|
||||
if len(ordered) <= pinnedIntegrationCount {
|
||||
return nil
|
||||
}
|
||||
return ordered[pinnedIntegrationCount:]
|
||||
}
|
||||
|
||||
func pinnedIntegrationItems(state *launch.LauncherState) []menuItem {
|
||||
ordered := orderedIntegrationItems(state)
|
||||
if len(ordered) <= pinnedIntegrationCount {
|
||||
return ordered
|
||||
}
|
||||
return ordered[:pinnedIntegrationCount]
|
||||
}
|
||||
|
||||
func orderedIntegrationItems(state *launch.LauncherState) []menuItem {
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
items := make([]menuItem, 0, len(state.Integrations))
|
||||
for _, info := range launch.ListIntegrationInfos() {
|
||||
integrationState, ok := state.Integrations[info.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
items = append(items, integrationMenuItem(integrationState))
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func primaryMenuItemCount(state *launch.LauncherState) int {
|
||||
return 1 + len(pinnedIntegrationItems(state))
|
||||
}
|
||||
|
||||
func initialCursor(state *launch.LauncherState, items []menuItem) int {
|
||||
if state == nil || state.LastSelection == "" {
|
||||
return 0
|
||||
}
|
||||
for i, item := range items {
|
||||
if state.LastSelection == "run" && item.isRunModel {
|
||||
return i
|
||||
}
|
||||
if item.integration == state.LastSelection {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
return m, nil
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "q", "esc":
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
|
||||
case "up", "k":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
if m.showOthers && m.cursor < primaryMenuItemCount(m.state) {
|
||||
m.showOthers = false
|
||||
m.items = buildMenuItems(m.state, false)
|
||||
m.cursor = min(m.cursor, len(m.items)-1)
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case "down", "j":
|
||||
if m.cursor < len(m.items)-1 {
|
||||
m.cursor++
|
||||
}
|
||||
if m.cursor < len(m.items) && m.items[m.cursor].isOthers && !m.showOthers {
|
||||
m.showOthers = true
|
||||
m.items = buildMenuItems(m.state, true)
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case "enter", " ":
|
||||
if m.selectableItem(m.items[m.cursor]) {
|
||||
m.selected = true
|
||||
m.action = actionForMenuItem(m.items[m.cursor], false)
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case "right", "l":
|
||||
item := m.items[m.cursor]
|
||||
if item.isRunModel || m.changeableItem(item) {
|
||||
m.selected = true
|
||||
m.action = actionForMenuItem(item, true)
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model) selectableItem(item menuItem) bool {
|
||||
if item.isRunModel {
|
||||
return true
|
||||
}
|
||||
if item.integration == "" || item.isOthers {
|
||||
return false
|
||||
}
|
||||
state, ok := m.state.Integrations[item.integration]
|
||||
return ok && state.Selectable
|
||||
}
|
||||
|
||||
func (m model) changeableItem(item menuItem) bool {
|
||||
if item.integration == "" || item.isOthers {
|
||||
return false
|
||||
}
|
||||
state, ok := m.state.Integrations[item.integration]
|
||||
return ok && state.Changeable
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
if m.quitting {
|
||||
return ""
|
||||
}
|
||||
|
||||
s := selectorTitleStyle.Render("Ollama "+versionStyle.Render(version.Version)) + "\n\n"
|
||||
|
||||
for i, item := range m.items {
|
||||
s += m.renderMenuItem(i, item)
|
||||
}
|
||||
|
||||
s += "\n" + selectorHelpStyle.Render("↑/↓ navigate • enter launch • → configure • esc quit")
|
||||
|
||||
if m.width > 0 {
|
||||
return lipgloss.NewStyle().MaxWidth(m.width).Render(s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (m model) renderMenuItem(index int, item menuItem) string {
|
||||
cursor := ""
|
||||
style := menuItemStyle
|
||||
title := item.title
|
||||
description := item.description
|
||||
modelSuffix := ""
|
||||
|
||||
if m.cursor == index {
|
||||
cursor = "▸ "
|
||||
}
|
||||
|
||||
if item.isRunModel {
|
||||
if m.cursor == index && m.state.RunModel != "" {
|
||||
modelSuffix = " " + modelStyle.Render("("+m.state.RunModel+")")
|
||||
}
|
||||
if m.cursor == index {
|
||||
style = menuSelectedItemStyle
|
||||
}
|
||||
} else if item.isOthers {
|
||||
if m.cursor == index {
|
||||
style = menuSelectedItemStyle
|
||||
}
|
||||
} else {
|
||||
integrationState := m.state.Integrations[item.integration]
|
||||
if !integrationState.Selectable {
|
||||
if m.cursor == index {
|
||||
style = greyedSelectedStyle
|
||||
} else {
|
||||
style = greyedStyle
|
||||
}
|
||||
} else if m.cursor == index {
|
||||
style = menuSelectedItemStyle
|
||||
}
|
||||
|
||||
if m.cursor == index && integrationState.CurrentModel != "" {
|
||||
modelSuffix = " " + modelStyle.Render("("+integrationState.CurrentModel+")")
|
||||
}
|
||||
|
||||
if !integrationState.Installed {
|
||||
if integrationState.AutoInstallable {
|
||||
title += " " + notInstalledStyle.Render("(install)")
|
||||
} else {
|
||||
title += " " + notInstalledStyle.Render("(not installed)")
|
||||
}
|
||||
if m.cursor == index {
|
||||
if integrationState.AutoInstallable {
|
||||
description = "Press enter to install"
|
||||
} else if integrationState.InstallHint != "" {
|
||||
description = integrationState.InstallHint
|
||||
} else {
|
||||
description = "not installed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return style.Render(cursor+title) + modelSuffix + "\n" + menuDescStyle.Render(description) + "\n\n"
|
||||
}
|
||||
|
||||
type TUIActionKind int
|
||||
|
||||
const (
|
||||
TUIActionNone TUIActionKind = iota
|
||||
TUIActionRunModel
|
||||
TUIActionLaunchIntegration
|
||||
)
|
||||
|
||||
type TUIAction struct {
|
||||
Kind TUIActionKind
|
||||
Integration string
|
||||
ForceConfigure bool
|
||||
}
|
||||
|
||||
func (a TUIAction) LastSelection() string {
|
||||
switch a.Kind {
|
||||
case TUIActionRunModel:
|
||||
return "run"
|
||||
case TUIActionLaunchIntegration:
|
||||
return a.Integration
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (a TUIAction) RunModelRequest() launch.RunModelRequest {
|
||||
return launch.RunModelRequest{ForcePicker: a.ForceConfigure}
|
||||
}
|
||||
|
||||
func (a TUIAction) IntegrationLaunchRequest() launch.IntegrationLaunchRequest {
|
||||
return launch.IntegrationLaunchRequest{
|
||||
Name: a.Integration,
|
||||
ForceConfigure: a.ForceConfigure,
|
||||
}
|
||||
}
|
||||
|
||||
func actionForMenuItem(item menuItem, forceConfigure bool) TUIAction {
|
||||
switch {
|
||||
case item.isRunModel:
|
||||
return TUIAction{Kind: TUIActionRunModel, ForceConfigure: forceConfigure}
|
||||
case item.integration != "":
|
||||
return TUIAction{Kind: TUIActionLaunchIntegration, Integration: item.integration, ForceConfigure: forceConfigure}
|
||||
default:
|
||||
return TUIAction{Kind: TUIActionNone}
|
||||
}
|
||||
}
|
||||
|
||||
func RunMenu(state *launch.LauncherState) (TUIAction, error) {
|
||||
menu := newModel(state)
|
||||
program := tea.NewProgram(menu)
|
||||
|
||||
finalModel, err := program.Run()
|
||||
if err != nil {
|
||||
return TUIAction{Kind: TUIActionNone}, fmt.Errorf("error running TUI: %w", err)
|
||||
}
|
||||
|
||||
finalMenu := finalModel.(model)
|
||||
if !finalMenu.selected {
|
||||
return TUIAction{Kind: TUIActionNone}, nil
|
||||
}
|
||||
|
||||
return finalMenu.action, nil
|
||||
}
|
||||
Reference in New Issue
Block a user