diff --git a/internal/ollama/client.go b/internal/ollama/client.go index 899b081..6a68c03 100644 --- a/internal/ollama/client.go +++ b/internal/ollama/client.go @@ -92,7 +92,7 @@ func (c *Client) StreamChatWithModel(ctx context.Context, model string, messages if err != nil { return fmt.Errorf("request ollama: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { payload, readErr := io.ReadAll(io.LimitReader(resp.Body, 4096)) @@ -138,7 +138,7 @@ func (c *Client) doModelOnlyRequest(ctx context.Context, endpoint string, body [ if err != nil { return fmt.Errorf("request ollama preload: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { payload, readErr := io.ReadAll(io.LimitReader(resp.Body, 4096)) @@ -237,7 +237,7 @@ func (c *Client) doEmbedRequest(ctx context.Context, endpoint string, body []byt if err != nil { return embedResponse{}, 0, fmt.Errorf("request ollama embeddings: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { payload, readErr := io.ReadAll(io.LimitReader(resp.Body, 4096)) diff --git a/internal/ollama/client_test.go b/internal/ollama/client_test.go index ee1112f..44720c4 100644 --- a/internal/ollama/client_test.go +++ b/internal/ollama/client_test.go @@ -17,7 +17,7 @@ func TestStreamChatWithModelIncludesDefaultOptions(t *testing.T) { t.Fatalf("request path = %q, want /api/chat", r.URL.Path) } - defer r.Body.Close() + defer func() { _ = r.Body.Close() }() var payload chatRequest if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { diff --git a/internal/search/search.go b/internal/search/search.go index 5aa5fab..0b9ac8f 100644 --- a/internal/search/search.go +++ b/internal/search/search.go @@ -232,7 +232,6 @@ type labeledSearchQuery struct { type fetchedDocument struct { document Document sourceMD string - include bool processed bool } @@ -1476,7 +1475,7 @@ func (s *Service) searchVariant(ctx context.Context, rewrittenQuery string, quer if err != nil { return nil, fmt.Errorf("request searxng: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { payload, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) @@ -1603,20 +1602,6 @@ func shouldSkipSearchURL(rawURL string) bool { } } -func isTrustedSearchHost(host string) bool { - normalized := strings.ToLower(strings.TrimSpace(host)) - if normalized == "" { - return false - } - trustedHosts := []string{"github.com", "stackoverflow.com", "developer.mozilla.org"} - for _, candidate := range trustedHosts { - if normalized == candidate || strings.HasSuffix(normalized, "."+candidate) { - return true - } - } - return strings.HasPrefix(normalized, "docs.") || strings.HasPrefix(normalized, "developer.") || strings.HasSuffix(normalized, ".gov") || strings.HasSuffix(normalized, ".edu") -} - func isVideoResult(rawURL string) bool { parsed, err := url.Parse(strings.TrimSpace(rawURL)) if err != nil { @@ -1758,7 +1743,7 @@ func (s *Service) fetchDocument(ctx context.Context, result Result, onActivity f } return failedFetchResult(progressKey, result.URL, onProgress), nil } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() notifyActivity(onActivity) if resp.StatusCode != http.StatusOK { @@ -1976,7 +1961,7 @@ func buildSummaryPrompt(originalQuery string, searchQuery string, documents []Do } builder.WriteString("Fuentes extraídas:\n") for i, document := range documents { - builder.WriteString(fmt.Sprintf("\n[%d] %s\n", i+1, safeTitle(document.Title))) + _, _ = fmt.Fprintf(&builder, "\n[%d] %s\n", i+1, safeTitle(document.Title)) builder.WriteString("URL: ") builder.WriteString(document.URL) builder.WriteString("\n") @@ -1992,7 +1977,7 @@ func buildSummaryPrompt(originalQuery string, searchQuery string, documents []Do builder.WriteString("\nFuentes:\n") for index, document := range documents { - builder.WriteString(fmt.Sprintf("- [%d] ", index+1)) + _, _ = fmt.Fprintf(&builder, "- [%d] ", index+1) builder.WriteString(document.URL) builder.WriteString("\n") } @@ -2334,7 +2319,7 @@ func buildFinalSummaryPrompt(query string, summaries []SourceSummary) string { builder.WriteString("\n\n") builder.WriteString("Resúmenes por fuente:\n") for index, summary := range summaries { - builder.WriteString(fmt.Sprintf("\n[%d] %s\n", index+1, safeTitle(summary.Title))) + _, _ = fmt.Fprintf(&builder, "\n[%d] %s\n", index+1, safeTitle(summary.Title)) builder.WriteString("URL: ") builder.WriteString(summary.URL) builder.WriteString("\n") @@ -2345,7 +2330,7 @@ func buildFinalSummaryPrompt(query string, summaries []SourceSummary) string { builder.WriteString("\nFuentes:\n") for index, summary := range summaries { - builder.WriteString(fmt.Sprintf("- [%d] ", index+1)) + _, _ = fmt.Fprintf(&builder, "- [%d] ", index+1) builder.WriteString(summary.URL) builder.WriteString("\n") } diff --git a/internal/search/search_test.go b/internal/search/search_test.go index fe7ecc4..7371525 100644 --- a/internal/search/search_test.go +++ b/internal/search/search_test.go @@ -411,9 +411,9 @@ func TestPrepareFallsBackToWebSearchWhenSemanticCacheMisses(t *testing.T) { if got := r.URL.Query().Get("q"); got != "consulta optimizada" { t.Fatalf("search query = %q, want rewritten fallback query", got) } - fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.8}]}`, baseURL) + _, _ = fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.8}]}`, baseURL) case "/a": - fmt.Fprint(w, pageAContent) + _, _ = fmt.Fprint(w, pageAContent) default: http.NotFound(w, r) } @@ -471,19 +471,19 @@ func TestPrepareIncludesAllSearchVariantsInDownloadProgress(t *testing.T) { query := r.URL.Query().Get("q") switch query { case "instalar ollama": - fmt.Fprintf(w, `{"results":[{"title":"A1","url":"%s/a1","content":"snippet a1","score":0.91},{"title":"A2","url":"%s/a2","content":"snippet a2","score":0.81},{"title":"A3","url":"%s/a3","content":"snippet a3","score":0.71}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"A1","url":"%s/a1","content":"snippet a1","score":0.91},{"title":"A2","url":"%s/a2","content":"snippet a2","score":0.81},{"title":"A3","url":"%s/a3","content":"snippet a3","score":0.71}]}`, baseURL, baseURL, baseURL, ) case installOllamaQuery: - fmt.Fprintf(w, `{"results":[{"title":"B1","url":"%s/b1","content":"snippet b1","score":0.89},{"title":"B2","url":"%s/b2","content":"snippet b2","score":0.79},{"title":"B3","url":"%s/b3","content":"snippet b3","score":0.69}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"B1","url":"%s/b1","content":"snippet b1","score":0.89},{"title":"B2","url":"%s/b2","content":"snippet b2","score":0.79},{"title":"B3","url":"%s/b3","content":"snippet b3","score":0.69}]}`, baseURL, baseURL, baseURL, ) case "ollama": - fmt.Fprintf(w, `{"results":[{"title":"C1","url":"%s/c1","content":"snippet c1","score":0.87},{"title":"C2","url":"%s/c2","content":"snippet c2","score":0.77},{"title":"C3","url":"%s/c3","content":"snippet c3","score":0.67}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"C1","url":"%s/c1","content":"snippet c1","score":0.87},{"title":"C2","url":"%s/c2","content":"snippet c2","score":0.77},{"title":"C3","url":"%s/c3","content":"snippet c3","score":0.67}]}`, baseURL, baseURL, baseURL, @@ -492,9 +492,9 @@ func TestPrepareIncludesAllSearchVariantsInDownloadProgress(t *testing.T) { t.Fatalf("unexpected search query: %q", query) } case "/a1", "/a2", "/a3", "/b1", "/b2", "/b3", "/c1", "/c2", "/c3": - fmt.Fprint(w, strings.TrimPrefix(r.URL.Path, "/")) + _, _ = fmt.Fprint(w, strings.TrimPrefix(r.URL.Path, "/")) case "/a": - fmt.Fprint(w, pageAContent) + _, _ = fmt.Fprint(w, pageAContent) default: http.NotFound(w, r) } @@ -526,19 +526,19 @@ func TestPrepareUsesTopThreeResultsPerVariantAsSources(t *testing.T) { case searchPath: switch r.URL.Query().Get("q") { case primaryVariantQuery: - fmt.Fprintf(w, `{"results":[{"title":"A1","url":"%s/a1","content":"snippet a1","score":0.92},{"title":"A2","url":"%s/a2","content":"snippet a2","score":0.82},{"title":"A3","url":"%s/a3","content":"snippet a3","score":0.72}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"A1","url":"%s/a1","content":"snippet a1","score":0.92},{"title":"A2","url":"%s/a2","content":"snippet a2","score":0.82},{"title":"A3","url":"%s/a3","content":"snippet a3","score":0.72}]}`, baseURL, baseURL, baseURL, ) case longVariantQuery: - fmt.Fprintf(w, `{"results":[{"title":"B1","url":"%s/b1","content":"snippet b1","score":0.91},{"title":"B2","url":"%s/b2","content":"snippet b2","score":0.81},{"title":"B3","url":"%s/b3","content":"snippet b3","score":0.71}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"B1","url":"%s/b1","content":"snippet b1","score":0.91},{"title":"B2","url":"%s/b2","content":"snippet b2","score":0.81},{"title":"B3","url":"%s/b3","content":"snippet b3","score":0.71}]}`, baseURL, baseURL, baseURL, ) case technicalVariantQuery: - fmt.Fprintf(w, `{"results":[{"title":"C1","url":"%s/c1","content":"snippet c1","score":0.90},{"title":"C2","url":"%s/c2","content":"snippet c2","score":0.80},{"title":"C3","url":"%s/c3","content":"snippet c3","score":0.70}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"C1","url":"%s/c1","content":"snippet c1","score":0.90},{"title":"C2","url":"%s/c2","content":"snippet c2","score":0.80},{"title":"C3","url":"%s/c3","content":"snippet c3","score":0.70}]}`, baseURL, baseURL, baseURL, @@ -547,7 +547,7 @@ func TestPrepareUsesTopThreeResultsPerVariantAsSources(t *testing.T) { t.Fatalf("unexpected search query: %q", r.URL.Query().Get("q")) } case "/a1", "/a2", "/a3", "/b1", "/b2", "/b3", "/c1", "/c2", "/c3": - fmt.Fprint(w, strings.TrimPrefix(r.URL.Path, "/")) + _, _ = fmt.Fprint(w, strings.TrimPrefix(r.URL.Path, "/")) default: http.NotFound(w, r) } @@ -638,7 +638,7 @@ func TestFetchSourceBuildsReadableMarkdown(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/a": - fmt.Fprint(w, pageAContent) + _, _ = fmt.Fprint(w, pageAContent) default: http.NotFound(w, r) } @@ -750,9 +750,9 @@ func TestPrepareWaitsForPendingSemanticCacheIngestBeforeRepeatingWebSearch(t *te searchMu.Lock() searchRequests++ searchMu.Unlock() - fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.8}]}`, baseURL) + _, _ = fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.8}]}`, baseURL) case "/a": - fmt.Fprint(w, pageAContent) + _, _ = fmt.Fprint(w, pageAContent) default: http.NotFound(w, r) } @@ -818,9 +818,9 @@ func TestPrepareFallsBackToWebSearchWhenSemanticCacheEntryExpired(t *testing.T) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case searchPath: - fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.8}]}`, baseURL) + _, _ = fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.8}]}`, baseURL) case "/a": - fmt.Fprint(w, pageAContent) + _, _ = fmt.Fprint(w, pageAContent) default: http.NotFound(w, r) } @@ -854,9 +854,9 @@ func TestPrepareReturnsErrorWhenNoSourcesCanBeProcessed(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case searchPath: - fmt.Fprintf(w, `{"results":[{"title":"Fallback","url":"%s/article","content":"search snippet","score":1}]}`, baseURL) + _, _ = fmt.Fprintf(w, `{"results":[{"title":"Fallback","url":"%s/article","content":"search snippet","score":1}]}`, baseURL) case "/article": - fmt.Fprint(w, "broken body") + _, _ = fmt.Fprint(w, "broken body") default: http.NotFound(w, r) } @@ -884,7 +884,7 @@ func TestPrepareUsesBackupSourcesWhenPrimaryDownloadsFail(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case searchPath: - fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":8},{"title":"B","url":"%s/b","content":"snippet b","score":7},{"title":"C","url":"%s/c","content":"snippet c","score":6},{"title":"D","url":"%s/d","content":"snippet d","score":5},{"title":"E","url":"%s/e","content":"snippet e","score":4},{"title":"F","url":"%s/f","content":"snippet f","score":3},{"title":"Video","url":"https://www.youtube.com/watch?v=abc","content":"video snippet","score":99}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":8},{"title":"B","url":"%s/b","content":"snippet b","score":7},{"title":"C","url":"%s/c","content":"snippet c","score":6},{"title":"D","url":"%s/d","content":"snippet d","score":5},{"title":"E","url":"%s/e","content":"snippet e","score":4},{"title":"F","url":"%s/f","content":"snippet f","score":3},{"title":"Video","url":"https://www.youtube.com/watch?v=abc","content":"video snippet","score":99}]}`, baseURL, baseURL, baseURL, @@ -893,18 +893,18 @@ func TestPrepareUsesBackupSourcesWhenPrimaryDownloadsFail(t *testing.T) { baseURL, ) case "/b": - fmt.Fprint(w, "page/b") + _, _ = fmt.Fprint(w, "page/b") case "/c": - fmt.Fprint(w, "page/c") + _, _ = fmt.Fprint(w, "page/c") case "/d": - fmt.Fprint(w, "page/d") + _, _ = fmt.Fprint(w, "page/d") case "/e": - fmt.Fprint(w, "page/e") + _, _ = fmt.Fprint(w, "page/e") case "/f": - fmt.Fprint(w, "page/f") + _, _ = fmt.Fprint(w, "page/f") case "/a": w.WriteHeader(http.StatusBadGateway) - fmt.Fprint(w, "upstream error") + _, _ = fmt.Fprint(w, "upstream error") default: http.NotFound(w, r) } @@ -943,13 +943,13 @@ func newMultiplexedSearchHandler(t *testing.T, baseURL *string) http.HandlerFunc case searchPath: serveMultiplexedSearchResults(t, w, r, *baseURL) case "/a": - fmt.Fprint(w, pageAContent) + _, _ = fmt.Fprint(w, pageAContent) case sharedPath: - fmt.Fprint(w, sharedPageContent) + _, _ = fmt.Fprint(w, sharedPageContent) case "/docs": - fmt.Fprint(w, "page/docs") + _, _ = fmt.Fprint(w, "page/docs") case "/c": - fmt.Fprint(w, "page/c") + _, _ = fmt.Fprint(w, "page/c") default: http.NotFound(w, r) } @@ -960,17 +960,17 @@ func serveMultiplexedSearchResults(t *testing.T, w http.ResponseWriter, r *http. t.Helper() switch r.URL.Query().Get("q") { case primaryVariantQuery: - fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.9},{"title":"B","url":"%s/shared","content":"snippet shared","score":0.8}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.9},{"title":"B","url":"%s/shared","content":"snippet shared","score":0.8}]}`, baseURL, baseURL, ) case longVariantQuery: - fmt.Fprintf(w, `{"results":[{"title":"Shared alt","url":"%s/shared","content":"snippet shared alt","score":0.95},{"title":"C","url":"%s/c","content":"snippet c","score":0.7}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"Shared alt","url":"%s/shared","content":"snippet shared alt","score":0.95},{"title":"C","url":"%s/c","content":"snippet c","score":0.7}]}`, baseURL, baseURL, ) case technicalVariantQuery: - fmt.Fprintf(w, `{"results":[{"title":"Docs","url":"%s/docs","content":"docs","score":0.6}]}`, baseURL) + _, _ = fmt.Fprintf(w, `{"results":[{"title":"Docs","url":"%s/docs","content":"docs","score":0.6}]}`, baseURL) default: t.Fatalf("unexpected multiplexed query: %q", r.URL.Query().Get("q")) } @@ -1170,7 +1170,7 @@ func (f *rewriteSearchFixture) handleRewriteSearch(t *testing.T) func(http.Respo if got := r.URL.Query().Get("q"); got != rewrittenSudoQuery { t.Fatalf("search query = %q, want rewritten query", got) } - fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.4},{"title":"B","url":"%s/b","content":"snippet b","score":0.9}]}`, + _, _ = fmt.Fprintf(w, `{"results":[{"title":"A","url":"%s/a","content":"snippet a","score":0.4},{"title":"B","url":"%s/b","content":"snippet b","score":0.9}]}`, f.baseURL, f.baseURL, ) @@ -1178,7 +1178,7 @@ func (f *rewriteSearchFixture) handleRewriteSearch(t *testing.T) func(http.Respo f.mu.Lock() f.pageRequests = append(f.pageRequests, r.URL.Path) f.mu.Unlock() - fmt.Fprintf(w, "page%s", r.URL.Path) + _, _ = fmt.Fprintf(w, "page%s", r.URL.Path) default: http.NotFound(w, r) } diff --git a/internal/tui/editor.go b/internal/tui/editor.go index bbed453..a9a6276 100644 --- a/internal/tui/editor.go +++ b/internal/tui/editor.go @@ -63,7 +63,7 @@ func editInExternalEditor(localizer *i18n.Localizer, editor, content string) tea cmd.Stderr = os.Stderr return tea.ExecProcess(cmd, func(runErr error) tea.Msg { - defer os.Remove(path) + defer func() { _ = os.Remove(path) }() if runErr != nil { return editorDoneMsg{err: fmt.Errorf(localizer.Get("editor.open_failed"), label, runErr)} } diff --git a/internal/tui/model.go b/internal/tui/model.go index 16f59b0..a16f20e 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -337,10 +337,6 @@ type model struct { configPath string } -type llmAccumulator interface { - StreamChatWithModel(ctx context.Context, model string, messages []ollama.ChatMessage, onChunk func(string) error) error -} - func noOpActivity() { _ = struct{}{} } @@ -548,22 +544,22 @@ func newMarkdownRenderer(colors colorScheme, wrap int) (*glamour.TermRenderer, e func markdownStyleConfig(colors colorScheme) glamouransi.StyleConfig { style := glamourstyles.DarkStyleConfig - style.Document.StylePrimitive.Color = stringPtr(colors.text) - style.Heading.StylePrimitive.Color = stringPtr(colors.accentSoft) - style.Heading.StylePrimitive.Bold = boolPtr(true) - style.Heading.StylePrimitive.Upper = boolPtr(true) + style.Document.Color = stringPtr(colors.text) + style.Heading.Color = stringPtr(colors.accentSoft) + style.Heading.Bold = boolPtr(true) + style.Heading.Upper = boolPtr(true) clearHeadingPrefix := func(block *glamouransi.StyleBlock, color string) { - block.StylePrimitive.Prefix = "" - block.StylePrimitive.Suffix = "" - block.StylePrimitive.Color = stringPtr(color) - block.StylePrimitive.Bold = boolPtr(true) - block.StylePrimitive.Upper = boolPtr(true) + block.Prefix = "" + block.Suffix = "" + block.Color = stringPtr(color) + block.Bold = boolPtr(true) + block.Upper = boolPtr(true) } clearHeadingPrefix(&style.H1, colors.accentSoft) - style.H1.StylePrimitive.BackgroundColor = nil - style.H1.StylePrimitive.Underline = boolPtr(true) + style.H1.BackgroundColor = nil + style.H1.Underline = boolPtr(true) clearHeadingPrefix(&style.H2, colors.accent) clearHeadingPrefix(&style.H3, colors.accent) @@ -1089,19 +1085,6 @@ func shouldRenderProgressDiagnostic(line search.ProgressUpdate) bool { } } -func progressIcon(kind search.ProgressKind) string { - switch kind { - case search.ProgressKindSearch: - return "󰍉" - case search.ProgressKindDownload: - return "" - case search.ProgressKindLLM: - return "󰭻" - default: - return "•" - } -} - func (m *model) updateProgress(update search.ProgressUpdate) { if m.progressBlockIndex < 0 || m.progressBlockIndex >= len(m.blocks) || m.blocks[m.progressBlockIndex].role != "progress" { m.appendProgressBlock() @@ -1240,17 +1223,13 @@ func (m *model) renderAssistantContentWithWidth(content string, width int, backg return strings.Join(sections, "\n") } -func (m *model) renderThinkingContent(content string) string { - return m.renderThinkingContentWithWidth(content, m.contentWidth(), m.colors.bgBase) -} - func (m *model) renderMarkdownContent(content string) string { return m.renderMarkdownContentWithWidth(content, m.contentWidth(), m.colors.bgBase) } func (m *model) renderThinkingContentWithWidth(content string, width int, background string) string { wrapped := m.wrapParagraph(strings.TrimSpace(content), width) - style := m.styles.thinkingBlock.Copy().Background(lipgloss.Color(background)).Width(width) + style := m.styles.thinkingBlock.Background(lipgloss.Color(background)).Width(width) return style.Render(wrapped) } @@ -1519,7 +1498,7 @@ func (m *model) buildSyntheticSourcesListForIndexes(documents []search.Document, continue } document := documents[index-1] - builder.WriteString(fmt.Sprintf("- [%d] %s\n", index, strings.TrimSpace(document.URL))) + _, _ = fmt.Fprintf(&builder, "- [%d] %s\n", index, strings.TrimSpace(document.URL)) } return strings.TrimSpace(builder.String()) } diff --git a/internal/tui/slash_input_test.go b/internal/tui/slash_input_test.go index 3421b24..93c941d 100644 --- a/internal/tui/slash_input_test.go +++ b/internal/tui/slash_input_test.go @@ -166,7 +166,7 @@ func TestRunRequestStreamStopsSearchTimeoutBeforeFinalLLM(t *testing.T) { func TestPreparePromptForModelRewritesSearchQueryOnlyWhenSearchBuilderNeedsWebSearch(t *testing.T) { modelCh := make(chan string, 1) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() + defer func() { _ = r.Body.Close() }() var request struct { Model string `json:"model"` } @@ -1670,7 +1670,7 @@ func TestHandleStreamChunkCreatesAssistantBlockLazily(t *testing.T) { func TestStartRequestUsesTranslateGemmaModelForTranslateCommand(t *testing.T) { modelCh := make(chan string, 1) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() + defer func() { _ = r.Body.Close() }() var request struct { Model string `json:"model"` @@ -1723,7 +1723,7 @@ func TestStartRequestSearchWarmsModelsImmediately(t *testing.T) { preloadModelCh := make(chan string, 2) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() + defer func() { _ = r.Body.Close() }() var request modelOnlyPayload if err := json.NewDecoder(r.Body).Decode(&request); err != nil { diff --git a/internal/tui/update.go b/internal/tui/update.go index 976b65d..36198b5 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -243,22 +243,22 @@ func (m *model) handleSourceModeKey(msg tea.KeyMsg) (bool, tea.Cmd) { } case "up": if m.inSourceMode() { - m.viewport.LineUp(1) + m.viewport.ScrollUp(1) return true, nil } case "down": if m.inSourceMode() { - m.viewport.LineDown(1) + m.viewport.ScrollDown(1) return true, nil } case "shift+up": if m.state == stateSourceView && m.sourceDocument != nil { - m.sidebar.LineUp(1) + m.sidebar.ScrollUp(1) return true, nil } case "shift+down": if m.state == stateSourceView && m.sourceDocument != nil { - m.sidebar.LineDown(1) + m.sidebar.ScrollDown(1) return true, nil } default: @@ -408,11 +408,12 @@ func (m *model) handleStreamProgress(msg streamProgressMsg) tea.Cmd { case progressKeyChunking: m.setStatus(m.localizer.Get("status.processing_sources")) case search.CachePersistKey(): - if msg.update.State == search.ProgressDone { + switch msg.update.State { + case search.ProgressDone: m.setStatus(m.localizer.Get("status.cache_updated")) - } else if msg.update.State == search.ProgressInfo { + case search.ProgressInfo: m.setStatus(m.cachePersistStatusText(msg.update)) - } else { + default: m.setStatus(m.localizer.Get("status.saving_cache")) } case progressKeyTokenUsage, progressKeyTokenFinal, progressKeyReduction: diff --git a/internal/tui/view.go b/internal/tui/view.go index 4bc5148..0bc1837 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -142,10 +142,6 @@ func (m model) conversationContent() string { return body.String() } -func (m model) shouldRenderSeparatorAfter(role string) bool { - return false -} - func (m model) separatorLine() string { width := m.contentWidth() if width <= 0 { @@ -399,7 +395,7 @@ func (m model) renderSourceSearchModal() string { // Title with full background width titleText := m.localizer.Get("status.source_search_title") - titleStyle := m.styles.keyBinding.Copy(). + titleStyle := m.styles.keyBinding. Background(lipgloss.Color(m.colors.bgRaised)). Bold(true). Width(inputWidth). @@ -469,7 +465,7 @@ func (m model) sourceSelectionMarkdown() string { if title == "" { title = strings.TrimSpace(doc.URL) } - body.WriteString(fmt.Sprintf("- %s %d. %s\n %s\n", glyph, index+1, title, strings.TrimSpace(doc.URL))) + _, _ = fmt.Fprintf(&body, "- %s %d. %s\n %s\n", glyph, index+1, title, strings.TrimSpace(doc.URL)) } return strings.TrimSpace(body.String()) } @@ -496,18 +492,6 @@ func (m model) layoutReservedHeight() int { return reserved } -func (m model) userBlockContentWidth() int { - width := m.contentWidth() - m.styles.userBlock.GetHorizontalFrameSize() - if width < 1 { - return 1 - } - return width -} - -func (m model) renderHeader() string { - return "" -} - func (m model) wrapParagraph(rendered string, width int) string { if width <= 0 || rendered == "" { return rendered diff --git a/sparkle-cli b/sparkle-cli new file mode 100755 index 0000000..88ea8dd Binary files /dev/null and b/sparkle-cli differ