Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f32e427d4 | ||
|
|
3049c49f7a | ||
|
|
14a4f2b8d4 | ||
|
|
6a869574fc | ||
|
|
c581cbacda | ||
|
|
e7fe17a4bc | ||
|
|
b35aaa3b68 | ||
|
|
be83967168 | ||
|
|
064bca1dda |
19
README.md
19
README.md
@@ -1,8 +1,21 @@
|
|||||||
[English](README-en.md)
|
[English](README-en.md)
|
||||||
|
|
||||||
zenfeed:用 AI 赋能 RSS,自动为你筛选、总结、推送重要信息,告别信息过载,重拾阅读掌控感。
|

|
||||||
|
|
||||||
开箱即用的公共服务站:https://zenfeed.xyz (集成 Github Trending,V2EX 热榜等常见公开信源)
|
三点:
|
||||||
|
|
||||||
|
**1. AI 版 RSS 阅读器**
|
||||||
|
|
||||||
|
**2. 实时 “新闻” 知识库**
|
||||||
|
|
||||||
|
**3. 帮你时刻关注 “指定事件” 的秘书(如 “关税政策变化”,“xx 股票波动”)**
|
||||||
|
|
||||||
|
开箱即用的公共服务站:https://zenfeed.xyz (集成 Hacker News,Github Trending,V2EX 热榜等常见公开信源)
|
||||||
|
> 总结模型以更新至 Gemini 2.5pro!!
|
||||||
|
|
||||||
|
豆包机器人上架中!
|
||||||
|
|
||||||
|
加入下方👇🏻微信群关注更新
|
||||||
|
|
||||||
## 前言
|
## 前言
|
||||||
|
|
||||||
@@ -142,8 +155,6 @@ $env:API_KEY = "硅基流动apikey"; docker-compose -p zenfeed up -d
|
|||||||
* 支持 Webhook 通知
|
* 支持 Webhook 通知
|
||||||
* 爬虫
|
* 爬虫
|
||||||
|
|
||||||
> 进展会第一时间在 [Linux Do](https://linux.do/u/ajd/summary) 更新
|
|
||||||
|
|
||||||
## 有任何问题与反馈,欢迎加群讨论
|
## 有任何问题与反馈,欢迎加群讨论
|
||||||
|
|
||||||
<img src="docs/images/wechat.png" alt="Wechat" width="150">
|
<img src="docs/images/wechat.png" alt="Wechat" width="150">
|
||||||
|
|||||||
BIN
docs/images/crad.png
Normal file
BIN
docs/images/crad.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 617 KiB |
@@ -275,7 +275,12 @@ func (r *router) Route(ctx context.Context, result *rule.Result) (groups []*Grou
|
|||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *router) generateSummary(ctx context.Context, prompt string, feeds []*Feed, sourceLabel string) (string, error) {
|
func (r *router) generateSummary(
|
||||||
|
ctx context.Context,
|
||||||
|
prompt string,
|
||||||
|
feeds []*Feed,
|
||||||
|
sourceLabel string,
|
||||||
|
) (string, error) {
|
||||||
content := r.parseContentToSummary(feeds, sourceLabel)
|
content := r.parseContentToSummary(feeds, sourceLabel)
|
||||||
if content == "" {
|
if content == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -296,6 +301,7 @@ func (r *router) generateSummary(ctx context.Context, prompt string, feeds []*Fe
|
|||||||
func (r *router) parseContentToSummary(feeds []*Feed, sourceLabel string) string {
|
func (r *router) parseContentToSummary(feeds []*Feed, sourceLabel string) string {
|
||||||
if sourceLabel == "" {
|
if sourceLabel == "" {
|
||||||
b := runtimeutil.Must1(json.Marshal(feeds))
|
b := runtimeutil.Must1(json.Marshal(feeds))
|
||||||
|
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -208,10 +208,11 @@ func (s *scraper) fillIDs(feeds []*model.Feed) []*model.Feed {
|
|||||||
for _, feed := range feeds {
|
for _, feed := range feeds {
|
||||||
// We can not use the pub time to join the hash,
|
// We can not use the pub time to join the hash,
|
||||||
// because the pub time is dynamic for some sources.
|
// because the pub time is dynamic for some sources.
|
||||||
|
//
|
||||||
|
// title may be changed for some sources... so...
|
||||||
source := feed.Labels.Get(model.LabelSource)
|
source := feed.Labels.Get(model.LabelSource)
|
||||||
title := feed.Labels.Get(model.LabelTitle)
|
|
||||||
link := feed.Labels.Get(model.LabelLink)
|
link := feed.Labels.Get(model.LabelLink)
|
||||||
feed.ID = hashutil.Sum64s([]string{source, title, link})
|
feed.ID = hashutil.Sum64s([]string{source, link})
|
||||||
}
|
}
|
||||||
|
|
||||||
return feeds
|
return feeds
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/benbjohnson/clock"
|
"github.com/benbjohnson/clock"
|
||||||
@@ -578,10 +579,14 @@ func (s *storage) blockDependencies() block.Dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *storage) rewrite(ctx context.Context, feeds []*model.Feed) ([]*model.Feed, error) {
|
func (s *storage) rewrite(ctx context.Context, feeds []*model.Feed) ([]*model.Feed, error) {
|
||||||
rewritten := make([]*model.Feed, 0, len(feeds))
|
var (
|
||||||
var wg sync.WaitGroup
|
rewritten = make([]*model.Feed, 0, len(feeds))
|
||||||
var errs []error
|
wg sync.WaitGroup
|
||||||
var mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
errs []error
|
||||||
|
dropped atomic.Int32
|
||||||
|
)
|
||||||
|
|
||||||
for _, item := range feeds { // TODO: Limit the concurrency & goroutine number.
|
for _, item := range feeds { // TODO: Limit the concurrency & goroutine number.
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(item *model.Feed) {
|
go func(item *model.Feed) {
|
||||||
@@ -596,6 +601,7 @@ func (s *storage) rewrite(ctx context.Context, feeds []*model.Feed) ([]*model.Fe
|
|||||||
}
|
}
|
||||||
if len(labels) == 0 {
|
if len(labels) == 0 {
|
||||||
log.Debug(ctx, "drop feed", "id", item.ID)
|
log.Debug(ctx, "drop feed", "id", item.ID)
|
||||||
|
dropped.Add(1)
|
||||||
|
|
||||||
return // Drop empty labels.
|
return // Drop empty labels.
|
||||||
}
|
}
|
||||||
@@ -607,10 +613,12 @@ func (s *storage) rewrite(ctx context.Context, feeds []*model.Feed) ([]*model.Fe
|
|||||||
}(item)
|
}(item)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if allFailed := len(errs) == len(feeds); allFailed {
|
|
||||||
return nil, errs[0]
|
switch len(errs) {
|
||||||
}
|
case 0:
|
||||||
if len(errs) > 0 {
|
case len(feeds) - int(dropped.Load()):
|
||||||
|
return nil, errs[0] // All failed.
|
||||||
|
default:
|
||||||
log.Error(ctx, errors.Wrap(errs[0], "rewrite feeds"), "error_count", len(errs))
|
log.Error(ctx, errors.Wrap(errs[0], "rewrite feeds"), "error_count", len(errs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user