Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
15d81e4
feat(brief): 优化研究简报排版和视觉层次
Color2333 Mar 17, 2026
847722f
feat(config): 统一日报配置到数据库 (#配置规范化)
Color2333 Mar 17, 2026
97c46d6
feat(statistics): add topic statistics page with distribution analysis
Color2333 Mar 19, 2026
9db0e34
feat(statistics): extend distribution stats with monthly trend, venue…
Color2333 Mar 19, 2026
601f913
fix(statistics): use json_extract instead of astext for JSON fields
Color2333 Mar 19, 2026
299f781
fix(statistics): use json_extract for citation_count and polish UI style
Color2333 Mar 19, 2026
afc02ff
feat: 美化统计页面图表样式 - 渐变色/阴影/圆角优化
Color2333 Mar 19, 2026
7e0a7d8
fix: MonthlyTrend 使用 absolute 布局修复柱状图空白问题
Color2333 Mar 19, 2026
bad16eb
docs: add CS category feed design doc
Color2333 Mar 19, 2026
7cc22a5
docs: add CS category feed implementation plan
Color2333 Mar 19, 2026
66c08e6
feat: add GitHub agent workflow for AI review
Color2333 Mar 19, 2026
dce5d73
feat(cs-feeds): 实现 arXiv CS 分类订阅功能
Color2333 Mar 19, 2026
28779ec
style: 格式化代码 (ruff format + prettier)
Color2333 Mar 19, 2026
c5af195
feat: 添加 PR Review Gate workflow
Color2333 Mar 19, 2026
15caac4
Merge branch 'main' into dev
Color2333 Mar 19, 2026
323f073
fix: 简化 PR Review Gate workflow
Color2333 Mar 19, 2026
778fe8d
fix: 添加 checkout step 修复 gh pr comment 问题
Color2333 Mar 19, 2026
041b4ef
fix: 添加 pull-requests: write 权限
Color2333 Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/opencode.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: opencode

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]

jobs:
opencode:
if: |
contains(github.event.comment.body, ' /oc') ||
startsWith(github.event.comment.body, '/oc') ||
contains(github.event.comment.body, ' /opencode') ||
startsWith(github.event.comment.body, '/opencode')
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
pull-requests: read
issues: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Run opencode
uses: anomalyco/opencode/github@latest
env:
ALIBABA_CODING_PLAN_API_KEY: ${{ secrets.ALIBABA_CODING_PLAN_API_KEY }}
with:
model: alibaba-coding-plan-cn/qwen3.5-plus
58 changes: 58 additions & 0 deletions .github/workflows/pr-review-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: PR Review Gate

on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number'
required: false
type: string

jobs:
pr-review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Get PR Number
id: pr
run: |
if [ -n "${{ github.event.inputs.pr_number }}" ]; then
PR_NUM="${{ github.event.inputs.pr_number }}"
else
PR_NUM="${{ github.event.number }}"
fi
echo "pr_number=$PR_NUM" >> $GITHUB_OUTPUT

- name: Post OpenCode Review Request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUM="${{ steps.pr.outputs.pr_number }}"

BODY='## 🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

**请运行以下命令进行 OpenCode review:**
```
/oc review https://github.com/${{ github.repository }}/pull/$PR_NUM
```

或者在 PR 页面评论 `/oc` 来触发 OpenCode review。

---

*This is an automated reminder from PR Review Gate.*'

gh pr comment $PR_NUM --body "$BODY"

- name: Set status check to success
run: |
echo "OpenCode review request posted."
2 changes: 2 additions & 0 deletions apps/api/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def invalidate_prefix(self, prefix: str):
for k in keys:
del self._store[k]


cache = TTLCache()


Expand Down Expand Up @@ -97,6 +98,7 @@ def iso_dt(dt: datetime | None) -> str | None:

def brief_date() -> str:
from packages.timezone import user_date_str

return user_date_str()


Expand Down
8 changes: 7 additions & 1 deletion apps/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ async def dispatch(self, request: Request, call_next):
request.state.user = payload
return await call_next(request)


# ---------- 启动时检查认证配置 ----------

settings = get_settings()
Expand All @@ -119,6 +120,7 @@ async def dispatch(self, request: Request, call_next):
app.add_middleware(AuthMiddleware)
app.add_middleware(GZipMiddleware, minimum_size=1000) # Starlette 内置跳过 text/event-stream


@app.exception_handler(AppError)
async def app_error_handler(_request: Request, exc: AppError):
"""统一处理所有业务异常"""
Expand Down Expand Up @@ -150,19 +152,23 @@ async def app_error_handler(_request: Request, exc: AppError):
agent,
auth,
content,
cs_feeds,
graph,
jobs,
papers,
pipelines,
settings as settings_router,
system,
topics,
writing,
)
from apps.api.routers import (
settings as settings_router,
)

app.include_router(system.router)
app.include_router(papers.router)
app.include_router(topics.router)
app.include_router(cs_feeds.router)
app.include_router(graph.router)
app.include_router(agent.router)
app.include_router(content.router)
Expand Down
34 changes: 20 additions & 14 deletions apps/api/routers/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,25 +111,31 @@ def stream_with_save():
text_content += data.get("content", "")
elif event_type == "tool_result":
# 记录工具调用结果
tool_calls_records.append({
"name": data.get("name"),
"success": data.get("success"),
"summary": data.get("summary"),
"data": data.get("data"),
})
tool_calls_records.append(
{
"name": data.get("name"),
"success": data.get("success"),
"summary": data.get("summary"),
"data": data.get("data"),
}
)
elif event_type == "action_result":
# 记录用户确认的操作结果
tool_calls_records.append({
"action_id": data.get("id"),
"success": data.get("success"),
"summary": data.get("summary"),
"data": data.get("data"),
})
tool_calls_records.append(
{
"action_id": data.get("id"),
"success": data.get("success"),
"summary": data.get("summary"),
"data": data.get("data"),
}
)
yield chunk

# 流结束后保存助手响应
if text_content or tool_calls_records:
_save_assistant_response(text_content, tool_calls_records if tool_calls_records else None)
_save_assistant_response(
text_content, tool_calls_records if tool_calls_records else None
)

return StreamingResponse(
stream_with_save(),
Expand Down Expand Up @@ -186,7 +192,7 @@ def get_conversation_messages(
) -> dict:
"""获取指定会话的所有消息"""
from packages.storage.db import session_scope
from packages.storage.repositories import AgentMessageRepository, AgentConversationRepository
from packages.storage.repositories import AgentConversationRepository, AgentMessageRepository

with session_scope() as session:
conv_repo = AgentConversationRepository(session)
Expand Down
8 changes: 4 additions & 4 deletions apps/api/routers/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ async def login(request: LoginRequest):
成功返回 JWT token
"""
settings = get_settings()

# 如果未配置密码,返回错误
if not settings.auth_password:
raise HTTPException(status_code=403, detail="Authentication is disabled")

# 验证密码
if not authenticate_user(request.password):
raise HTTPException(status_code=401, detail="Incorrect password")

# 生成 token
access_token = create_access_token(data={"sub": "papermind-user"})
return LoginResponse(access_token=access_token)
Expand All @@ -52,4 +52,4 @@ async def auth_status():
检查认证是否启用
"""
settings = get_settings()
return AuthStatusResponse(auth_enabled=bool(settings.auth_password))
return AuthStatusResponse(auth_enabled=bool(settings.auth_password))
13 changes: 11 additions & 2 deletions apps/api/routers/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from fastapi import APIRouter, HTTPException, Query

from apps.api.deps import brief_date, brief_service, cache, graph_service, iso_dt, settings
from apps.api.deps import brief_service, cache, graph_service, iso_dt
from packages.domain.schemas import DailyBriefRequest
from packages.domain.task_tracker import global_tracker
from packages.storage.db import session_scope
Expand Down Expand Up @@ -168,7 +168,16 @@ def daily_brief(req: DailyBriefRequest) -> dict:
"""生成每日简报(异步任务)"""
from packages.domain.task_tracker import global_tracker

recipient = req.recipient or settings.notify_default_to
# 如果没有指定收件人,从数据库读取配置
recipient = req.recipient
if not recipient:
from packages.storage.db import session_scope
from packages.storage.repositories import DailyReportConfigRepository

with session_scope() as session:
config = DailyReportConfigRepository(session).get_config()
if config.send_email_report and config.recipient_emails:
recipient = config.recipient_emails.split(",")[0]

def _generate_fn(progress_callback=None):
# publish() 内部已写入 generated_content 表,无需重复
Expand Down
Loading