fetch_ml/cmd/tui/internal/model/state.go
Jeremie Fraeys 803677be57 feat: implement Go backend with comprehensive API and internal packages
- Add API server with WebSocket support and REST endpoints
- Implement authentication system with API keys and permissions
- Add task queue system with Redis backend and error handling
- Include storage layer with database migrations and schemas
- Add comprehensive logging, metrics, and telemetry
- Implement security middleware and network utilities
- Add experiment management and container orchestration
- Include configuration management with smart defaults
2025-12-04 16:53:53 -05:00

206 lines
6.3 KiB
Go

package model
import (
"fmt"
"time"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbles/spinner"
"github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport"
"github.com/charmbracelet/lipgloss"
)
type ViewMode int
const (
ViewModeJobs ViewMode = iota
ViewModeGPU
ViewModeQueue
ViewModeContainer
ViewModeSettings
ViewModeDatasets
ViewModeExperiments
)
type JobStatus string
const (
StatusPending JobStatus = "pending"
StatusQueued JobStatus = "queued"
StatusRunning JobStatus = "running"
StatusFinished JobStatus = "finished"
StatusFailed JobStatus = "failed"
)
type Job struct {
Name string
Status JobStatus
TaskID string
Priority int64
}
func (j Job) Title() string { return j.Name }
func (j Job) Description() string {
icon := map[JobStatus]string{
StatusPending: "⏸",
StatusQueued: "⏳",
StatusRunning: "▶",
StatusFinished: "✓",
StatusFailed: "✗",
}[j.Status]
pri := ""
if j.Priority > 0 {
pri = fmt.Sprintf(" [P%d]", j.Priority)
}
return fmt.Sprintf("%s %s%s", icon, j.Status, pri)
}
func (j Job) FilterValue() string { return j.Name }
type Task struct {
ID string `json:"id"`
JobName string `json:"job_name"`
Args string `json:"args"`
Status string `json:"status"`
Priority int64 `json:"priority"`
CreatedAt time.Time `json:"created_at"`
StartedAt *time.Time `json:"started_at,omitempty"`
EndedAt *time.Time `json:"ended_at,omitempty"`
Error string `json:"error,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
type DatasetInfo struct {
Name string `json:"name"`
SizeBytes int64 `json:"size_bytes"`
Location string `json:"location"`
LastAccess time.Time `json:"last_access"`
}
// State holds the application state
type State struct {
Jobs []Job
QueuedTasks []*Task
Datasets []DatasetInfo
JobList list.Model
GpuView viewport.Model
ContainerView viewport.Model
QueueView viewport.Model
SettingsView viewport.Model
DatasetView viewport.Model
ExperimentsView viewport.Model
Input textinput.Model
ApiKeyInput textinput.Model
Status string
ErrorMsg string
InputMode bool
Width int
Height int
ShowHelp bool
Spinner spinner.Model
ActiveView ViewMode
LastRefresh time.Time
IsLoading bool
JobStats map[JobStatus]int
ApiKey string
SettingsIndex int
Keys KeyMap
}
type KeyMap struct {
Refresh key.Binding
Trigger key.Binding
TriggerArgs key.Binding
ViewQueue key.Binding
ViewContainer key.Binding
ViewGPU key.Binding
ViewJobs key.Binding
ViewDatasets key.Binding
ViewExperiments key.Binding
ViewSettings key.Binding
Cancel key.Binding
Delete key.Binding
MarkFailed key.Binding
RefreshGPU key.Binding
Help key.Binding
Quit key.Binding
}
var Keys = KeyMap{
Refresh: key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "refresh all")),
Trigger: key.NewBinding(key.WithKeys("t"), key.WithHelp("t", "queue job")),
TriggerArgs: key.NewBinding(key.WithKeys("a"), key.WithHelp("a", "queue w/ args")),
ViewQueue: key.NewBinding(key.WithKeys("v"), key.WithHelp("v", "view queue")),
ViewContainer: key.NewBinding(key.WithKeys("o"), key.WithHelp("o", "containers")),
ViewGPU: key.NewBinding(key.WithKeys("g"), key.WithHelp("g", "gpu status")),
ViewJobs: key.NewBinding(key.WithKeys("1"), key.WithHelp("1", "job list")),
ViewDatasets: key.NewBinding(key.WithKeys("2"), key.WithHelp("2", "datasets")),
ViewExperiments: key.NewBinding(key.WithKeys("3"), key.WithHelp("3", "experiments")),
Cancel: key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "cancel task")),
Delete: key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "delete job")),
MarkFailed: key.NewBinding(key.WithKeys("f"), key.WithHelp("f", "mark failed")),
RefreshGPU: key.NewBinding(key.WithKeys("G"), key.WithHelp("G", "refresh GPU")),
ViewSettings: key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "settings")),
Help: key.NewBinding(key.WithKeys("h", "?"), key.WithHelp("h/?", "toggle help")),
Quit: key.NewBinding(key.WithKeys("q", "ctrl+c"), key.WithHelp("q", "quit")),
}
func InitialState(apiKey string) State {
items := []list.Item{}
delegate := list.NewDefaultDelegate()
delegate.Styles.SelectedTitle = delegate.Styles.SelectedTitle.
Foreground(lipgloss.Color("170")).
Bold(true)
delegate.Styles.SelectedDesc = delegate.Styles.SelectedDesc.
Foreground(lipgloss.Color("246"))
jobList := list.New(items, delegate, 0, 0)
jobList.Title = "ML Jobs & Queue"
jobList.SetShowStatusBar(true)
jobList.SetFilteringEnabled(true)
jobList.SetShowHelp(false)
// Styles will be set in View or here?
// Keeping style initialization here as it's part of the model state setup
jobList.Styles.Title = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.AdaptiveColor{Light: "#2980b9", Dark: "#7aa2f7"}).
Padding(0, 0, 1, 0)
input := textinput.New()
input.Placeholder = "Args: --epochs 100 --lr 0.001 --priority 5"
input.Width = 60
input.CharLimit = 200
apiKeyInput := textinput.New()
apiKeyInput.Placeholder = "Enter API key..."
apiKeyInput.Width = 40
apiKeyInput.CharLimit = 200
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#2980b9", Dark: "#7aa2f7"})
return State{
JobList: jobList,
GpuView: viewport.New(0, 0),
ContainerView: viewport.New(0, 0),
QueueView: viewport.New(0, 0),
SettingsView: viewport.New(0, 0),
DatasetView: viewport.New(0, 0),
ExperimentsView: viewport.New(0, 0),
Input: input,
ApiKeyInput: apiKeyInput,
Status: "Connected",
InputMode: false,
ShowHelp: false,
Spinner: s,
ActiveView: ViewModeJobs,
LastRefresh: time.Now(),
IsLoading: false,
JobStats: make(map[JobStatus]int),
ApiKey: apiKey,
SettingsIndex: 0,
Keys: Keys,
}
}