Documentation
¶
Overview ¶
Package userdata is the state dbc keeps for the user between sessions: the query history, the editor buffer and the assistant's conversations, all under ~/.config/dbc.
It started life inside the former tview UI (package ui, since removed), and kept that UI's on-disk format — same paths, same JSON-lines shape — so history and the scratchpad written by older dbc builds still load.
Index ¶
- Constants
- func BufferFile() string
- func ChatTitle(msgs []ChatMsg) string
- func ChatsDir() string
- func HistoryFile() string
- func LoadBuffer(path string) string
- func NewChatID(now time.Time) string
- func RemoveChat(dir, id string) error
- func SaveBuffer(path, text string) error
- func SaveChat(dir string, c Chat) error
- type Chat
- type ChatMeta
- type ChatMsg
- type Entry
- type History
Constants ¶
const ( // MaxChats caps how many conversations the directory keeps, trimmed // from the oldest. The feature is "the last few chats": an unbounded // archive would be a directory growing forever and a picker nobody can // scan. Thirty is a working week or two of questions. MaxChats = 30 // MaxChatTitle bounds a derived title, in runes. The picker row also // carries a date, a message count and sometimes a connection name, and // a title long enough to push those off the row costs more than it says. MaxChatTitle = 72 )
Variables ¶
This section is empty.
Functions ¶
func BufferFile ¶
func BufferFile() string
BufferFile is where the editor buffer persists between sessions, under the user's config directory. It returns "" when no home directory is known, and the buffer simply does not persist.
func ChatTitle ¶
ChatTitle picks what a conversation is remembered by: the first line of the first thing the USER asked. Not the answer — the question is what the user scans a list for, and an answer's first line is usually a restatement or a preamble. Returns "" when there is no question; the caller decides how an untitled row reads.
func ChatsDir ¶
func ChatsDir() string
ChatsDir is where conversations persist, beside the history and the editor buffer. It returns "" when no home directory is known, and conversations then live only for the session.
func HistoryFile ¶
func HistoryFile() string
HistoryFile is where the history persists, beside the editor buffer. It returns "" when no home directory is known, and history lives only for the session.
func LoadBuffer ¶
LoadBuffer returns the persisted editor buffer, or "" when there is none.
func NewChatID ¶
NewChatID mints a conversation id from a wall-clock instant: sortable by construction, which lets a listing fall back on id order when a file's Updated stamp is missing, and legible to a user browsing the directory.
The tail is the full nanosecond field. ⟲ new saves the old conversation and the next save mints the new one's id moments later; a coarser tail could collide there, and a collision would overwrite the earlier file.
func RemoveChat ¶
RemoveChat deletes one conversation. A missing file is success: the caller wanted it gone.
func SaveBuffer ¶
SaveBuffer persists the editor buffer. The file is user-only: query scratchpads have a way of accumulating real values.
func SaveChat ¶
SaveChat writes c into dir as <id>.json, then trims the directory back to MaxChats. Rewriting the same id is the normal case — the live conversation is re-saved after every answer — so the write goes to a temp file that is renamed over the old one: a crash mid-write must not leave a half-written transcript where a readable one used to be.
An empty dir, id or message list is a no-op rather than an error. The caller saves unconditionally at teardown, and "there was no conversation" is not a failure.
The files are user-only, like the history: a conversation about a query carries the query, and on connections with ai_rows its result rows.
Types ¶
type Chat ¶
type Chat struct {
ID string `json:"id"`
Title string `json:"title"`
Agent string `json:"agent,omitempty"` // the backend's display name
Model string `json:"model,omitempty"` // the model's display name
Conn string `json:"conn,omitempty"` // the active connection at the last save
Started time.Time `json:"started"`
Updated time.Time `json:"updated"`
Msgs []ChatMsg `json:"msgs"`
}
Chat is one archived conversation.
func (Chat) RestoreNote ¶
RestoreNote is the transcript line shown when a saved conversation is reopened: what the user got back, and what they did not. A reopened conversation is a transcript, not a resumed agent session, and silence about that would let a follow-up get a confidently unrelated answer from a model that never saw the conversation. Both UIs show these words.
type ChatMeta ¶
type ChatMeta struct {
ID string
Title string
Agent string
Conn string
Updated time.Time
Count int // messages, so a row can say how much is in there
}
ChatMeta is the listing view: what a picker row needs, without the transcript. ListChats still reads whole files — they are few and small, and a partial parse would need a second schema to stay honest — but returning only this keeps a caller from holding every conversation in memory to draw a list of titles.
func ListChats ¶
ListChats returns the archived conversations newest first. A file that will not parse costs itself and nothing else: one truncated or hand-edited transcript must not take the whole list down with it. A missing directory is the common case — a user who has never asked anything — and is not an error.
type ChatMsg ¶
ChatMsg is one transcript entry. Role is a string ("user", "agent", "tool", "note", "info", "error") rather than the TUI's integer: an archive outlives the enum, and a reordered constant block must not silently restyle every stored conversation.
type Entry ¶
Entry is one statement the user ran — one line of the history file.
func MatchHistory ¶
MatchHistory keeps the entries whose SQL or connection name holds q, case-insensitively. An empty query keeps everything.
type History ¶
type History struct {
// contains filtered or unexported fields
}
History is the statements the user has run, oldest first, backed by a JSON-lines file: one object per line carries the multi-line SQL that a plain-text history would have to escape, and appending one entry costs one write rather than a rewrite of the file.
It is safe for concurrent use. The TUI touches it from one goroutine, but `dbc web` gives every browser tab its own workspace over ONE shared History (so a query run in any tab, or in the terminal's history file, is recallable from all of them), and those tabs record from their own request goroutines. mu also serializes the file appends, so two tabs recording at once cannot interleave half-lines.
func LoadHistory ¶
LoadHistory reads the history file, skipping lines it cannot parse — a half-written last line from a killed process must not cost the whole history. An over-long file is trimmed back to histMax here, which is the only point at which the file is rewritten rather than appended to.
func (*History) Add ¶
Add records a statement, unless it repeats the one just recorded — running the same query twice while watching a table change should not bury the rest of the history. The entry is kept in memory whether or not the file write works, so a read-only config directory costs persistence, not the session.