okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.pettui: rename and delete sessions straight from the list, and keep an archive from being undone by an older listing5e287ce

main
4.5 KiB170 linesraw
1package tui
2
3import (
4	"albedo/cli/internal/daemon"
5	"context"
6	"errors"
7	"slices"
8
9	tea "charm.land/bubbletea/v2"
10)
11
12func (m *AppModel) handlePreferences(msg tea.Msg) (tea.Cmd, bool) {
13	switch msg := msg.(type) {
14	case settingsLoadedMsg:
15		if msg.Gen != m.SettingsGen {
16			return nil, true
17		}
18		if msg.Err != nil {
19			m.Chat.AddError(msg.Err.Error())
20			m.SessionPicker.notice = msg.Err.Error()
21			return nil, true
22		}
23		m.Profiles = msg.Settings.Profiles
24		m.SettingsETags = msg.Settings.ETags
25		m.ApplyUI(msg.Settings.UI)
26		return nil, true
27	case uiSavedMsg:
28		if msg.Open {
29			if msg.Err != nil {
30				m.Chat.AddError(msg.Err.Error())
31				if _, uncertain := errors.AsType[*daemon.UncertainOutcomeError](msg.Err); uncertain {
32					return nil, true
33				}
34			}
35			if msg.Err == nil {
36				m.ApplyUI(msg.Prefs)
37			}
38			return m.refreshSettingsCmd(), true
39		}
40		// One save runs at a time, so this answer always ends it.
41		m.UISaving, m.SessionPicker.Saving = false, false
42		if msg.Session == "" && msg.Gen != m.SettingsGen {
43			// The settings read an invalidation asked for waited on this save.
44			return m.loadSettingsCmd(m.SettingsGen), true
45		}
46		if msg.Err != nil {
47			m.Chat.AddError(msg.Err.Error())
48			m.SessionPicker.notice = msg.Err.Error()
49			return nil, true
50		}
51		m.SessionPicker.notice = ""
52		m.ApplyUI(msg.Prefs)
53		if msg.Session == "" {
54			return nil, true
55		}
56		if !m.listingSessions {
57			return nil, true
58		}
59		// The listing in flight predates the save and would undo it.
60		m.SessionGen++
61		return m.loadSessionsCmd(m.SessionGen), true
62	case SessionPreferenceMsg:
63		if m.UISaving {
64			m.SessionPicker.Saving = false
65			return nil, true
66		}
67		m.UISaving = true
68		m.SettingsGen++
69		m.SessionGen++
70		patch := daemon.UIPreferencesPatch{ETag: msg.ETag}
71		switch msg.Field {
72		case "pinned":
73			patch.Pinned = &msg.Value
74		case "archived":
75			patch.Archived = &msg.Value
76		}
77		return m.patchUICmd(msg.ID, patch, m.SettingsGen), true
78	}
79	return nil, false
80}
81
82// ApplyUI takes acknowledged shared preferences from the daemon.
83func (m *AppModel) ApplyUI(prefs daemon.UIPreferences) {
84	if len(prefs.SessionETags) != 0 || len(prefs.Opens) != 0 {
85		m.SessionGen++ // Earlier listings predate these acknowledged session facts.
86	}
87	if prefs.ETag != "" {
88		m.UI.Thinking, m.UI.Tools, m.UI.ETag = prefs.Thinking, prefs.Tools, prefs.ETag
89		if prefs.DismissedNotices != nil {
90			m.UI.DismissedNotices = slices.Clone(prefs.DismissedNotices)
91		}
92	}
93	for i := range m.Sessions {
94		m.Sessions[i] = applySessionPreferences(m.Sessions[i], prefs)
95	}
96	if m.ActiveSession != nil {
97		captured := applySessionPreferences(*m.ActiveSession, prefs)
98		m.ActiveSession = &captured
99	}
100	m.updateSessionPickerItems()
101	m.Chat.Flags.Thinking, m.Chat.Flags.Tools = m.UI.Thinking, m.UI.Tools
102}
103
104func applySessionPreferences(session daemon.Session, prefs daemon.UIPreferences) daemon.Session {
105	if etag, updated := prefs.SessionETags[session.ID]; updated {
106		session.ETag = etag
107		session.Pinned = slices.Contains(prefs.Pinned, session.ID)
108		session.Archived = slices.Contains(prefs.Archived, session.ID)
109	}
110	if opens, updated := prefs.Opens[session.ID]; updated {
111		session.Opens = opens
112	}
113	return session
114}
115
116func (m *AppModel) refreshSettingsCmd() tea.Cmd {
117	if m.UISaving {
118		return nil
119	}
120	m.SettingsGen++
121	return m.loadSettingsCmd(m.SettingsGen)
122}
123
124func (m *AppModel) loadSettingsCmd(gen int) tea.Cmd {
125	if m.UISaving {
126		return nil
127	}
128	conn := m.Conn
129	return func() tea.Msg {
130		settings, err := daemon.GetSettings(context.Background(), conn)
131		return settingsLoadedMsg{Settings: settings, Gen: gen, Err: err}
132	}
133}
134
135func (m *AppModel) patchUICmd(session string, patch daemon.UIPreferencesPatch, gen int) tea.Cmd {
136	conn := m.Conn
137	if session == "" {
138		patch.ETag = m.UI.ETag
139	}
140	if patch.Thinking != nil {
141		patch.Thinking = new(*patch.Thinking)
142	}
143	if patch.Tools != nil {
144		patch.Tools = new(*patch.Tools)
145	}
146	if patch.Pinned != nil {
147		patch.Pinned = new(*patch.Pinned)
148	}
149	if patch.Archived != nil {
150		patch.Archived = new(*patch.Archived)
151	}
152	return func() tea.Msg {
153		var prefs daemon.UIPreferences
154		var err error
155		if session == "" {
156			prefs, err = daemon.PatchUI(context.Background(), conn, patch)
157		} else {
158			prefs, err = daemon.PatchSessionUI(context.Background(), conn, session, patch)
159		}
160		return uiSavedMsg{Prefs: prefs, Gen: gen, Session: session, Err: err}
161	}
162}
163
164func (m *AppModel) recordOpenCmd(session string) tea.Cmd {
165	conn := m.Conn
166	return func() tea.Msg {
167		prefs, err := daemon.RecordOpen(context.Background(), conn, session)
168		return uiSavedMsg{Prefs: prefs, Open: true, Err: err}
169	}
170}