From 20ae31fe9fbd9b7036adb98ad8dbac01930cbef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:27:37 -0400 Subject: [PATCH] Chat Sessions: components 241-260 --- artifact_card.go | 6 +++--- capability_cards.go | 6 +++--- chat_empty_state.go | 8 ++++---- conversation_export.go | 10 +++++----- conversation_item.go | 14 +++++++------- conversation_list.go | 10 +++++----- conversation_search.go | 16 ++++++++-------- cost_estimator.go | 11 ++++------- date_separator.go | 8 +++----- feedback_form.go | 8 ++++---- project_knowledge_panel.go | 10 +++++----- project_list.go | 10 +++++----- prompt_library.go | 16 ++++++++-------- regenerate_menu.go | 6 +++--- shared_conversation_view.go | 8 ++++---- stop_generating_button.go | 2 +- suggestion_chips.go | 6 +++--- system_prompt_editor.go | 10 +++++----- .../darwin/conversation_list-dark.png | Bin 2666 -> 19901 bytes .../darwin/conversation_list-light.png | Bin 2668 -> 19233 bytes thinking_indicator.go | 4 ++-- welcome_screen.go | 2 +- 22 files changed, 83 insertions(+), 88 deletions(-) diff --git a/artifact_card.go b/artifact_card.go index 478888f..1bf76d4 100644 --- a/artifact_card.go +++ b/artifact_card.go @@ -10,7 +10,7 @@ import ( "mujicaui/theme" ) -// ArtifactKind is what an artifact holds. The zero value is ArtifactCode. +// ArtifactKind is what an artifact holds; zero is ArtifactCode. type ArtifactKind int const ( @@ -23,7 +23,7 @@ const ( type ArtifactCardOptions struct { Kind ArtifactKind Title string - // Detail is a short fact, such as "Go ยท 120 lines". + // Detail is a short fact, such as "Go, 120 lines". Detail string // Streaming shows the artifact is still being written. Streaming bool @@ -34,7 +34,7 @@ var artifactKinds = [3]struct{ icon, name string }{ {"file", "artifact_card.code"}, {"file-text", "artifact_card.document"}, {"layout-grid", "artifact_card.web"}, } -// ArtifactCard stands for an artifact inside a message; a click or Enter asks to open the artifact panel. +// ArtifactCard stands for an artifact; activating asks to open it. func ArtifactCard(c *ui.Context, opts ArtifactCardOptions) ChoiceResult { if opts.Title == "" { panic("mujica: ArtifactCard needs a Title") diff --git a/capability_cards.go b/capability_cards.go index da12989..fb977cf 100644 --- a/capability_cards.go +++ b/capability_cards.go @@ -9,7 +9,7 @@ import ( "mujicaui/theme" ) -// CapabilityCard explains one thing the assistant can do, with example prompts. +// CapabilityCard explains one assistant capability with example prompts. type CapabilityCard struct { Title, Description string Icon *ui.SVG @@ -30,7 +30,7 @@ type CapabilityCardsView struct { Example string } -// CapabilityCards shows what the assistant can do; clicking an example hands it to the caller. +// CapabilityCards shows capabilities; a clicked example goes to the caller. func CapabilityCards(c *ui.Context, cards []CapabilityCard, opts CapabilityCardsOptions) CapabilityCardsView { if opts.Columns < 0 { panic("mujica: CapabilityCards Columns is negative") @@ -76,7 +76,7 @@ func CapabilityCards(c *ui.Context, cards []CapabilityCard, opts CapabilityCards return v } -// capabilityExample is a quoted example prompt button; it reports a click. +// capabilityExample is a quoted example button reporting a click. func capabilityExample(c *ui.Context, k theme.Tokens, text string) bool { d := Density(c) b := ui.ButtonBase(c).MinHeight(d.ControlHeight()).Padding(d.Unit(), d.Unit()*3).Justify(ui.Start). diff --git a/chat_empty_state.go b/chat_empty_state.go index 38628f9..a386f0a 100644 --- a/chat_empty_state.go +++ b/chat_empty_state.go @@ -8,7 +8,7 @@ import ( "mujicaui/icons" ) -// ChatEmptyKind is why a chat view is empty. The zero value is ChatEmptyNew. +// ChatEmptyKind is why a chat is empty; zero is ChatEmptyNew. type ChatEmptyKind int const ( @@ -20,18 +20,18 @@ const ( ChatEmptyOffline ) -// ChatEmptyStateOptions configure a ChatEmptyState; empty texts use the kind's built-in copy. +// ChatEmptyStateOptions configure a ChatEmptyState; empty texts use built-in copy. type ChatEmptyStateOptions struct { Kind ChatEmptyKind // Query, for ChatEmptyNoResults, is named in the description. Query string Title string Description string - // Action replaces the kind's button text: Start, Clear search or Retry. + // Action replaces the kind's built-in button text. Action string } -// ChatEmptyState explains an empty chat (new, no results, offline) and offers the next step. +// ChatEmptyState explains an empty chat and offers the next step. func ChatEmptyState(c *ui.Context, opts ChatEmptyStateOptions) EmptyStateResult { switch opts.Kind { case ChatEmptyNew: diff --git a/conversation_export.go b/conversation_export.go index e9c6f40..3acf073 100644 --- a/conversation_export.go +++ b/conversation_export.go @@ -13,7 +13,7 @@ import ( "mujicaui/theme" ) -// ExportFormat is the text form of an export. The zero value is ExportMarkdown. +// ExportFormat is an export's text form; zero is ExportMarkdown. type ExportFormat int const ( @@ -22,7 +22,7 @@ const ( ExportText ) -// ExportRange is which messages an export holds. The zero value is ExportAll. +// ExportRange is which messages an export holds; zero is ExportAll. type ExportRange int const ( @@ -35,7 +35,7 @@ const ( var exportRoles = map[SessionRole]string{SessionUser: "user", SessionAssistant: "assistant", SessionSystem: "system"} -// ExportConversation writes messages as Markdown, JSON or plain text; it does not parse their text. +// ExportConversation writes messages as Markdown, JSON or plain text. func ExportConversation(title string, messages []SessionMessage, format ExportFormat) string { var b strings.Builder switch format { @@ -145,13 +145,13 @@ type ConversationExportOptions struct { // ConversationExportView is a built ConversationExport and the export asked for. type ConversationExportView struct { Element *ui.Element - // Exported reports Content is ready for the caller to write, as Format. + // Exported reports Content is ready for the caller to write. Exported bool Content string Format ExportFormat } -// ConversationExport picks a format and range, previews the result and hands the text to the caller to save. +// ConversationExport picks format and range, previews, and hands over text. func ConversationExport(c *ui.Context, s *ConversationExportState, messages []SessionMessage, opts ConversationExportOptions) ConversationExportView { if s == nil || opts.Title == "" { panic("mujica: ConversationExport needs a state and a Title") diff --git a/conversation_item.go b/conversation_item.go index 9d04be3..2846c97 100644 --- a/conversation_item.go +++ b/conversation_item.go @@ -14,21 +14,21 @@ import ( // ConversationItemOptions configure a ConversationItem. type ConversationItemOptions struct { - // Today decides the time stamp's form; zero uses the window clock. + // Today decides the time stamp; zero uses the clock. Today Date Selected bool Disabled bool - // Draggable lets the row be dragged onto a ProjectList as a ConversationDrag. + // Draggable lets the row be dropped on a ProjectList. Draggable bool } // ConversationDrag is the value a dragged conversation carries. type ConversationDrag struct{ ID string } -// ConversationItemView is a built ConversationItem and what the user asked for this frame. +// ConversationItemView is a built ConversationItem and its requests. type ConversationItemView struct { Element *ui.Element - // Opened, Deleted, Exported and PinToggled are requests for the caller to carry out. + // Opened, Deleted, Exported and PinToggled are requests for the caller. Opened, Deleted, Exported, PinToggled bool // Renamed reports a confirmed new title, already in conv.Title. Renamed bool @@ -41,7 +41,7 @@ type conversationItemState struct { type conversationItemKey struct{} -// ConversationItem is a conversation row: title, time, and a menu to rename in place, pin, export or delete. +// ConversationItem is a conversation row with rename, pin, export, delete. func ConversationItem(c *ui.Context, conv *ChatConversation, opts ConversationItemOptions) ConversationItemView { if conv == nil || conv.ID == "" { panic("mujica: ConversationItem needs a conversation with an ID") @@ -118,7 +118,7 @@ func ConversationItem(c *ui.Context, conv *ChatConversation, opts ConversationIt return v } -// conversationItemEditor is the in-place title field: Enter keeps a non-blank title, Escape cancels. +// conversationItemEditor edits the title in place; Escape cancels. func conversationItemEditor(c *ui.Context, k theme.Tokens, conv *ChatConversation, st *conversationItemState, v *ConversationItemView) { d := Density(c) in := ui.TextInputBase(c, &st.draft).Grow(1).MinHeight(d.ControlHeight()-4).Padding(0, d.Unit()*2). @@ -142,7 +142,7 @@ func conversationItemEditor(c *ui.Context, k theme.Tokens, conv *ChatConversatio } } -// conversationItemMenu is a borderless ellipsis button opening the menu build fills. +// conversationItemMenu is a borderless ellipsis button opening a menu. func conversationItemMenu(c *ui.Context, label string, build func(m *PopupMenu)) *ui.Element { b := iconAction(c, icons.Must("ellipsis"), label).Role(ui.RoleMenuButton).Shrink(0) open := ui.Local(b, conversationItemKey{}, func() bool { return false }) diff --git a/conversation_list.go b/conversation_list.go index f2d1a0d..9700a1b 100644 --- a/conversation_list.go +++ b/conversation_list.go @@ -37,7 +37,7 @@ type ConversationListState struct { plan conversationListPlan } -// conversationListPlan is the grouped rows, rebuilt only when the data or day changes. +// conversationListPlan holds grouped rows, rebuilt on new data or day. type conversationListPlan struct { src []ChatConversation day Date @@ -45,7 +45,7 @@ type conversationListPlan struct { built bool } -// conversationListRow is group header g (item < 0) or an index into the items. +// conversationListRow is a header (item < 0) or item index. type conversationListRow struct { group, item int } @@ -60,10 +60,10 @@ type ConversationListView struct { // Changed reports a newly chosen conversation, in State.Selected. func (v ConversationListView) Changed() bool { return v.changed } -// Submitted reports Enter or a double click on the chosen conversation. +// Submitted reports Enter or a double click on the choice. func (v ConversationListView) Submitted() bool { return v.submitted } -// ConversationList groups conversations into pinned, today, yesterday, past 7 days and older, building only rows in view. +// ConversationList groups conversations by recency, building only visible rows. func ConversationList(c *ui.Context, s *ConversationListState, items []ChatConversation, opts ConversationListOptions, row func(conv ChatConversation)) ConversationListView { if s == nil { panic("mujica: ConversationList needs a state") @@ -181,7 +181,7 @@ var conversationListGroups = [5]string{ "conversation_list.week", "conversation_list.older", } -// conversationListGroup is the group index of conv: pinned, today, yesterday, 7 days, older. +// conversationListGroup returns the recency group index of conv. func conversationListGroup(conv ChatConversation, today Date) int { day := DateOf(conv.Updated) switch { diff --git a/conversation_search.go b/conversation_search.go index 44bc501..f711f30 100644 --- a/conversation_search.go +++ b/conversation_search.go @@ -20,9 +20,9 @@ type ConversationSearchHit struct { // ConversationSearchOptions configure a ConversationSearch. type ConversationSearchOptions struct { - // Loading shows the caller's search for the query is under way. + // Loading shows the caller's search is under way. Loading bool - // Error is the caller's failed search; it shows with a retry button. + // Error describes a failed search, shown with a retry button. Error string Placeholder string Label string @@ -32,9 +32,9 @@ type ConversationSearchOptions struct { // ConversationSearchView is a built ConversationSearch and this frame's events. type ConversationSearchView struct { Element *ui.Element - // QueryChanged asks the caller to search again for the new query. + // QueryChanged asks the caller to search for the new query. QueryChanged bool - // Chosen is the hit opened by a click or Enter, empty for none. + // Chosen is the hit opened this frame, or empty. Chosen string Retry bool } @@ -47,7 +47,7 @@ type conversationSearchState struct { type conversationSearchKey struct{} -// ConversationSearch searches titles and content; the caller supplies hits for the query and the matches are highlighted. +// ConversationSearch shows caller-supplied hits for a query, highlighting matches. func ConversationSearch(c *ui.Context, query *string, hits []ConversationSearchHit, opts ConversationSearchOptions) ConversationSearchView { if query == nil { panic("mujica: ConversationSearch needs a query") @@ -97,7 +97,7 @@ func conversationSearchList(c *ui.Context, s *conversationSearchState, q string, s.rows.Key = func(i int) any { return hits[i].ID } s.rows.Label = func(i int) string { return hits[i].Title } s.rows.Selected = &s.cursor - // Rows have one height, so the list can size itself before measuring them. + // Rows share one height, so the list sizes itself early. rowH := d.ControlHeight() + FontSize(c, theme.CaptionSize)*theme.LineHeight + 2 restore := dataSelectTheme(c, 0) list := ui.List(c, &s.rows, len(hits), func(i int) { @@ -134,7 +134,7 @@ func conversationSearchList(c *ui.Context, s *conversationSearchState, q string, return chosen } -// conversationSearchSpans splits text into spans, marking each case-insensitive match of q. +// conversationSearchSpans marks each case-insensitive match of q in text. func conversationSearchSpans(text, q string, k theme.Tokens) []ui.Span { r := []rune(text) var spans []ui.Span @@ -152,7 +152,7 @@ func conversationSearchSpans(text, q string, k theme.Tokens) []ui.Span { return spans } -// conversationSearchMatches returns rune ranges of case-insensitive matches of q in text. +// conversationSearchMatches returns rune ranges where q matches, ignoring case. func conversationSearchMatches(text, q string) [][2]int { t, p := []rune(text), []rune(q) if len(p) == 0 { diff --git a/cost_estimator.go b/cost_estimator.go index 553aa80..b70e411 100644 --- a/cost_estimator.go +++ b/cost_estimator.go @@ -7,11 +7,10 @@ import ( "github.com/egoist/mygo/ui" - "mujicaui/internal" "mujicaui/theme" ) -// CostEstimatorOptions configure a CostEstimator. Prices are per million tokens, from the caller. +// CostEstimatorOptions configure a CostEstimator; prices are per million tokens. type CostEstimatorOptions struct { InputTokens, OutputTokens int InputPrice, OutputPrice float64 @@ -21,7 +20,7 @@ type CostEstimatorOptions struct { Digits int } -// CostEstimate is the cost of the input, the output and both. +// CostEstimate is the input, output and total cost. type CostEstimate struct{ Input, Output, Total float64 } // EstimateCost prices token counts at per-million rates. @@ -31,7 +30,7 @@ func EstimateCost(inputTokens, outputTokens int, inputPrice, outputPrice float64 return CostEstimate{in, out, in + out} } -// CostEstimator shows input and output tokens and their estimated cost at the caller's prices. +// CostEstimator shows token counts and cost at caller prices. func CostEstimator(c *ui.Context, opts CostEstimatorOptions) *ui.Element { if opts.InputTokens < 0 || opts.OutputTokens < 0 { panic("mujica: CostEstimator token counts must not be negative") @@ -76,9 +75,7 @@ func CostEstimator(c *ui.Context, opts CostEstimatorOptions) *ui.Element { cell(msg(c, "cost_estimator.output"), k.Text, false) cell(strconv.Itoa(opts.OutputTokens), k.Text, true) cell(money(est.Output), k.Text, true) - ui.Box(c).ColumnSpan(3).Height(9).Draw(func(p *ui.Painter, r ui.Rect) { - internal.Rule(p, r.X, r.X+r.W, r.Y+r.H/2, k.Ornament) - }) + ui.Box(c).ColumnSpan(3).Height(theme.BorderWidth).MarginY(d.Unit()).Background(k.Ornament) ui.Text(c, msg(c, "cost_estimator.total")).Font(theme.SerifFont).TextColor(k.Text) cell(strconv.Itoa(opts.InputTokens+opts.OutputTokens), k.Text, true) ui.Text(c, money(est.Total)).Bold().TextColor(k.AccentText).JustifySelf(ui.End).FontFeatures("tnum") diff --git a/date_separator.go b/date_separator.go index 1d73c87..7e03af2 100644 --- a/date_separator.go +++ b/date_separator.go @@ -16,7 +16,7 @@ type DateSeparatorOptions struct { Today Date } -// DateSeparator marks where a day's messages begin: Today, Yesterday, or the full date in the window's language. +// DateSeparator marks where a day's messages begin, localized. func DateSeparator(c *ui.Context, day Date, opts DateSeparatorOptions) *ui.Element { if !day.Valid() { panic("mujica: DateSeparator needs a valid date") @@ -30,9 +30,7 @@ func DateSeparator(c *ui.Context, day Date, opts DateSeparatorOptions) *ui.Eleme label := dateSeparatorLabel(c, day, today) row := ui.Row(c).FillWidth().Gap(u*3).AlignItems(ui.Center).Padding(u*2, 0).Role(ui.RoleText).Label(label) line := func() { - ui.Box(c).Grow(1).Height(9).Draw(func(p *ui.Painter, r ui.Rect) { - internal.Rule(p, r.X, r.X+r.W, r.Y+r.H/2, k.Border) - }) + ui.Box(c).Grow(1).Height(theme.BorderWidth).Background(k.Border) } row.Children(func() { line() @@ -59,7 +57,7 @@ func dateSeparatorLabel(c *ui.Context, day, today Date) string { msg(c, fmt.Sprintf("calendar.month_%d", day.Month)), day.Day, day.Year) } -// dateSeparatorWhen is a short time stamp: the clock today, else the relative day or date. +// dateSeparatorWhen is a short stamp: time, day or date. func dateSeparatorWhen(c *ui.Context, t time.Time, today Date) string { day := DateOf(t) if day == today { diff --git a/feedback_form.go b/feedback_form.go index 4edcd75..0b7e0b5 100644 --- a/feedback_form.go +++ b/feedback_form.go @@ -10,7 +10,7 @@ import ( "mujicaui/theme" ) -// FeedbackRating is the thumb the user gave. The zero value is FeedbackUp. +// FeedbackRating is the thumb given; zero is FeedbackUp. type FeedbackRating int const ( @@ -18,7 +18,7 @@ const ( FeedbackDown ) -// FeedbackValue is what the user wrote: chosen reasons and a comment. +// FeedbackValue is the chosen reasons and a comment. type FeedbackValue struct { Reasons []string Comment string @@ -29,7 +29,7 @@ type FeedbackFormOptions struct { Rating FeedbackRating // Reasons replaces the built-in reasons for the rating. Reasons []string - // Submitting shows the caller sending the feedback and blocks the form. + // Submitting shows the caller sending; it blocks the form. Submitting bool Disabled bool } @@ -45,7 +45,7 @@ var feedbackReasons = [2][]string{ {"feedback_form.down.wrong", "feedback_form.down.unhelpful", "feedback_form.down.unsafe", "feedback_form.down.style"}, } -// FeedbackForm asks why after a thumbs up or down: reason chips and a comment, then Submit or Cancel. +// FeedbackForm asks for reasons and a comment after a rating. func FeedbackForm(c *ui.Context, value *FeedbackValue, opts FeedbackFormOptions) FeedbackFormView { if value == nil { panic("mujica: FeedbackForm needs a value") diff --git a/project_knowledge_panel.go b/project_knowledge_panel.go index 8c9208b..4a2cce1 100644 --- a/project_knowledge_panel.go +++ b/project_knowledge_panel.go @@ -11,7 +11,7 @@ import ( "mujicaui/theme" ) -// KnowledgeFile is a file in a project's knowledge, sized in tokens by the caller. +// KnowledgeFile is a project knowledge file, sized in tokens. type KnowledgeFile struct { ID, Name string Tokens int @@ -26,16 +26,16 @@ type ProjectKnowledgePanelOptions struct { Disabled bool } -// ProjectKnowledgePanelView is a built panel and what the user asked for. +// ProjectKnowledgePanelView is a built panel and its requests. type ProjectKnowledgePanelView struct { Element *ui.Element // Add asks the caller to pick files to add. Add bool - // Removed is the ID of a file to remove, empty for none. + // Removed is the ID of a file to remove. Removed string } -// ProjectKnowledgePanel lists a project's knowledge files with add, remove and how full it is. +// ProjectKnowledgePanel lists knowledge files with add, remove and usage. func ProjectKnowledgePanel(c *ui.Context, files []KnowledgeFile, opts ProjectKnowledgePanelOptions) ProjectKnowledgePanelView { if opts.Capacity <= 0 { panic("mujica: ProjectKnowledgePanel needs a positive Capacity") @@ -90,7 +90,7 @@ func ProjectKnowledgePanel(c *ui.Context, files []KnowledgeFile, opts ProjectKno return v } -// projectKnowledgeMeter shows used of capacity; near and over the limit add a word and an icon, not only color. +// projectKnowledgeMeter shows usage; limits get words and icons. func projectKnowledgeMeter(c *ui.Context, k theme.Tokens, used, capacity int) { d := Density(c) frac := float64(used) / float64(capacity) diff --git a/project_list.go b/project_list.go index 4c21553..6c73018 100644 --- a/project_list.go +++ b/project_list.go @@ -12,7 +12,7 @@ import ( "mujicaui/theme" ) -// ChatProject is a project that holds conversations; Folder groups projects, empty for none. +// ChatProject holds conversations; Folder groups projects, empty for none. type ChatProject struct { ID, Name, Folder string } @@ -23,7 +23,7 @@ type ProjectListOptions struct { Disabled bool } -// ProjectListState is the chosen project and which folders and projects are open. +// ProjectListState is the chosen project and open folders and projects. type ProjectListState struct { // Selected is the chosen project's ID; empty for none. Selected string @@ -38,12 +38,12 @@ type ProjectListView struct { Changed bool // Opened is a conversation the user opened, empty for none. Opened string - // Moved asks the caller to move conversation MoveID into project MoveTo. + // Moved asks the caller to move MoveID into MoveTo. Moved bool MoveID, MoveTo string } -// ProjectList shows folders of projects and their conversations; drop a ConversationDrag on a project, or use Move to, to file it. +// ProjectList shows project folders and conversations; drops move them. func ProjectList(c *ui.Context, s *ProjectListState, projects []ChatProject, conversations func(projectID string) []ChatConversation, opts ProjectListOptions) ProjectListView { if s == nil || conversations == nil { panic("mujica: ProjectList needs a state and conversations") @@ -91,7 +91,7 @@ func ProjectList(c *ui.Context, s *ProjectListState, projects []ChatProject, con return v } -// projectListFolder is a folder heading on a CollapsibleBase trigger, which reports expanded. +// projectListFolder is a folder heading on a CollapsibleBase trigger. func projectListFolder(c *ui.Context, title string, open *bool) *ui.Element { k := Tokens(c) d := Density(c) diff --git a/prompt_library.go b/prompt_library.go index 827f12c..738566e 100644 --- a/prompt_library.go +++ b/prompt_library.go @@ -13,7 +13,7 @@ import ( "mujicaui/theme" ) -// PromptTemplate is a reusable prompt; {{name}} in Body is a variable to fill before inserting. +// PromptTemplate is a reusable prompt; {{name}} marks a variable. type PromptTemplate struct { ID, Category, Title, Body string } @@ -24,7 +24,7 @@ type PromptLibraryOptions struct { Disabled bool } -// PromptLibraryState is the library's category, search, chosen template and variable values. +// PromptLibraryState holds category, query, chosen template and variable values. type PromptLibraryState struct { // Category filters templates; empty shows all. Category string @@ -34,7 +34,7 @@ type PromptLibraryState struct { shown promptLibraryShown } -// promptLibraryShown caches the filtered templates until the data, query or category changes. +// promptLibraryShown caches filtered templates until data, query or category change. type promptLibraryShown struct { src []PromptTemplate query, category string @@ -43,10 +43,10 @@ type promptLibraryShown struct { built bool } -// PromptLibraryView is a built PromptLibrary and the text inserted this frame. +// PromptLibraryView is a built PromptLibrary and inserted text. type PromptLibraryView struct { Element *ui.Element - // Inserted is the chosen template with its variables filled, empty for none. + // Inserted is the filled template, or empty. Inserted string } @@ -70,7 +70,7 @@ func PromptLibraryFill(body string, values map[string]string) string { }) } -// PromptLibrary browses templates by category and search, previews one, fills its variables and inserts it. +// PromptLibrary browses, previews, fills and inserts prompt templates. func PromptLibrary(c *ui.Context, s *PromptLibraryState, templates []PromptTemplate, opts PromptLibraryOptions) PromptLibraryView { if s == nil { panic("mujica: PromptLibrary needs a state") @@ -152,7 +152,7 @@ func promptLibraryCategory(c *ui.Context, k theme.Tokens, s *PromptLibraryState, } } -// selectableRow is a focusable row chosen by click, Enter or Space, marked with Selection and a diamond. +// selectableRow is a focusable row marked chosen with a diamond. func selectableRow(c *ui.Context, k theme.Tokens, key, label string, chosen bool) bool { d := Density(c) b := ui.ButtonBase(c).Key(key).MinHeight(d.ControlHeight()).Padding(0, d.Unit()*2).Gap(d.Unit() * 2).Justify(ui.Start). @@ -176,7 +176,7 @@ func selectableRow(c *ui.Context, k theme.Tokens, key, label string, chosen bool return b.Clicked() && !b.IsDisabled() } -// promptLibraryPreview shows the filled body, a field per variable, and Insert once all are filled. +// promptLibraryPreview shows the body, variable fields and Insert. func promptLibraryPreview(c *ui.Context, k theme.Tokens, s *PromptLibraryState, t PromptTemplate, disabled bool) string { d := Density(c) vars := PromptLibraryVariables(t.Body) diff --git a/regenerate_menu.go b/regenerate_menu.go index b224406..95fe6c4 100644 --- a/regenerate_menu.go +++ b/regenerate_menu.go @@ -29,7 +29,7 @@ type RegenerateChoice struct { // RegenerateMenuOptions configure a RegenerateMenu. type RegenerateMenuOptions struct { - // Models lists models to retry with; Current is marked and left out. + // Models lists models to retry with; Current is left out. Models []string Current string // Variants replaces the built-in Longer, Shorter, Simpler and More detailed. @@ -37,14 +37,14 @@ type RegenerateMenuOptions struct { Disabled bool } -// RegenerateMenuView is a built menu and the retry chosen this frame. +// RegenerateMenuView is a built menu and the chosen retry. type RegenerateMenuView struct { Element *ui.Element Chosen bool Choice RegenerateChoice } -// RegenerateMenu offers regenerate, retry with another model, and variants such as longer or shorter. +// RegenerateMenu offers retry, another model, and rewrite variants. func RegenerateMenu(c *ui.Context, opts RegenerateMenuOptions) RegenerateMenuView { variants := opts.Variants if variants == nil { diff --git a/shared_conversation_view.go b/shared_conversation_view.go index 00676df..cda049f 100644 --- a/shared_conversation_view.go +++ b/shared_conversation_view.go @@ -13,7 +13,7 @@ import ( "mujicaui/theme" ) -// SessionRole is who wrote a message. The zero value is SessionUser. +// SessionRole is who wrote a message; zero is SessionUser. type SessionRole int const ( @@ -22,7 +22,7 @@ const ( SessionSystem ) -// SessionMessage is one message of a saved conversation, as plain text. +// SessionMessage is one saved message, as plain text. type SessionMessage struct { Role SessionRole // Author names the writer; empty uses the role's name. @@ -39,7 +39,7 @@ type SharedConversationViewOptions struct { Disabled bool } -// SharedConversationView shows a shared conversation read-only, with who shared it and when; it reports Copy link. +// SharedConversationView shows a shared conversation read-only with Copy link. func SharedConversationView(c *ui.Context, messages []SessionMessage, opts SharedConversationViewOptions) ChoiceResult { if opts.Title == "" { panic("mujica: SharedConversationView needs a Title") @@ -78,7 +78,7 @@ func SharedConversationView(c *ui.Context, messages []SessionMessage, opts Share type sharedConversationKey struct{} -// sessionMessageView is one read-only message: author, time and selectable plain text. +// sessionMessageView shows one read-only message with author and time. func sessionMessageView(c *ui.Context, k theme.Tokens, m SessionMessage) { d := Density(c) if m.Role < SessionUser || m.Role > SessionSystem { diff --git a/stop_generating_button.go b/stop_generating_button.go index c63739c..0d5fe29 100644 --- a/stop_generating_button.go +++ b/stop_generating_button.go @@ -17,7 +17,7 @@ type StopGeneratingButtonOptions struct { Disabled bool } -// StopGeneratingButton asks to stop a reply while one is generating; Cmd+. (Ctrl+. elsewhere) does too. +// StopGeneratingButton asks to stop generating; Mod+. does too. func StopGeneratingButton(c *ui.Context, opts StopGeneratingButtonOptions) ChoiceResult { if !opts.Generating { return ChoiceResult{} diff --git a/suggestion_chips.go b/suggestion_chips.go index 0b4fcc7..c11de04 100644 --- a/suggestion_chips.go +++ b/suggestion_chips.go @@ -12,7 +12,7 @@ import ( // SuggestionChipsOptions configure SuggestionChips. type SuggestionChipsOptions struct { - // Send makes a click send the suggestion; by default it fills the input. + // Send makes a click send instead of filling the input. Send bool // Label names the group, such as "Follow-up questions". Label string @@ -24,11 +24,11 @@ type SuggestionChipsView struct { Element *ui.Element // Chosen is the suggestion clicked, empty for none. Chosen string - // Sent reports that Chosen should be sent now; otherwise it is already in the input. + // Sent asks to send Chosen now instead of filling. Sent bool } -// SuggestionChips offers starter prompts or follow-ups; a click fills *input, or with Send asks to send. +// SuggestionChips offers prompts that fill *input or ask to send. func SuggestionChips(c *ui.Context, input *string, suggestions []string, opts SuggestionChipsOptions) SuggestionChipsView { if input == nil { panic("mujica: SuggestionChips needs an input") diff --git a/system_prompt_editor.go b/system_prompt_editor.go index 7fcba8d..ffb0792 100644 --- a/system_prompt_editor.go +++ b/system_prompt_editor.go @@ -17,23 +17,23 @@ import ( type SystemPromptEditorOptions struct { // Default is what Restore default puts back. Default string - // Saved is the text last saved; a different value shows as unsaved. + // Saved is the text last saved; differences show as unsaved. Saved string - // MaxLength is the character limit; over it, Save is blocked. Zero is none. + // MaxLength limits characters, blocking Save beyond it; zero is none. MaxLength int // Saving shows the caller's save in progress and blocks editing. Saving bool Disabled bool } -// SystemPromptEditorView is a built editor and what the user asked for. +// SystemPromptEditorView is a built editor and its requests. type SystemPromptEditorView struct { Element *ui.Element - // Save asks the caller to save *value; Restored reports the default was put back. + // Save asks to save *value; Restored reports the default returned. Save, Restored bool } -// SystemPromptEditor edits a system prompt with a character count, restore default, an unsaved mark and Mod+S to save. +// SystemPromptEditor edits a system prompt with count, restore and save. func SystemPromptEditor(c *ui.Context, value *string, opts SystemPromptEditorOptions) SystemPromptEditorView { if value == nil { panic("mujica: SystemPromptEditor needs a value") diff --git a/testdata/snapshots/darwin/conversation_list-dark.png b/testdata/snapshots/darwin/conversation_list-dark.png index 493b24742e87ace160c3ca37309af0251b1b537a..775de36e4bcff8fd52fe6f1cab0d69fa44d808d9 100644 GIT binary patch literal 19901 zcmd43bxP0*lMS;%vx?(XiexWnQOi@UqK4({&m?sBu=cNcLH7w?Z3 z7w_&x^~7{{^mJ8bW&ScNGi$=+WJD2Qv0=Y_`GO!WCZzD?3z!+`lMe$9x)WbPDgEV( zbfCBpzmi+V*}ArrrZMi&^@O+0|rNQq6=5CQx!f`AzOkd=m^Yy%y7G+7K)sg5wh+^?u7 zIYAgk6nMywB~sA}WEcD88( z81uAtT@m{#M_HXAG;p5wRc5d z`o1soOsYcrZ!;fqu5fAJlo$`Nqi{R48($3;uYvme5{(&Rxc1}B**y@s66|s$DVwu( zofQd#TEP7?PcfOR3Rq;a;J+^zv7F&;V&OqHh%bT;YKdI>N~u3GWx|AtbgYYMhillI zwBBA;8=G`ISK=HN3!*!3X4)CEKRZZ@Djz*RTiy3dHRXl8wS^lTXN^c#W?gr$rmG^M zy!$HCJTGfR@)XUJ?r+F4004WtozQEvp*4quy|eXqGyRLf!NLVsKC6XOG}I_4U&6Vi zxwQ74w+KtOOPr9r>Eo3OGB_JpA}YMElROt|u2)N|A-pA2^rb5bPcsp#H?^~y+*j8h z0YeZy;?Gnvxc_?S2ZoWby01@q3ua%w>YKSq!Jxpdzi=Qy_6dZ-S$E{v<+qu3XC*nc zNsEfhurS?^W@!-h*B35Zi|-e-=w^J_Gofa_&k*T;#q9>ey#^P(W&iQ+chx@rJpEy# zSfmKWlLx)Q!RXrN*hYey*?K=Dz)gt&(#Jmnj-!t)0}S9<^p) zZCgvlT3Gg7qxB?wEm@(>TzsL%`zn%GdV(euR$5xg=}Uug8bhtWu2I*nqr_BxoHgzH zVO^FM60Yl(J7Xp%8Bxbsb-#Hx{zf1(c}D4)v~OxtuXT=RvDCoo9?-(Dc!4Cv`@53OL=N@I*n$!(xx>(Av7 z_}CJ%5Bx?XDW;(izRFB_iGfbGZV|s2At8Ky0OVP}x#*J2m1o7CQ~z!9u@PRKUrvB^ zFD+}CR#;? zdc}Dim4-xwpeDNgZ;66}MtJ|ao3TocqP-~^4Xky&_};nqjZC9eX(m7+X8smAw`r#n~G6>x8B6^*_Yteaiij> zJ1?gh4`JNN8PKocaq{M9V%i?s3n`o`{Z$5DF?!TQ^_Bg@&Gmk;`!8)s7e|R4CGo>0 zZe*PNbjI?x6O?-!q79VOm;aV3g$S#4v1gQ?8Gm|Y_FDPL z_280pj_1GWOCKHjwlFb4MdKtmmva<>m5d_5$*D0dN;2`8hT*+)Cebx;dpN!@i!(bl zEc?d6%638F^%}dUXWK!gR}v5)ZHUIle0nl%Sd*4kp0XRul2*EUO4mg?c((kb;XL3I zPfW1s@+a-?3!OH=wK61j`)P*h;+Xkci#t2AK(`-yUXyzcQC!T_1OOsat3hE_R z*T>>gFEc5N_B^Tbn-11iSAJR9tH25wd^|hL4Sl}$$*U`&_teZm-IOKp0GTN@Aui3_ z#>9)zDOotNB(N(7R~=D`r_i zy9`XYUA?v_v0`a^R+lHeHdcGd$*RGS6>_SonsqLkANRz8a!(uQS-9+Jr6>E;?z8J! zSWX&6YQ5Xd4ULV52kQ*1j4xKMdjpX`VtG>uiG@@}`3IMUt{>lbUEP}7JGj-Q?v?%0 zZ7`BcisdFUB5%~U&>e3=NH()HE>0EI-7l}$DNqFZX7+&?DvNU!uxq5TPGlcm+pkM~ zjt9*I+)m&`q{%>4$Z|`oy+_PH+5~g+>>_xa)9dS*-W;xT>GWTqfA1PYgrufk58m_h zsKVI0yDnJ6faWhL=>XiXI0hRg2gC>-4jUDfgOQ)9S-4Hdv)Z>eaGA?4*L-ip$v}5> z*Qh82_DWydGc|3DvO^SQ`JAm)K81y(1Bn7AfSIsuFbj(sNdDlVI_eZ7>9OcIGfj-{ z_^hqZQJ{JGI}|7rlF-KGs!A^wALJp364EuR^*%D{6Pn*ngE%-^u+&wrc)s9%LHsgQ z_eIo2=3+?SGgaz7kRaN^+yW*jx06CQc;t#qjQq5vP;PjcqviY@4>tz?k)ISbUXck! zpy%@WD|CgKxivKvm9>pcO=E3$7kgZBDHSF;f!o|GeF8}beBqxFA!2^Y6WphTD)D1w zW!8>lm_7zCq8BgQx$$&k-00Q%lK9Q5jE@D)x`p!fU|$%J!hZo|VdFZqXb~UBiM7gP z^Zr0>pRs+;H5}R|9?=wq;ot9x&6GTHWw$#=NOTqNhp3=ONQED?l}u4kB^Gja|mdq40O>HG1K z>H-rO73Ec<_O^T>79_w=h+HU>%JL<)XM4}gF|T+pIo}B?03WLcxX(&~q1Yiv*v}WT zO$jfm4|s_T&NJQdv9Pv&)NK)xlq6w3EtLuu#(gqsshUh0C$gpYs5`@`0!ih7kb?wN zqJ*=VEO{TB zHH^mDZf3G>vyY+LBypewu$h(h9wcx30(<~OJPvhj#UITpk9`%Ea5mqmze|+6%EHGR zrg#m%L!nDb*o2&~(W?%#+rlCuYV_=`uh-tsW}(Ama%N7g%rv@4|1Kz4u4XxryK4y~ zg@#VAj$v<}%t*U3ytzg*eZ644B8ZhR*5~RSFt9&rbZ&F8L2(Y}gMZiJFhGL@yMBY_ z*L9`wc~^62Fiu|EN1*=TGMF-Ff{&N@%y5mw%F9f#Yp*Zo-g_rfgkWV2R>;9TV#Vjf zyvt(StJ2Mv8)m|Km`V^MWk{8ZEq1)p+O0BRI1(%I@+bHPzSD*0LXP2H5ajj%J^>Jk z)|b`Zve5lZlvGkK*~E()uzW`i1^H;x zmr~MJVJY+q7bs|Dj(2dJ_0OH!hpApO)l>bm!#AV#R7R*H5q%hvt#y@29_|2rCU+nG zf%*|N=_q{nwR}0!u&_xRGmAdB|N0hg1Gu7-jF!^Y*N47}IdaGX;Ia9Unzx1u_(3zi zT*R?|<={EoQh)uDAd5qy{cy(m4+q`Md!}gw;A^J?hc@uZ(;%KOaM;KBQ9q#Rh5>hr`rixidxEji+UyNRo-?Lza2um-WF0hs(+N;kRr?o~?(7&{>=-5Ksu3mXuPda=&H1W% z-7QOg8*v9>wA&cuODZb!!;lr~Eb7e^0W_NdsfQR>P$~i-{|C07ie9|zrg&;WsgFLE zQxKh~U!e$1TnLMN2>5<;YoC5*=J8~dR*9$>#V-i6US&cZVJPk`v&BvezKK3?BS(=hN)W@3&)*hjN>S9`6^Nq(3TRgeJrw9-#+h&e39 z3Sa6ij6@al+Bv9=6P@8sbN$@S6*`N{qceK`U%w(n{>))n^jVSw`&IIGo!9vA11Dze zB!{^RIoH>ObR`8Vs`s+;B}aV;ukPK~XPnxbC|>7UGW>VA5+AWmWoq1S5e~5w%jf6K z(BKE_;W)`qON!Dv(j$y5MT_#rAyH%11}n_XtkPL$w}svc7FSt!>D)-s9 zmJP%HR&hPpCc!6`(YEc;Ax&lp88L~grZ2A%V;;%DK(dIW&(Dx6Q6@;d8An1z5PcWl za|mk1mZrH-Augz7qjuZJY3l@pTtb5TW%->+(cDPDU7tOt+uwZ+9yty9$W2xVdqU#8 zD%XX050xXe&$hU|PuMw!ZNG{#?+OQwvWhz!ds* z3|p-BO+MFO;w;J{?<;HN?-3kybXFIbVt(B=5ux}+GcStpC=)XYMgEc=z5W8)zzvLt z)b;(F;t^*4U1s^iS1#YPmct8<$8zS^Ygc-%TL|3W5O!Y>yTJfoptrvK|9ml|`6a|( zpbP0-q^)fnk9|T#SoqjENpIy(4^*404Fl7hDU_V*S7&88)qU^svYm#p`NbhY6;};~ zzKR@sMKQCTbESi~vYwrhx$b7q@<*e2fowB zN!_D5@57JE%F61FbOd-PCR>mPU@P1%j1|(9tf`!6w||15tQ=?YL>7|`1BssF$u@46 zcZ1mS-COpEb|z0*0<7O_@1#mi_b$fHr&iN zKTX6<6F6=H92r5t1?WxprqWhad<*-zqS4;qmtCtybMP|f820s+_nG5oMQ!ei@pQTO zwkB|N6!dx)W*pRZKUtx8isf5X&!&bZ-B#Sb?dkyVH<4uxGz)P7)+{yAaWtFhRTd^JqV6a<>l?e_I@7; zwoMNX29xqo(t3Lw%BVbb=DE8I*M4q%e$I$fQ#+badE?oWzO0cEYcx2hclme+@NUMX zC5;%=Ao|ofjvZ|a88Mh`HQP5HPJA}ApCO}sgG|%`YJ)s^Ah0a-XyZNPgL*M+z`(c5 zfxy;PQgU`}&CBHc0tSxH>v~4nnsz*e+LVCXtsiMgOvu8bba!xN)TPB`{L&RO=t7)#!pNT?i%}&DH`<}M6bWLuagR)Xe`bWfe*Uy~xcCEQ%2uD*{ zKIa$D1BHKj9j(*Xb5Kpzw(WE|G-987dnZ0aS5RouKR|J~kIum%bM>fMUY2l`uo$CCY(xC%-dRkg4$kc-f*yy?56URb9 zXkdx&srhMic|hxlj*gCIK+nR$mYRyl;Y{o=mf?N5UDfqbm^W^CAf6V=FC-H^yD{AZ zujuL7c9;0_a4sn{Ki?~H>@DZ+ZtUY@ub%qLA-cGsLi$EvZeij5gRgPoFatGS?LbNP z_V)Vf_Wt|AoQdh7it;zwG3gX$BYm4%wkbX`*4?GBql@vo_#a`MKp-_4w9q=b^Uv^) zGf5q#JGZ;yZSp~pAmKPo{I=^n(mbKBU*m9zsJR}WMjVCmb1F^r%@x$tdQWam@UH=Y zrTIm68OI0YFrh}03X=)D1fCsvW%-RZPeW&uVq4$1P~-zq67xsey!xn?q( z{n;R9|2#RtHaXdZ_50h~zcjqKs;#DY+OZaeRY*%@>FnH)JE>cXL*f3NnB3k@=+F^0cU29dY1K2AMUp?9+qr>p`_PiSm zb+x?(t-Nmfm$La>s&V)x9+Kl`{CjnmtVWnY0R$nT%*^#il;!0XE*HG3dwV3s|#Ir3wZ_xVw9B<`VH(Sozz9=mY;XLr{I#0 z&d$Dv>0F57b6T4fU!Ic@?Y=|>{*0#>#;B=hAR}f^zTmqM%Pwj@+}pEWYkySaoBaEW z48X%b2-`n%3>$MDwLseaypMOn9tjq;R*(U$W{`2!mdX#}A zzJ9ITm38;XTv@sI(06XAOTvNG$q6S<Em#IZ!)NT8L?2{v$G-gY zQFbf*NbI6)-zq-g*6WUM2|v+9`zAyQm@xB)GTt;99fJ*r4+sy6RZ-fr_Lo>> zUiw0i=BJcQWjg&Ztys*4RCUwR;C;^VIxRdu(7+J8ud}+hw|OV~ymfSZ?6+3_aNM>( zob$2$Qa#;b=Q1`n_%wlU!g?9G`#C0GWl$R5H;ke(Lnkb(o55%)?(xENu!jSKhgVS| zg9rh2)c$~|%bN8mRE!s~QgbPOzLdT=5y8o|&aDDM8*%7i(8^#GT@qwPu(SVI88SO9 z#|f+SjrmlS$;x(pI`?lcEHt(_7Sdm9Z5_1Kifgp{AZ!jn>@PIpV{#4~cLnveUY3kw)$yx#b{RBH_s3XSAD6`{U0uHl+z3nQEoKV(k(8!0s*1(5@9cGaFA&>KjIH(k!JI+l{Jqv(_8 z>+e4Tkz_r6!PtkB4+-wgV`#fs$V zwD|y6f$y{4pU3;h3sZz2EmuE4f$-@*&8Ex!J&fqPsd82F*kpu74Z^J|X>IkZTlz%h z2FKoj#r9e$zKf$4P^6LEl@$33$>)P#uDa_;q)?({rPh5t_`b~P+sf+d!4Rx)?%3%u zR`2i>(&nZNA`JL@Y01ru`QdhGM`dGdIYTHROrV^D0(gjFgYjwfT03#zRASgf`g-TSU4=7c?tFZgNed5x zqB2y8qhnmlT9$|$-$CIE#`|tRBnbb$8e(ev`0VT^CC=iaV7ihPcT;|Yv9Z*RJ@?zu zar!~~wfYD9W3F#VaJ(*ihU-JkQ4KZ#-KuFxCEJ&dF&sYQV?<<$AI9x2DwYE$#rtm;v77TT#Y>;TyUOjNpfJ)OMiKLd;ddJM<{+S==J6E=Zzo^AQLxh5Iq zS!~?J8rDbZW%DP7#4}(46qvz8fyWv7)0E$>^z4c|w3)o1v|Bv#a`>fm7}cv1U=uH( z1*j5p7(TSH2J|${STyofgTeXFo}c9G?bQtor0&)@;C}SeX}6jI7x4Ck3IFi3+|J9c ze}^^J_BJ%=ZU6Jj?edHa`XT)ZXT`jl$>^VlF%!wWK5}Nee2NnxI+w9 zHcHL*Er=ncQngQIA-kTeor7uA@m!_Fe~cw;%FK$6>_o&;#Dm=S5-}-~1elkmxDzb!pkD z<864b(G6==KJL?9N8KUa?Q=AJg7Ez+0?oZ#lQ`?}@($kaE^p9qD+VpV?>r*l9%$*Y|H}~-*pW|Qg&15Vz zW_>Tp=H`nd2gS9uDl|6h)i&sf0xr)`tv57<<)JRvB zHLK-qde>|o*VMk6wQ3(_M@i3m=x~LJO49@p}ps(KVt;!rNK?7vQgA=CX zcVVc(^5*qiu8zZuDABvFm-{cTuQVE%IP8295xbrbJ|&w{SlDsUPp{wI5*B;gqw^I}L+ z6?uMf8ytj{^>ww^bJK?l()a1qq3GmfUeHQilLrAlxxJ;Dnx2D#mz&#Fp6I-X$4??&o67t;&&9S^(LvF_oB4&;XK7^CpMR$lyrcWf8XMWzv`&*25DRW< z>cNSOModaru@g@gadooi>r$kSySIkVq3ULe&`n&WQl+3WWe70uk0pv`Be}3iqJRwx z77YObg04(-8x!reUS0v?Ch{e7)9-SWDGd$xTU&odCg6hgm8j;`%y+icbfkq$4lVBR z)N7qbfPbW9RF1}G;-X-B^HH;Qe;)!OC+($jlm^Urc)SgbaZNs{%al&0fqms;#C^>p z2DR_}Hgpyu^T*{~x)K-!@m7%o!Mj4bx?Se90+9TXLSbPchVHB=Q4MF(7sFP}?-0&} zQ);==+KX8zTwZ(mK26||FYhqjjOun7U3Nh<{=d3@$m4T?`~CX+4d#Etc>e#DWq51s zzSew7a%BGjO&1acvV*pyr~haA4+@dqwMZ?e3m+HhhT7O+-+zg4Jw3?-n!eyLwjwLV zizibtj)X~O*CQ>b)&}v;=TEoKM<0s5tFzXHAj`4j})HV#c z4k{tPlpSCY!t=2{VS!FJe_$gw9m#dq$DG-D`aD4uQ~uYz@n`{6UzL#XrtXN3ArMa7 zz79I%JC8R65_KA~0GJOQj;{EWSwnLY;BMae_^;P#@BnvZVh5gRf_krs2IOlSaUkzT z>dW|}x)K#z?RD?c-%#YL7xJ*S)A|ei<4W(tB+@{d=xiPa%52_o6}Go%WwwZ#`}qMC zt3u|@i-UFD3!~(&&S!V*s3iNYJ0G8c2U>cClBG_PWU}>{>-LaP2x7!gK$cH`PE$tj z$;nG&;^a474JvMTHV7gk;}Nf0S$dsGN{`z1AEz)o|pK6D)=qi=m6z96$VsUH_loBlY=#^q|cCrMOJWxPeA< zD`4-tN-W8*Qt%Skd@4m6<>^nfCKsYWja5(h1ndnDfEQDT_Ds>Hhh6=lkJD91pgUMu zl8Ac?$2phBvfKI5Ah}4M{5&?TyOEsP-`CUhY}vM~$KCN>>-3~806+54N=2`8ax0VF?L(0^wD6xjm#!zcnyX<)L-&eqsbY#c<8L`LKK)}OO*Rh_s)dQ(WaT^=Vp z+LnlLM1i}32#YgThVPXWK?YYX&Isjd_1iVMyUC|H?6F z;>f7R%soY@flgej=2C>zYO%0sE0s#6vC~;`EE_fQ5=WFF-FYI+`E&FHnaBe8i}X?y z3I^?W0?z4u-FIF9@4x-tZD^2!br&mSSzRiB$!DrH z`=gbE{SlTR(Lk2&{xaOS68tc_vT*$h=JyNj$N8uAUJ-Gs)^*=JZU!M;#LWmNsE*=a zniTT~E~FpbTZ$k)h}je*qqCTh@bG6%#-RFz{|n*%=kuu1C1BAbyd0wijBZXC2zlMf zUcLW0KG3@MZogsq{Ry6WC`EtAE;w!}!m__QqT!T-Chk6!b}u3O`YSj zwwtORdH4cK3e}Q#Sy6cM^kXAi{wlwg>c^IKS(d@TB}6`Qr38($j~nn2(!uN(QEKt| z)KbR)&wN%JP+t493=aOU6}G=YM`3D9r-kiLf(EVHMTb*q>bgf(YRQ2$3E^AY7R%z_ zzke4~a@7uBbZ;?0j4?2~KKll5qwZ9TX=qGd9^W3X9jsNe)b1XZgWFKlvE_h<1DHg z8WH5n=5+9wv1H`m2FHO;FRd+2h*(6G)x7|qnBNaoXj|!7Ezwy%V(-r{%y?X-3)11! zZ8Pe_lQUnMY)r^@DpMuI(1;pXHJc59v(vLf1<0!?J-k17_j2BI5irwo8VOVRZ-WgWU`OuTL&CQqr}%^|}vUruk(;ZI+v+cocjC%?Sv+AEg^H1q zl#-Q}y2ai{Z*uu2VCH4D*IwY5O0&=)St6KursCb+J28z&RDZSpm1s90!cnqTq4rGs z?6boeAz0tlb`SOxn=)KNQi`6IW*_H@ww%@pfQu1fY;MS5Z;>ES*=$d{+COIWG1Gz> zT(eaE$g55ZgG4hyJ32BJ=IveE#{j)cG9M<`mo(_pT{g-Ubkzy1XRFI15Wiy1IMUKm(p)%rsD*hfW{J&H znV1)PSBQ_j;@XJBE-2P~v-!J< zo!);iAke~oo9u%rn!Ta;xn9k4X@Wo4&HJ_!@E4{h%lg_$ny=$!x3cdrI$IRx{c`F@ zo}h^N3i!XuCJq3Ly7=SYT_+8Tt`Z3lUk2inF{Q@FdT9(Co^I}eY;>zialTdU&BOpE zvqAr3ZtpC=9A3ZOJ9G^tUFE8rU^>;s(TS6rRO%ZxXB`q0NjL}CcX00(?n;#|6SBIO zG>wVRCZU3i?S1`ap~jP z2Dl<<#Z(&dVEyKkrbnHbRg6ds0Duw z7LHl%w1*z-HmYEXl^e$kl$#0KYc1sroD$Wwt#z&kAx=3>RWE}T;bvIr_tFXf+Pc4f znfO(X$Ly8($vsn9wxzA!zY-<9IU?RyeQiSY5xwt9!|_LtEq;I~cnavA9*DjO@{_z} zPt6zW`l8F0{yyTFy>E2DkUC}??Hl!dREPf5#G2MRN?_WKPnJAFd<#-EdG}ynMO&WM zI?3W5Y3CHjmHx4pD@gn>zJ>e3Q&Yt=>P{hqNQ9ED=4CD6nwcx8X`AR-q)L!wo}wRW+bO(7h7FIV=H*$@jpIIk?=(abhH z*G9L?+Q=djIXsJP+9=fC&gkECw@ZuYPRGok&g!Jz-<*t>*_f^&!oxAtVLBY|74W{v z8d(=A}ERCR` zUs+m(>4gBvye28F6N1I}aH#dNf7PMP&_mr(gkoHxT;uHI;V#0;?(?aEb?!|!&Hzp< z@w~xEoO7j%g;K^B@`Hy9Ts z5s%9)z96Wkj^eDp4QbbOCwUXy0%WDeHCzMl#WXp|$}N$<;T#T*6T##X7U{hX(zp2* zmnYCvbV#@wns$mfof;t*OQs~YkZI%p0T+EbOiG>-HA~6*rtQ_ZbL5~#W1@Mg_d#WV zIXHXRAfah4%J)IRN*5DBIcI3`ZLs_4#Y;WCoXZ9i9!fTw!ZMNPgUuqCO6(>UC22@{ z^Y`b+!lNa%C97_1eaj{EVmb|0Sy% zY3k|8z);9mG&BozUh00Wo0|17)uBr+D(;;&wrhA?iQ|OLyeDq#hkTL+;Gg|9SuR ze>k}R$E!XLw&VhKzpCwPWlvh0Cq6n4Dl*}*0Y^3jV#;QxNGnP#J@P^Meo$om4QvZJ z(%~M{WVPiqM!>nLy7q5=;gHA`M9tOTZOH>!t@rGJ$1)uA%{tVOK15s&D~0^ekz4f; z*+Rq{x<94=md2xh6%*ffEbx=j(6*C(^3?9+S}!uqZ*}4FsIz|=C-z#V21QBJdS~7x zlou#>@)4EjP!{4L+yCkZLm48#u@}?aL!U>;0iQhKl$STJ(972px(a>&CLY)rg~$jm z-%DsM`0neOzI=VXSwEAb_xh!usO6 z@xS3Az3yp2c&NhOFpk-wsB7nvVhg+aCNmF8?N9gKvUu=5$i76X_I8CQV8PiwoFPal zEKKwrM0!S>44j>pNyV?-TXCB3%Fx2swXHBFBIQc#@MYPzlkZA7r)z_zMWEW77SD8A z30KCKIj=P?J~M%L*Y(+K98+?-lf!?S9%f0j^U&*xqDg+%N*Dg6H0nM)Kv#A43n&^= zzw!v&m2}r|-E}ZKNqxt#moi*YbDnH+jr9|Ggf#f1yP>Syvk_V#NUFx-<;qqmUsBvEw z43e80NK}TQ=yqhDY&gR7IdPn61kTwXe&BU^z4MBf z(QGSmY6pOrgnM9g|8YRDXf6F`BoUAIPN7)Vv(4iD@SAdj^fZc3a@^%w6{rAiPjw-5 zrY?dJet@8JO;eL_N>_98?em_9{KYsCNyazj{cjBx(Ojk6ezKBobDHC}#l(#;V{>?D zIv)y=kU~OY&~KulK1bWVvF_UlC;st4uGe3+A3w5EDxL(0R9XTyriLHx=xr!V_L(08l_`-g>NiE@hL|$XT+*=7ej`gvQ2uG69)fZsEjV;-pHF!q$5heMC{oz| zAi+fu(7_k{3A>M>%qqExEU=J7?v+*P$D2H0P^&hEK&>k|PUYcy8$-5#59LL-?oiZ$ z%1VCfQc^8Hvri;mT3pX&mVgt|XHu79buXa5!YPQV900jj1dfpIg^^XI#cR2Mb?5_2Y+LV;=0c9h@_Q(~?zLBjK)KzS%uu znw0gK;Bm(KyTXx0BDhB!3cuSvrn+!eO{G;jS}mX z4tI{oAX8%Z(n-P?kz57>qgcYV%PVFsUmdtxm>mx%-buQcnHT%M0=|I4Qd=N9`~UaX zuJZ4`l{9lB7BIdzilAH@TRwF~MF{$*_`dNd=NF{}tUgS7x#*F`q!7fj486ZGKe$y> zWSMxJ^bVQn9#uE6yTR-tQ31SsK6;YfP;X*c4~o`?U6BqB^+RwUdkoC1E_Ny9d1X;Q zD2MvPpl%OaEKef`;IgRi?(YvK9TyiD``~o+-cBm`1$bpqKkZ$)X(n`^K1@!mZ4A+L zzBE0+uKnxb4H0m5P67SZhQSaM?&SpzR^ovGutI&L$>6Kf-L<$rGMvi1+R#wZ=j(VB zPECFrD2115GYu5S3; zw7RM+N!Og5olK0=M)NwKP=ZBrZ=*bdC?BHjj3@2j3Rb;Xow=NY~=)>pUS3msbxrH1t&y zKL`C<3?DF^HQanl7S7l)AO#r(p80E{)#x4XL(MJ=tYq%8d&7A}`N zG;qJa&8!6!SuRJ^0d+x2+XLY=%T^Ws4-AvsxHYv6#B9zBqO0vpuiGnzkJ z{Iq<-VweKp;vm(M+`z#PLT-(Zt-Dx9Pr``bbB6Io_q>jXH$@TH?8mREX_S+fzrJ(J zl$gKU#7*M?goOey*=#FU0X*6T*N|2^MTH_CW}0p$D!hKbp5C6olIkFQ#&w0{<&Qnn z8xi2)Qy3#n$L!iMANz{rDUb!Yb%4S5K2qd{)#xuDC-<@s1i~XK4G|0I#c_~h|^;krP>%)-*mrE)<)4+5WPc}VCG#bi5LEItAF;F_3Z3V~wPDOLF% z^=7@K&%y?!w8A6|ZJOAf3ON-O-Vfz0EUXqUA9`{zb@jxcg15)))YQZKB+;3BxMhy7 zRxStQ^{Wy{PRIgbVaH1lnS0+|9AI&X>g{^00_AjcP#%62{VjGH{cg#mK0g}1ygoiT zn2i6E-8YSW5#iM6$&H4V{QmBW85+r$N!8@xV|3Br`#pRtJJx{v4DSzMvjjk&04Bn_w!v;u+zY*1cZq?-G*0wmV zo3wI{XCHPNjeviS=eSV|k&Zrk1#E-z_G&VjYEw}{ADJn`D8zUqG4O2!5cEYc~#DcXMAIgrz zO~~gnG!ToTe-U!ugHmk1Ak%;uWEYvhtevT(EbhmzkLBv9tfXY4#T65nTU3wp2H;vo z3ljV#r=ZFHevVI&uwxTU9vt$H-__9u2BBvGGSHjH5mSJRqn*HZ{Cq4G+rCo+ih$|G> zb?(mm?|>mo5t!$W^MazH-5R^b%1X8_)!(XAAuSvvoDQ;s>ubXJRhH({wuKui%4EX0 zE5g45ce96JgNV@#`xX`+F7SDMph`eFNFE2Z!`T@yo5miaYHv$X2R1VuXnAubwSc>Cs_cn3!%k37jQJQGYDdgAgk! zik$9VJMs~<`4W7W%%hYzQ7jPyZYf8JN?LldIn`q|h}GIvtvWi|Mfu*_>LTjB%xPhL zjGKIx#%R?HySjgp_WJ z@w=c}`gPwc%rK!hO1IhGuXVodS)A?cG|*7_OsP`#;c7JBU{=)skJYTNIh%EV?(OXX znX`76xdYd)B`%NIKXF^uQvV0fzTUn2M>dgtXH4As1@AAf5H^u2b$=n2uYdk_`KFED z8hO4e>pf1`t8%hEKBg^dYaOHJuf1|@nAV?c;F7jp50!6UKFp8Pz3;VT`O=s^%a5gR zW?IC?zU}MtTPokv#>RRwWp9(+9tX=4DMo>*XN;vZma@45ixrJ+a&BH$bB{g${%>9U z`dLzub~_K`m;Jn?x=@iZ&h9mEb$*6PR&+G`y?}Yi$9fiCTw!bb=YA=thlkGc8IdVd z#U8jX&RE%0`|H;)(fjk~8qHj@b!+TSk%jjrid#f*wK^&O`@8)}zRENHKkqyL)g{*d z4?d|RjI>*m{5xxuDI!!J)oS2>Y4YY%{B%Bg)t;RP3a_*8dpf6m{@i_W z{}-6ny#Dla^BarOJ=)hdeNb}_JXrbTY^qsZOTsZmW4;-Mf1J~0s;o@x*Utp*?|gHn zs3pK?_l!i7yx#TIVRq>W4sF||xPWzg#5Rpd4h}|7+ZfnEjnFKMh|4ongba62Xck|v z#_8Oj$g<0C&+OVIzyHA1Ifnmi_wN*1ImKACRjP(|M%(;tW;j(GR^PTeY-iAy`mhZBvtX@#= z%4_~6;Lgk)CY6Ft>-wgho_2<#yW_TS<;~vIx6g7)zi~xRby>1vvhbuL)(xdx(aY-( zU(-Be@o$x+`}}}^-0BlUJ}|kctkM83$xHKF60B*Tdh)u_T3OpqN-Z+W{U&uRoqhl7 zg#9hCYQ`7jtql}ji+7z~C9kqfTy>fF!*pgDpqC9_Jy@|a+lj-__xN+M`|>&tK5dPQ zYAjTfIzs}L?yr?~ez4W%*ZGU@KQL9SV)74tu=14svz1r9ePq-gA(V0`mZ#?jg9%*5LA ztIw`kliBmrYT_cldz%sk=Xx2MN=+|W4IGm#KN`Jmy=AnekJ-{-Q=PNwN7SEXWUW~H zskFF!qnW8x`Mcfo@5MYgQDk5~bA|Q`;cNfy^u#kVF+DoZ`FEoge{{u!h|Y zV$Djwhu`f#ANsbob1%sLX7qI^{<3i8 zN=d#Wzp$PzJikzGP literal 2666 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⋙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