Compare commits
6 Commits
analysis-a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
094600ee26 | ||
|
|
c03e4c8359 | ||
|
|
584f94e1ef | ||
|
|
6c4223de92 | ||
|
|
f67db8ea86 | ||
|
|
bc54cc852e |
@@ -1,44 +0,0 @@
|
||||
# Design Principles for Expert-Centric Systems
|
||||
|
||||
This document outlines a set of core design principles for creating powerful, flexible, and developer-first software systems. These principles are derived from an analysis of projects that prioritize user control and system transparency over simplified, mass-market user experiences.
|
||||
|
||||
## I. Core Philosophy: Empower the Expert
|
||||
|
||||
The central philosophy is to build tools for the top 10% of users—the power users, developers, and domain experts. These users demand control, deep customization, and transparency. By satisfying their needs, the system becomes inherently robust and capable, often serving a wider audience in simpler configurations as a secondary benefit.
|
||||
|
||||
**Motto:** "Give experts the tools to build their own castles."
|
||||
|
||||
## II. Guiding Principles
|
||||
|
||||
### 1. **Build Engines, Not Just Interfaces**
|
||||
- **Principle:** The core of the product should be a robust, well-documented, API-first engine. All user interfaces (web, mobile, CLI) are considered clients of this core engine. New functionality is always implemented in the engine first.
|
||||
- **Rationale:** This approach ensures that the system's core logic is decoupled from its presentation, promoting stability, testability, and multi-platform support from day one.
|
||||
|
||||
### 2. **Embrace Configuration as Code**
|
||||
- **Principle:** Prefer declarative, text-based configuration (e.g., YAML, JSON, HCL) over complex graphical user interfaces for system setup and logic definition.
|
||||
- **Rationale:** "Config-as-code" is version-controllable, transparent, and infinitely more powerful for expressing complex logic. It allows users to manage system behavior with the same rigor and tooling they use for source code. The UI should be a convenient way to *manage* this configuration, not hide it.
|
||||
|
||||
### 3. **Design for Modularity and Composability**
|
||||
- **Principle:** Architect the system as a collection of powerful, independent components that can be wired together in flexible ways. Follow the Unix philosophy: create small, focused components that do one thing well and can be chained together.
|
||||
- **Rationale:** A modular, pipeline-based architecture (e.g., `Ingest -> Transform -> Store -> Process -> Notify`) allows users to create their own unique workflows by composing the provided building blocks. This makes the system adaptable to use cases the original designers may not have envisioned.
|
||||
|
||||
### 4. **Transparency is a Feature (The "Glass Box" Approach)**
|
||||
- **Principle:** The system's internal logic must be transparent and auditable. Users should be able to understand precisely *why* a piece of data was processed in a certain way by tracing it through the configuration and system logs. Avoid "magic" black boxes.
|
||||
- **Rationale:** Expert users need to trust the system. Trust is built on understanding and control. When something goes wrong, a transparent system is debuggable, while a black box is merely frustrating.
|
||||
|
||||
### 5. **Technology as an Augmentation Tool**
|
||||
- **Principle:** When incorporating complex technologies (like AI, machine learning, or advanced algorithms), position them as tools to be wielded by the user, not as replacements for user judgment. The user must remain in control.
|
||||
- **Rationale:** This ensures the user is the ultimate authority. The system provides powerful capabilities, but the user defines *how* they are applied through rules, prompts, or scripts, maintaining agency and control over the final outcome.
|
||||
|
||||
### 6. **Be Pragmatic and Lean**
|
||||
- **Principle:** Focus relentlessly on the core processing pipeline and a stable, extensible architecture. Be willing to omit features that add complexity without contributing to the core value proposition for expert users (e.g., complex user management systems in a tool designed for self-hosting).
|
||||
- **Rationale:** This keeps the product lean, focused, and maintainable. It assumes that expert users are capable of integrating the tool into their own infrastructure (e.g., placing it behind a reverse proxy for authentication).
|
||||
|
||||
## III. Prioritization Framework
|
||||
|
||||
When evaluating new features, use the following hierarchy of questions:
|
||||
|
||||
1. **Flexibility and Composability:** Does it increase the system's architectural flexibility or the ability to compose existing components in new ways? (Highest Priority)
|
||||
2. **Expert Empowerment:** Does it empower the expert user to solve a complex, high-value problem that was previously out of reach?
|
||||
3. **Integration and Extensibility:** Does it unblock a new integration point or workflow with other systems?
|
||||
4. **User Experience Simplification:** Is it a UI tweak or a feature aimed at simplifying a task for less technical users? (Lowest Priority, unless it can be implemented without compromising the power of the underlying engine).
|
||||
24
README.md
24
README.md
@@ -1,5 +1,3 @@
|
||||
[Nano Banana🍌 公益站](https://image-generation.zenfeed.xyz/):集成 Twitter 热门 Prompt,轻松玩转各种姿势
|
||||
---
|
||||
[English](README-en.md)
|
||||
|
||||
---
|
||||
@@ -55,14 +53,6 @@ zenfeed 是你的 <strong>AI 信息中枢</strong>。它既是<strong>智能 RSS
|
||||
|
||||
---
|
||||
|
||||
**赞助项目可以领取 Gemini Key**
|
||||
|
||||
<a href="https://afdian.com/a/glidea"><img src="docs/images/sponsor.png" width="500"></a>
|
||||
<br/>
|
||||
<a href="https://afdian.com/a/glidea">赞助项目,支持发展</a>
|
||||
|
||||
---
|
||||
|
||||
## 💡 前言
|
||||
|
||||
RSS(简易信息聚合)诞生于 Web 1.0 时代,旨在解决信息分散的问题,让用户能在一个地方聚合、追踪多个网站的更新,无需频繁访问。它将网站更新以摘要形式推送给订阅者,便于快速获取信息。
|
||||
@@ -215,18 +205,26 @@ $env:API_KEY = "sk-..."; docker-compose -p zenfeed up -d
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="docs/images/wechat.png" alt="Wechat QR Code" width="300">
|
||||
<img src="https://github.com/glidea/zenfeed/blob/main/docs/images/wechat.png?raw=true" alt="Wechat QR Code" width="300">
|
||||
<br>
|
||||
<strong>AI 学习交流社群</strong>
|
||||
</td>
|
||||
<td align="center">
|
||||
<img src="docs/images/sponsor.png" width="500">
|
||||
<img src="https://github.com/glidea/banana-prompt-quicker/blob/main/images/glidea.png?raw=true" width="250">
|
||||
<br>
|
||||
<strong><a href="https://afdian.com/a/glidea">请杯奶茶 🧋</a></strong>
|
||||
<strong><a href="https://glidea.zenfeed.xyz/">我的其它项目</a></strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" colspan="2">
|
||||
<img src="https://github.com/glidea/banana-prompt-quicker/blob/main/images/readnote.png?raw=true" width="400">
|
||||
<br>
|
||||
<strong><a href="https://www.xiaohongshu.com/user/profile/5f7dc54d0000000001004afb">📕 小红书账号 - 持续分享 AI 原创</a></strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
都看到这里了,顺手点个 **Star ⭐️** 呗,这是我持续维护的最大动力!
|
||||
|
||||
有好玩的 AI 工作也请联系我!
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
| `scrape.past` | `time.Duration` | 抓取 Feed 的回溯时间窗口。例如 `1h` 表示只抓取过去 1 小时的 Feed。 | `24h` | 否 |
|
||||
| `scrape.interval` | `time.Duration` | 抓取每个源的频率 (全局默认值)。例如 `1h`。 | `1h` | 否 |
|
||||
| `scrape.rsshub_endpoint` | `string` | RSSHub 的端点。你可以部署自己的 RSSHub 服务器或使用公共实例 (参见 [RSSHub 文档](https://docs.rsshub.app/guide/instances))。例如 `https://rsshub.app`。 | | 是 (如果使用了 `rsshub_route_path`) |
|
||||
| `scrape.rsshub_access_key` | `string` | RSSHub 的访问密钥。用于访问控制。(详情见 [RSSHub文档访问控制](https://docs.rsshub.app/deploy/config#access-control-configurations)) | | 否 |
|
||||
| `scrape.sources` | `对象列表` | 用于抓取 Feed 的源列表。详见下方的 **抓取源配置**。 | `[]` | 是 (至少一个) |
|
||||
|
||||
### 抓取源配置 (`scrape.sources[]`)
|
||||
|
||||
@@ -59,6 +59,7 @@ This section configures parameters related to the Jina AI Reader API, primarily
|
||||
| `scrape.past` | `time.Duration` | Time window to look back when scraping feeds. E.g., `1h` means only scrape feeds from the past 1 hour. | `24h` | No |
|
||||
| `scrape.interval` | `time.Duration` | Frequency to scrape each source (global default). E.g., `1h`. | `1h` | No |
|
||||
| `scrape.rsshub_endpoint` | `string` | Endpoint for RSSHub. You can deploy your own RSSHub server or use a public instance (see [RSSHub Documentation](https://docs.rsshub.app/guide/instances)). E.g., `https://rsshub.app`. | | Yes (if `rsshub_route_path` is used) |
|
||||
| `scrape.rsshub_access_key` | `string` | The access key for RSSHub. Used for access control. (see [RSSHub config](https://docs.rsshub.app/deploy/config#access-control-configurations))| | No |
|
||||
| `scrape.sources` | `list of objects` | List of sources to scrape feeds from. See **Scrape Source Configuration** below. | `[]` | Yes (at least one) |
|
||||
|
||||
### Scrape Source Configuration (`scrape.sources[]`)
|
||||
|
||||
@@ -95,10 +95,11 @@ type LLM struct {
|
||||
}
|
||||
|
||||
type Scrape struct {
|
||||
Past timeutil.Duration `yaml:"past,omitempty" json:"past,omitempty" desc:"The lookback time window for scraping feeds. e.g. 1h means only scrape feeds in the past 1 hour. Default: 3d"`
|
||||
Interval timeutil.Duration `yaml:"interval,omitempty" json:"interval,omitempty" desc:"How often to scrape each source, it is a global interval. e.g. 1h. Default: 1h"`
|
||||
RSSHubEndpoint string `yaml:"rsshub_endpoint,omitempty" json:"rsshub_endpoint,omitempty" desc:"The endpoint of the RSSHub. You can deploy your own RSSHub server or use the public one (https://docs.rsshub.app/guide/instances). e.g. https://rsshub.app. It is required when sources[].rss.rsshub_route_path is set."`
|
||||
Sources []ScrapeSource `yaml:"sources,omitempty" json:"sources,omitempty" desc:"The sources for scraping feeds."`
|
||||
Past timeutil.Duration `yaml:"past,omitempty" json:"past,omitempty" desc:"The lookback time window for scraping feeds. e.g. 1h means only scrape feeds in the past 1 hour. Default: 3d"`
|
||||
Interval timeutil.Duration `yaml:"interval,omitempty" json:"interval,omitempty" desc:"How often to scrape each source, it is a global interval. e.g. 1h. Default: 1h"`
|
||||
RSSHubEndpoint string `yaml:"rsshub_endpoint,omitempty" json:"rsshub_endpoint,omitempty" desc:"The endpoint of the RSSHub. You can deploy your own RSSHub server or use the public one (https://docs.rsshub.app/guide/instances). e.g. https://rsshub.app. It is required when sources[].rss.rsshub_route_path is set."`
|
||||
RSSHubAccessKey string `yaml:"rsshub_access_key,omitempty" json:"rsshub_access_key,omitempty" desc:"The access key for RSSHub. Used for access control. (see [RSSHub config](https://docs.rsshub.app/deploy/config#access-control-configurations))"`
|
||||
Sources []ScrapeSource `yaml:"sources,omitempty" json:"sources,omitempty" desc:"The sources for scraping feeds."`
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
|
||||
@@ -80,6 +80,7 @@ func (c *Config) From(app *config.App) {
|
||||
URL: app.Scrape.Sources[i].RSS.URL,
|
||||
RSSHubEndpoint: app.Scrape.RSSHubEndpoint,
|
||||
RSSHubRoutePath: app.Scrape.Sources[i].RSS.RSSHubRoutePath,
|
||||
RSSHubAccessKey: app.Scrape.RSSHubAccessKey,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ type ScrapeSourceRSS struct {
|
||||
URL string
|
||||
RSSHubEndpoint string
|
||||
RSSHubRoutePath string
|
||||
RSSHubAccessKey string
|
||||
}
|
||||
|
||||
func (c *ScrapeSourceRSS) Validate() error {
|
||||
@@ -46,9 +47,22 @@ func (c *ScrapeSourceRSS) Validate() error {
|
||||
return errors.New("URL must be a valid HTTP/HTTPS URL")
|
||||
}
|
||||
|
||||
// Append access key as query parameter if provided
|
||||
c.appendAccessKey()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ScrapeSourceRSS) appendAccessKey() {
|
||||
if c.RSSHubEndpoint != "" && c.RSSHubAccessKey != "" && !strings.Contains(c.URL, "key=") {
|
||||
if strings.Contains(c.URL, "?") {
|
||||
c.URL += "&key=" + c.RSSHubAccessKey
|
||||
} else {
|
||||
c.URL += "?key=" + c.RSSHubAccessKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Factory code block ---
|
||||
func newRSSReader(config *ScrapeSourceRSS) (reader, error) {
|
||||
if err := config.Validate(); err != nil {
|
||||
|
||||
@@ -122,6 +122,55 @@ func TestNewRSS(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Scenario: "Valid Configuration - RSSHub with Access Key",
|
||||
Given: "a valid configuration with RSSHub details and access key",
|
||||
When: "creating a new RSS reader",
|
||||
Then: "should succeed, construct the URL with access key, and return a valid reader",
|
||||
GivenDetail: givenDetail{
|
||||
config: &ScrapeSourceRSS{
|
||||
RSSHubEndpoint: "http://rsshub.app/",
|
||||
RSSHubRoutePath: "/_/test",
|
||||
RSSHubAccessKey: "testkey",
|
||||
},
|
||||
},
|
||||
WhenDetail: whenDetail{},
|
||||
ThenExpected: thenExpected{
|
||||
wantErr: false,
|
||||
validateFunc: func(t *testing.T, r reader) {
|
||||
Expect(r).NotTo(BeNil())
|
||||
rssReader, ok := r.(*rssReader)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(rssReader.config.URL).To(Equal("http://rsshub.app/_/test?key=testkey"))
|
||||
Expect(rssReader.config.RSSHubEndpoint).To(Equal("http://rsshub.app/"))
|
||||
Expect(rssReader.config.RSSHubRoutePath).To(Equal("/_/test"))
|
||||
Expect(rssReader.config.RSSHubAccessKey).To(Equal("testkey"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Scenario: "Valid Configuration - URL with Access Key",
|
||||
Given: "a valid configuration with URL and access key",
|
||||
When: "creating a new RSS reader",
|
||||
Then: "should succeed, append access key to URL, and return a valid reader",
|
||||
GivenDetail: givenDetail{
|
||||
config: &ScrapeSourceRSS{
|
||||
URL: "http://example.com/feed",
|
||||
RSSHubAccessKey: "testkey",
|
||||
},
|
||||
},
|
||||
WhenDetail: whenDetail{},
|
||||
ThenExpected: thenExpected{
|
||||
wantErr: false,
|
||||
validateFunc: func(t *testing.T, r reader) {
|
||||
Expect(r).NotTo(BeNil())
|
||||
rssReader, ok := r.(*rssReader)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(rssReader.config.URL).To(Equal("http://example.com/feed"))
|
||||
Expect(rssReader.config.RSSHubAccessKey).To(Equal("testkey"))
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// --- Run tests ---
|
||||
|
||||
Reference in New Issue
Block a user