refactor: implement new UI/UX architecture with redesigned desktop and mobile layouts, CSS-in-CSS modularization, and updated components.

This commit is contained in:
2026-08-29 00:53:57 -07:00
parent dfbf646332
commit cd98c0120e
47 changed files with 8039 additions and 7236 deletions
+129 -4
View File
@@ -12,6 +12,7 @@ import (
"sync"
"time"
aiActions "git.pengzhan.dev/haopengzhan/rsspipe/internal/actions/ai"
"git.pengzhan.dev/haopengzhan/rsspipe/internal/ai"
"git.pengzhan.dev/haopengzhan/rsspipe/internal/auth"
"git.pengzhan.dev/haopengzhan/rsspipe/internal/crawler"
@@ -222,11 +223,12 @@ func (h *Handler) CreateFeed(w http.ResponseWriter, r *http.Request) {
if req.Title != nil && strings.TrimSpace(*req.Title) != "" {
title = strings.TrimSpace(*req.Title)
}
if title == "" {
title = req.FeedURL
iconURL := ""
if req.IconURL != nil && strings.TrimSpace(*req.IconURL) != "" {
iconURL = strings.TrimSpace(*req.IconURL)
}
feed, err := h.db.CreateFeed(title, req.FeedURL, res.SiteURL, req.CategoryID)
feed, err := h.db.CreateFeed(title, req.FeedURL, res.SiteURL, iconURL, req.CategoryID)
if err != nil {
h.writeError(w, http.StatusInternalServerError, err.Error())
return
@@ -426,7 +428,7 @@ func (h *Handler) ImportOPML(w http.ResponseWriter, r *http.Request) {
title = item.FeedURL
}
_, err := h.db.CreateFeed(title, item.FeedURL, item.SiteURL, catID)
_, err := h.db.CreateFeed(title, item.FeedURL, item.SiteURL, "", catID)
if err == nil {
createdCount++
}
@@ -636,6 +638,70 @@ func (h *Handler) ScrapeEntry(w http.ResponseWriter, r *http.Request) {
h.writeJSON(w, http.StatusOK, updatedEntry)
}
// ToggleShareEntry toggles public sharing for an entry and returns the updated state
func (h *Handler) ToggleShareEntry(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
shareUUID, err := h.db.ToggleShareEntry(id)
if err != nil {
h.writeError(w, http.StatusInternalServerError, err.Error())
return
}
entry, _ := h.db.GetEntry(id)
h.writeJSON(w, http.StatusOK, map[string]interface{}{
"shared": shareUUID != nil,
"share_uuid": shareUUID,
"entry": entry,
})
}
// GenerateAISummary on-demand generates an AI summary for a single article
func (h *Handler) GenerateAISummary(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
entry, err := h.db.GetEntry(id)
if err != nil {
h.writeError(w, http.StatusNotFound, "entry not found")
return
}
cfg := aiActions.LoadConfig(h.db)
if !cfg.Enabled {
h.writeError(w, http.StatusBadRequest, "AI is not enabled in settings")
return
}
if cfg.APIKey == "" && cfg.Provider != "ollama" {
h.writeError(w, http.StatusBadRequest, "AI API key is not configured in settings")
return
}
prompt := "Summarize the key points of this article clearly with 3 concise bullet points and a brief takeaway."
resultText, err := h.aiClient.GenerateEnrichment(r.Context(), cfg, prompt, *entry)
if err != nil {
h.writeError(w, http.StatusInternalServerError, "AI generation failed: "+err.Error())
return
}
variant := models.EntryVariant{
EntryID: entry.ID,
VariantKey: "ai_summary",
ContentType: "text",
TextValue: resultText,
Metadata: map[string]interface{}{
"model": cfg.Model,
},
}
if err := h.db.SetVariant(variant); err != nil {
h.writeError(w, http.StatusInternalServerError, "failed to save AI summary: "+err.Error())
return
}
updated, err := h.db.GetEntry(id)
if err != nil {
h.writeError(w, http.StatusInternalServerError, err.Error())
return
}
h.writeJSON(w, http.StatusOK, updated)
}
// Rules API Handlers
func (h *Handler) ListRules(w http.ResponseWriter, r *http.Request) {
rules, err := h.db.ListRules()
@@ -967,3 +1033,62 @@ func (h *Handler) TestAIConnection(w http.ResponseWriter, r *http.Request) {
h.writeJSON(w, http.StatusOK, res)
}
// Search History Handlers
func (h *Handler) GetSearchHistory(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q")
limitStr := r.URL.Query().Get("limit")
limit := 10
if limitStr != "" {
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
limit = l
}
}
history, err := h.db.GetSearchHistory(q, limit)
if err != nil {
h.writeError(w, http.StatusInternalServerError, "failed to get search history: "+err.Error())
return
}
if history == nil {
history = []models.SearchHistoryItem{}
}
h.writeJSON(w, http.StatusOK, history)
}
func (h *Handler) RecordSearch(w http.ResponseWriter, r *http.Request) {
var req struct {
Keyword string `json:"keyword"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
h.writeError(w, http.StatusBadRequest, "invalid request body")
return
}
if strings.TrimSpace(req.Keyword) == "" {
h.writeJSON(w, http.StatusOK, map[string]string{"status": "ignored"})
return
}
if err := h.db.RecordSearch(req.Keyword); err != nil {
h.writeError(w, http.StatusInternalServerError, "failed to record search: "+err.Error())
return
}
h.writeJSON(w, http.StatusOK, map[string]string{"status": "recorded"})
}
func (h *Handler) DeleteSearchHistory(w http.ResponseWriter, r *http.Request) {
keyword := r.URL.Query().Get("keyword")
if keyword != "" {
if err := h.db.DeleteSearchHistory(keyword); err != nil {
h.writeError(w, http.StatusInternalServerError, "failed to delete search history: "+err.Error())
return
}
} else {
if err := h.db.ClearSearchHistory(); err != nil {
h.writeError(w, http.StatusInternalServerError, "failed to clear search history: "+err.Error())
return
}
}
h.writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
}