// This program looks up words fromm Wiktionary, and creates Anki flashcards // from them. package main import ( "database/sql" "fmt" "log" "net/http" "time" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" _ "github.com/mattn/go-sqlite3" "github.com/microcosm-cc/bluemonday" ) const ( rawDictionary = "/home/david/work/french-wiktionary-flashcards/raw-wiktextract-data.jsonl" dictionary = "/home/david/work/french-wiktionary-flashcards/raw-wiktextract-data.sqlite3" apiURL = "http://localhost:8765" deckName = "Français" modelName = "Basic-830ae" ) type model struct { wordInput textinput.Model err error currentWord string currentDefinition string db *sql.DB c *http.Client wordAddStatus string p bluemonday.Policy } type ( errMsg error definitionMsg string wordAddedMsg string ) func initialModel(c *http.Client, db *sql.DB) model { ti := textinput.New() ti.Placeholder = "" ti.Focus() ti.CharLimit = 156 ti.Width = 36 return model{ wordInput: ti, err: nil, db: db, c: c, wordAddStatus: "(Press 'Enter' to add this word and its definition to Anki)", p: *bluemonday.StrictPolicy(), } } func (m model) Init() tea.Cmd { return textinput.Blink } func lookupWord(db *sql.DB, word string) tea.Cmd { return func() tea.Msg { var definition string row := db.QueryRow(`select definition from words where word = ? limit 1`, word) err := row.Scan(&definition) if err != nil { return errMsg(fmt.Errorf("looking up '%s': %s", word, err)) } return definitionMsg(definition) } } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd var textPickerLongerCmds []tea.Cmd switch msg := msg.(type) { case definitionMsg: m.currentDefinition = string(msg) return m, nil case wordAddedMsg: m.wordAddStatus = fmt.Sprintf("✅ Added '%s' to Anki", string(msg)) case tea.KeyMsg: switch msg.Type { case tea.KeyCtrlC, tea.KeyEsc: return m, tea.Quit case tea.KeyEnter: return m, addCard(m.c, m.currentWord, m.currentDefinition) } case errMsg: m.err = msg return m, nil } if m.wordInput.Value() != m.currentWord { m.currentWord = m.wordInput.Value() textPickerLongerCmds = append(textPickerLongerCmds, lookupWord(m.db, m.currentWord)) } m.wordInput, cmd = m.wordInput.Update(msg) textPickerLongerCmds = append(textPickerLongerCmds, cmd) return m, tea.Batch(textPickerLongerCmds...) } func (m model) View() string { return fmt.Sprintf( "Look up a word:\n\n%s\n\n%s\n%s\n\n%s\n%s", m.wordInput.View(), m.wordAddStatus, "(esc to quit)", "Current definition:\n", m.p.Sanitize(m.currentDefinition), ) + "\n" } func main() { db, err := setupDatabase() if err != nil { log.Fatalf("setting up database: %s", err) } defer db.Close() c := http.DefaultClient c.Timeout = 5 * time.Second p := tea.NewProgram(initialModel(c, db)) if _, err := p.Run(); err != nil { log.Fatal(err) } }