Skip to content

refactor: dashboard/settings redesign + keychain API key storage#99

Merged
jerry609 merged 9 commits into
feat/datainfra-refactor-trackerfrom
fix/encrypt-api-keys-at-rest
Feb 12, 2026
Merged

refactor: dashboard/settings redesign + keychain API key storage#99
jerry609 merged 9 commits into
feat/datainfra-refactor-trackerfrom
fix/encrypt-api-keys-at-rest

Conversation

@jerry609

Copy link
Copy Markdown
Owner

Summary

Dashboard + Settings 破坏性重构,附带后端 Keychain 安全存储。

Dashboard

  • 新布局:inline StatsBar header + 2/3 ActivityTimeline + 1/3 右栏(LLMUsageChart / SavedPapers / DeadlineRadar)
  • 删除 9 个废弃组件(StatsCard, QuickActions, QuickFilters, PipelineStatus, ReadingQueue, ActivityFeed, feed/*)
  • 精简 LLMUsageChart(provider totals inline)和 DeadlineRadar 样式
  • 新增 usage proxy route (/api/model-endpoints/usage)

Settings

  • ccswitch 风格 provider 卡片(绿色左边框 = default,状态圆点,masked key)
  • Dialog 弹窗表单替代原始内联表单
  • Quick Presets 底部快捷入口
  • Keychain 标记显示

Backend

  • KeychainStore:封装 keyring 库,API key 存入 macOS Keychain
  • model_endpoint_store.py:upsert 走 Keychain(DB 存 __keychain__ 标记),delete 清理 Keychain,_to_dict 从 Keychain 读取
  • Fallback:keyring 不可用时回退到 DB Fernet 加密

Types/API

  • 新增 TimelineItem / SavedPaper 类型,删除 Activity / PipelineTask / ReadingQueueItem
  • fetchActivities → 真实 paper 数据转 timeline
  • fetchSavedPapers 替代 fetchReadingQueue
  • fetchLLMUsage SSR 走后端直连,CSR 走 Next.js proxy

- PaperCard: collapsible evidence quotes section below judge scores
- SavedTab: export dropdown (BibTeX/RIS/Markdown) triggering browser download
- TopicWorkflowDashboard: Download .md button for daily paper reports
- SearchResults: add papers_cool and hf_daily to SOURCE_OPTIONS

Closes #96
…ion test

- Add GET /api/research/metrics/evidence-coverage endpoint that returns
  coverage_rate, total_claims, total_with_evidence, and daily trend data
- Add unit test verifying saved papers rank higher than skipped papers
  in list_track_feed
- Enhance POST /api/model-endpoints/{id}/test to return latency_ms,
  success, and error fields
- Add unit tests for all new functionality

Closes #92
Closes #95
Closes #97
- Add utils/secret.py with encrypt/decrypt using Fernet (AES-128-CBC + HMAC-SHA256)
- Encryption key from PAPERBOT_SECRET_KEY env var, fallback key with warning
- model_endpoint_store: encrypt on write, decrypt on read
- Graceful migration: non-Fernet values (legacy plaintext) pass through on decrypt
- Fix pre-existing test bugs (unquoted f-string path expressions)

Closes #77
…gine

- Remove paper_searcher parameter from ContextEngine.__init__
- Remove legacy SemanticScholarSearch import and direct API call path
- ContextEngine now exclusively uses PaperSearchService for paper search
- Fallback: logs warning and returns empty results if no search_service provided
- Remove unused PaperMeta import

Closes #78
- Replace direct harvester calls (ArxivHarvester, SemanticScholarHarvester,
  OpenAlexHarvester) with PaperSearchService.search()
- PaperSearchService handles dedup and persistence internally, removing
  the need for separate dedup and store phases
- Remove unused imports (HarvestedPaper, HarvestResult, HarvestRunResult,
  PaperDeduplicator, HarvesterPort, individual harvesters)
- Keep pipeline orchestration: keyword expansion, venue recommendation,
  harvest run records, progress events

Closes #79
Delete PapersCoolTopicSearchWorkflow, TopicSearchSourceRegistry,
SemanticScholarSearch and their test files. Update all test mocks
to patch _run_topic_search / run_unified_topic_search instead of
the removed classes. Fix pre-existing SSE event assertions that
expected per-paper judge events no longer emitted by
EnrichmentPipeline.

Closes #80
Rewrite dashboard with clean 2/3+1/3 grid (StatsBar, ActivityTimeline,
LLMUsageChart, SavedPapers, DeadlineRadar), delete 9 deprecated
components. Rebuild settings page with ccswitch-style provider cards
and Dialog form. Add KeychainStore for macOS Keychain API key storage
with DB encryption fallback. Add usage proxy route.
Copilot AI review requested due to automatic review settings February 12, 2026 13:36
@coderabbitai

coderabbitai Bot commented Feb 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/encrypt-api-keys-at-rest

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jerry609
jerry609 merged commit efc9796 into feat/datainfra-refactor-tracker Feb 12, 2026
6 checks passed
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @jerry609, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a comprehensive overhaul of the user interface for the Dashboard and Settings pages, enhancing usability and visual consistency. Concurrently, it introduces a significant security upgrade by integrating Keychain for API key storage, ensuring sensitive data is handled more robustly. These changes are supported by underlying data model refactorings and API adjustments to streamline data flow and improve overall system architecture.

Highlights

  • Dashboard Redesign: The dashboard has been destructively refactored with a new layout, featuring an inline StatsBar header, a 2/3 width ActivityTimeline, and a 1/3 width right column for LLMUsageChart, SavedPapers, and DeadlineRadar. Nine deprecated components have been removed.
  • Settings Page Redesign: The settings page now uses a ccswitch-style provider card for model endpoints, dialog forms for editing, quick presets for common configurations, and displays Keychain markers for securely stored API keys.
  • Keychain API Key Storage: A new KeychainStore backend has been implemented to securely store API keys in the macOS Keychain (or SecretService/KWallet on Linux), with a fallback to DB Fernet encryption if Keychain is unavailable.
  • Data Model and API Refinements: New TimelineItem and SavedPaper types have been introduced, replacing Activity, PipelineTask, and ReadingQueueItem. API endpoints for activities and saved papers have been updated accordingly, and LLM usage fetching now supports SSR via a new usage proxy route.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • pyproject.toml
    • Added cryptography and keyring as new dependencies.
  • src/paperbot/api/routes/model_endpoints.py
    • Imported time module for latency measurement.
    • Updated EndpointTestResponse to include success, latency_ms, and error fields.
    • Implemented latency measurement using time.monotonic() during model endpoint testing.
    • Modified test_model_endpoint to return detailed test results including latency and error information.
  • src/paperbot/api/routes/paperscool.py
    • Enhanced _count_report_claims_and_evidence to count evidence quotes from the judge's output.
    • Removed direct invocation of PapersCoolTopicSearchWorkflow, now utilizing run_unified_topic_search.
  • src/paperbot/api/routes/research.py
    • Imported Response from fastapi.responses for custom responses.
    • Defined EvidenceCoverageResponse Pydantic model for new metrics endpoint.
    • Added a new GET endpoint /research/metrics/evidence-coverage to retrieve evidence coverage statistics.
    • Introduced utility functions (_make_citation_key, _dedup_citation_keys, _escape_bibtex, _paper_to_bibtex, _paper_to_ris, _paper_to_markdown) for paper export formatting.
    • Implemented a new GET endpoint /research/papers/export to allow exporting saved papers in BibTeX, RIS, or Markdown formats.
  • src/paperbot/application/workflows/analysis/judge_prompts.py
    • Modified the LLM prompt structure to include evidence_quotes for more detailed paper judgments.
  • src/paperbot/application/workflows/analysis/paper_judge.py
    • Added evidence_quotes field to PaperJudgment dataclass with proper initialization.
    • Included evidence_quotes in the to_dict serialization method.
    • Integrated evidence_quotes into the judgment payload processing.
    • Implemented _parse_evidence_quotes static method for robust parsing of evidence quotes from LLM output.
  • src/paperbot/application/workflows/harvest_pipeline.py
    • Removed several unused imports related to old harvesting and deduplication mechanisms.
    • Introduced PaperSearchService and make_default_search_service for unified paper search.
    • Refactored HarvestPipeline to delegate search, deduplication, and persistence to PaperSearchService, removing internal deduplicator and individual harvesters.
    • Updated run method to use the new PaperSearchService for a streamlined harvesting process.
    • Adjusted update_harvest_run parameters to align with the new search service's output.
    • Modified the close method to properly shut down the PaperSearchService.
  • src/paperbot/application/workflows/paperscool_topic_search.py
    • Removed the entire paperscool_topic_search.py file, as its functionality is now integrated into unified_topic_search.
  • src/paperbot/application/workflows/topic_search_sources.py
    • Removed the entire topic_search_sources.py file, as its functionality is now integrated into unified_topic_search.
  • src/paperbot/context_engine/engine.py
    • Removed PaperMeta import, simplifying data models.
    • Eliminated the paper_searcher attribute from ContextEngine initialization.
    • Removed the legacy SemanticScholarSearch path and related paper parsing logic, now relying on the unified search_service.
  • src/paperbot/infrastructure/stores/keychain.py
    • Added new file keychain.py to provide KeychainStore for secure API key management using the keyring library.
  • src/paperbot/infrastructure/stores/model_endpoint_store.py
    • Imported logger and KeychainStore for enhanced logging and secure storage.
    • Defined _KEYCHAIN_MARKER to indicate Keychain-stored API keys.
    • Modified upsert_endpoint to store API keys in Keychain if available, otherwise encrypt them in the database.
    • Updated delete_endpoint to remove corresponding API keys from Keychain.
    • Adjusted _to_dict to retrieve API keys from Keychain when marked, and added key_source to indicate storage location.
  • src/paperbot/infrastructure/stores/paper_store.py
    • Corrected key access logic in get_user_library for sorting by saved_at and default values.
  • src/paperbot/utils/search.py
    • Removed the entire search.py file, as its functionality has been replaced by a more unified search service.
  • src/paperbot/utils/secret.py
    • Added new file secret.py providing encrypt and decrypt functions for symmetric encryption using cryptography.fernet.
  • tests/test_framework.py
    • Removed TestSearchModule class and its associated tests.
  • tests/unit/test_arq_daily_papers.py
    • Replaced _FakeWorkflow with a dictionary _fake_search_result and an async function _fake_run_unified for testing.
    • Updated monkeypatching to target unified_topic_search instead of paperscool_topic_search.
  • tests/unit/test_connection_test_endpoint.py
    • Added new file test_connection_test_endpoint.py to test model endpoint connection functionality, including latency and error reporting.
  • tests/unit/test_evidence_coverage_route.py
    • Added new file test_evidence_coverage_route.py to test the new /research/metrics/evidence-coverage API endpoint.
  • tests/unit/test_export.py
    • Added new file test_export.py to test paper export functionalities for BibTeX, RIS, and Markdown formats, including citation key handling.
  • tests/unit/test_feed_ranking.py
    • Added new file test_feed_ranking.py to verify that saved papers are ranked higher than skipped papers in the feed.
  • tests/unit/test_model_endpoint_store.py
    • Corrected database URL paths in various tests for ModelEndpointStore.
  • tests/unit/test_paper_judge.py
    • Added tests to ensure PaperJudge correctly parses and handles evidence_quotes from LLM output.
    • Verified that evidence_quotes default to an empty list when missing or invalid.
  • tests/unit/test_paperscool_cli.py
    • Replaced _FakeWorkflow with _FAKE_SEARCH_RESULT and _fake_unified_search for CLI tests.
    • Updated monkeypatching to use run_unified_topic_search for CLI commands.
  • tests/unit/test_paperscool_route.py
    • Replaced _FakeWorkflow and PapersCoolTopicSearchWorkflow with _fake_run_topic_search and _fake_run_topic_search_multi for route tests.
    • Updated monkeypatching to use _run_topic_search for API routes.
    • Removed specific assertions related to the 'judge' event type in SSE tests.
  • tests/unit/test_paperscool_topic_search.py
    • Removed the entire test_paperscool_topic_search.py file.
  • tests/unit/test_topic_search_sources.py
    • Removed the entire test_topic_search_sources.py file.
  • web/src/app/api/model-endpoints/usage/route.ts
    • Added new file route.ts to create a Next.js API route for proxying LLM usage data.
  • web/src/app/dashboard/page.tsx
    • Refactored the dashboard layout to a 2/3 + 1/3 grid structure.
    • Replaced StatsCard with the new StatsBar component.
    • Replaced ActivityFeed with the new ActivityTimeline component.
    • Removed PipelineStatus, ReadingQueue, QuickActions, and QuickFilters components.
    • Integrated the new SavedPapers component into the dashboard.
    • Updated data fetching to use fetchActivities and fetchSavedPapers, removing calls to fetchTrendingTopics, fetchPipelineTasks, and fetchReadingQueue.
    • Simplified the display of LLM usage data within the chart.
  • web/src/app/settings/page.tsx
    • Redesigned the UI for model providers using enhanced Card components with visual indicators for default status.
    • Implemented a Dialog component for a more structured add/edit experience for model endpoints.
    • Added maskKey and statusDot utility functions for improved UI presentation.
    • Updated QUICK_PRESETS with a reordered 'OpenRouter' entry.
    • Modified openAdd and openEdit functions to manage the state of the new dialog component.
    • Updated saveItem, deleteItem, activateItem, and testItem functions to align with the new UI and backend Keychain integration.
    • Introduced key_source display to indicate whether an API key is stored in Keychain or the database.
  • web/src/components/dashboard/ActivityFeed.tsx
    • Removed the entire ActivityFeed.tsx file.
  • web/src/components/dashboard/ActivityTimeline.tsx
    • Added new file ActivityTimeline.tsx to display recent user activities in a chronological timeline format.
  • web/src/components/dashboard/DeadlineRadar.tsx
    • Adjusted styling and spacing for a cleaner look.
    • Limited the number of displayed deadline items to five.
    • Simplified the text for track badges.
    • Removed the 'Open in Workflows' link.
    • Shortened the 'Official CFP' link text to 'CFP'.
  • web/src/components/dashboard/LLMUsageChart.tsx
    • Updated CardHeader to display total tokens and cost directly.
    • Adjusted chart height and font sizes for better readability.
    • Added a message to display when no usage data is available.
  • web/src/components/dashboard/PipelineStatus.tsx
    • Removed the entire PipelineStatus.tsx file.
  • web/src/components/dashboard/QuickActions.tsx
    • Removed the entire QuickActions.tsx file.
  • web/src/components/dashboard/QuickFilters.tsx
    • Removed the entire QuickFilters.tsx file.
  • web/src/components/dashboard/ReadingQueue.tsx
    • Removed the entire ReadingQueue.tsx file.
  • web/src/components/dashboard/SavedPapers.tsx
    • Added new file SavedPapers.tsx to display a list of saved papers on the dashboard.
  • web/src/components/dashboard/StatsBar.tsx
    • Added new file StatsBar.tsx for a compact, inline display of key statistics.
  • web/src/components/dashboard/StatsCard.tsx
    • Removed the entire StatsCard.tsx file.
  • web/src/components/dashboard/feed/ConferenceCard.tsx
    • Removed the entire ConferenceCard.tsx file.
  • web/src/components/dashboard/feed/MilestoneCard.tsx
    • Removed the entire MilestoneCard.tsx file.
  • web/src/components/dashboard/feed/NewPaperCard.tsx
    • Removed the entire NewPaperCard.tsx file.
  • web/src/components/research/PaperCard.tsx
    • Extended the Paper type to include evidence_quotes.
    • Implemented UI to display evidence_quotes with an expand/collapse toggle.
  • web/src/components/research/SavedTab.tsx
    • Added functionality to export saved papers in BibTeX, RIS, or Markdown formats.
    • Integrated the refresh button into a new dropdown menu for export options.
  • web/src/components/research/SearchResults.tsx
    • Added papers_cool and hf_daily to the SOURCE_OPTIONS for search results.
  • web/src/components/research/TopicWorkflowDashboard.tsx
    • Added a download button for the DailyPaper markdown report.
  • web/src/lib/api.ts
    • Removed Activity, PipelineTask, and ReadingQueueItem types.
    • Introduced new TimelineItem and SavedPaper types.
    • Refactored fetchActivities to dynamically generate timeline items from fetched papers.
    • Added fetchSavedPapers to retrieve a curated list of saved papers.
    • Modified fetchLLMUsage to leverage a Next.js proxy route for client-side rendering compatibility.
  • web/src/lib/types.ts
    • Updated the Paper interface to include a 'Saved' status.
    • Replaced the Activity interface with new TimelineItem and SavedPaper interfaces.
    • Removed the PipelineTask and ReadingQueueItem interfaces.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR delivers a breaking redesign of the Dashboard and Settings UI, alongside backend support for secure API key storage (Keychain-first with encrypted-DB fallback) and several related API/type updates.

Changes:

  • Redesign Dashboard layout (StatsBar + ActivityTimeline + right-rail cards) and remove deprecated dashboard/feed components.
  • Redesign Settings providers UX (card list + add/edit dialog) and surface Keychain status/masked key display.
  • Add backend utilities/endpoints for Keychain/secret handling plus new export + metrics endpoints, with updated tests.

Reviewed changes

Copilot reviewed 50 out of 50 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
web/src/lib/types.ts Update frontend types (TimelineItem/SavedPaper) and expand Paper status.
web/src/lib/api.ts Update dashboard-related fetchers and add SSR/CSR usage fetching behavior.
web/src/components/research/TopicWorkflowDashboard.tsx Add client-side markdown download for DailyPaper report.
web/src/components/research/SearchResults.tsx Add new search source options.
web/src/components/research/SavedTab.tsx Add export dropdown for saved papers.
web/src/components/research/PaperCard.tsx Add evidence quotes UI for judge results.
web/src/components/dashboard/feed/NewPaperCard.tsx Remove deprecated feed card component.
web/src/components/dashboard/feed/MilestoneCard.tsx Remove deprecated feed card component.
web/src/components/dashboard/feed/ConferenceCard.tsx Remove deprecated feed card component.
web/src/components/dashboard/StatsCard.tsx Remove deprecated stats card component.
web/src/components/dashboard/StatsBar.tsx Add compact stats header component.
web/src/components/dashboard/SavedPapers.tsx Add dashboard card for saved papers list.
web/src/components/dashboard/ReadingQueue.tsx Remove deprecated reading queue component.
web/src/components/dashboard/QuickFilters.tsx Remove deprecated quick filters component.
web/src/components/dashboard/QuickActions.tsx Remove deprecated quick actions component.
web/src/components/dashboard/PipelineStatus.tsx Remove deprecated pipeline status component.
web/src/components/dashboard/LLMUsageChart.tsx Simplify chart header + empty state behavior.
web/src/components/dashboard/DeadlineRadar.tsx Tweak radar layout and limit displayed items.
web/src/components/dashboard/ActivityTimeline.tsx Add new timeline component for recent activity.
web/src/components/dashboard/ActivityFeed.tsx Remove deprecated activity feed component.
web/src/app/settings/page.tsx Redesign providers settings page with dialog-based editing + keychain indicator.
web/src/app/dashboard/page.tsx Switch dashboard to new layout and new data sources.
web/src/app/api/model-endpoints/usage/route.ts Add Next.js proxy route for usage endpoint.
tests/unit/test_topic_search_sources.py Remove tests for removed legacy topic search sources module.
tests/unit/test_paperscool_topic_search.py Remove tests for removed legacy paperscool topic search workflow.
tests/unit/test_paperscool_route.py Update route tests to monkeypatch unified search runner.
tests/unit/test_paperscool_cli.py Update CLI tests to use unified search runner mock.
tests/unit/test_paper_judge.py Add evidence_quotes parsing/serialization tests.
tests/unit/test_model_endpoint_store.py Fix sqlite tmp_path string interpolation in tests.
tests/unit/test_feed_ranking.py Add ranking test asserting saved beats skipped.
tests/unit/test_export.py Add export formatting tests (BibTeX/RIS/Markdown).
tests/unit/test_evidence_coverage_route.py Add tests for evidence coverage metrics endpoint.
tests/unit/test_connection_test_endpoint.py Add tests for model endpoint connection test response.
tests/unit/test_arq_daily_papers.py Update daily job test to mock unified search runner.
tests/test_framework.py Remove search module tests tied to removed legacy search utilities.
src/paperbot/utils/secret.py Add Fernet-based secret encryption helper.
src/paperbot/utils/search.py Remove legacy Semantic Scholar search utility module.
src/paperbot/infrastructure/stores/paper_store.py Adjust library sorting key usage.
src/paperbot/infrastructure/stores/model_endpoint_store.py Implement keychain-backed API key storage with encrypted-DB fallback.
src/paperbot/infrastructure/stores/keychain.py Add keyring wrapper for OS keychain storage.
src/paperbot/context_engine/engine.py Remove legacy direct SemanticScholarSearch fallback path.
src/paperbot/application/workflows/topic_search_sources.py Remove legacy topic search sources module.
src/paperbot/application/workflows/paperscool_topic_search.py Remove legacy papers.cool topic search workflow.
src/paperbot/application/workflows/harvest_pipeline.py Switch harvesting to unified PaperSearchService.
src/paperbot/application/workflows/analysis/paper_judge.py Add evidence_quotes to judgment model + parsing.
src/paperbot/application/workflows/analysis/judge_prompts.py Extend judge prompt schema to include evidence_quotes.
src/paperbot/api/routes/research.py Add evidence coverage endpoint + paper export endpoint and helpers.
src/paperbot/api/routes/paperscool.py Count judge evidence_quotes as evidence in metrics.
src/paperbot/api/routes/model_endpoints.py Add latency/success fields to endpoint test response.
pyproject.toml Add keyring + cryptography dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 126 to +142
row.name = name
row.vendor = vendor
row.base_url = str(payload.get("base_url") or row.base_url or "").strip() or None
row.api_key_env = (
str(payload.get("api_key_env") or row.api_key_env or "OPENAI_API_KEY").strip()
or "OPENAI_API_KEY"
)
if "api_key" in payload:
api_key_text = str(payload.get("api_key") or "").strip()
if not api_key_text:
row.api_key_value = None
KeychainStore.delete_key(name)
elif not api_key_text.startswith("***"):
row.api_key_value = api_key_text
if KeychainStore.store_key(name, api_key_text):
row.api_key_value = _KEYCHAIN_MARKER
else:
row.api_key_value = _encrypt_secret(api_key_text)

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keychain entries are keyed by endpoint_name (KeychainStore.store_key(name, ...) / get_key(row.name)). If an endpoint is renamed without re-entering the API key, the stored secret becomes unreachable (and delete_key will also target the new name, leaving the old entry behind). Prefer a stable identifier for the keyring username (e.g., endpoint id) or add explicit rename/migration logic (copy old key -> new name, then delete old).

Copilot uses AI. Check for mistakes.
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from loguru import logger

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logger is imported but never used in this module. Remove the import to avoid lint/type-check noise.

Suggested change
from loguru import logger

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +49
raw = os.getenv(_ENV_KEY, "").strip()
if raw:
# Accept either a raw 32-byte-urlsafe-b64 Fernet key or an
# arbitrary passphrase that we hash into one.
try:
Fernet(raw.encode())
key = raw.encode()
except Exception:
key = base64.urlsafe_b64encode(
hashlib.sha256(raw.encode()).digest()
)
else:
logger.warning(
f"{_ENV_KEY} is not set — using deterministic fallback key. "
"Set this variable in production to secure API keys at rest."
)
key = base64.urlsafe_b64encode(
hashlib.sha256(b"paperbot-default-insecure-key").digest()
)

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When PAPERBOT_SECRET_KEY is missing, _get_fernet() derives a deterministic fallback key from a constant string. That makes DB-encrypted API keys decryptable by anyone with code access and can give a false sense of “encryption at rest”. Consider failing closed (raise) when attempting to encrypt/decrypt secrets without a configured key, or generate a random key once and require it to be persisted/managed explicitly (and gate the relaxed behavior behind a clear dev-only flag).

Copilot uses AI. Check for mistakes.
Comment thread web/src/lib/api.ts
Comment on lines +113 to +117
// Use Next.js proxy route for SSR compatibility
const url = typeof window === "undefined"
? `${API_BASE_URL}/model-endpoints/usage?${qs.toString()}`
: `/api/model-endpoints/usage?${qs.toString()}`
const res = await fetch(url, { cache: "no-store" })

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PAPERBOT_API_BASE_URL is treated as including /api here (default is ...:8000/api), but Next.js proxy helpers (apiBaseUrl() in app/api/**) treat the same env var as the server root (default ...:8000). With a real env value, this can easily lead to double-missing or double-/api prefixes depending on where it’s used. Consider standardizing the env var to always be the root origin (no /api) and have lib/api.ts append /api in its path joins (or rename the env vars to make the contract unambiguous).

Copilot uses AI. Check for mistakes.
for k in keys:
count = seen.get(k, 0)
seen[k] = count + 1
result.append(k if count == 0 else f"{k}{chr(ord('a') + count)}")

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_dedup_citation_keys says it appends a/b/c suffixes, but the implementation appends b for the first collision (because count starts at 1 for the first duplicate). Either change the algorithm to start with a on the first collision (more standard for BibTeX) or update the docstring/tests to match the intended suffix scheme.

Suggested change
result.append(k if count == 0 else f"{k}{chr(ord('a') + count)}")
result.append(k if count == 0 else f"{k}{chr(ord('a') + count - 1)}")

Copilot uses AI. Check for mistakes.
error=None,
)
except Exception as exc:
latency_ms = int((time.monotonic() - t0) * 1000)

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable latency_ms is not used.

Suggested change
latency_ms = int((time.monotonic() - t0) * 1000)

Copilot uses AI. Check for mistakes.
title="Skipped Paper on Machine Learning Inference",
keywords=["machine learning", "inference"],
)
neutral_pid = _insert_paper(

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable neutral_pid is not used.

Suggested change
neutral_pid = _insert_paper(
_insert_paper(

Copilot uses AI. Check for mistakes.
Comment thread tests/unit/test_export.py
Comment on lines +5 to +6
import pytest

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'pytest' is not used.

Suggested change
import pytest

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +12
Base,
PaperFeedbackModel,
PaperModel,
ResearchTrackModel,

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import of 'Base' is not used.
Import of 'ResearchTrackModel' is not used.

Suggested change
Base,
PaperFeedbackModel,
PaperModel,
ResearchTrackModel,
PaperFeedbackModel,
PaperModel,

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +66
except Exception:
pass

Copilot AI Feb 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
except Exception:
pass
except Exception as exc:
logger.warning(f"keychain delete failed for {endpoint_name}: {exc}")

Copilot uses AI. Check for mistakes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这是一个非常大的重构,改进了仪表盘和设置的用户界面,并使用系统钥匙串增加了安全的 API 密钥存储。这些更改结构良好,显著改善了应用程序的用户体验和安全状况。后端重构为使用 PaperSearchService 是一个很好的设计选择,它集中了搜索逻辑。新的论文导出功能也是一个很棒的补充。我发现了一个在引用密钥生成中的潜在错误和一个响应模型中的小设计问题。总的来说,工作非常出色。

@@ -66,10 +67,13 @@ class EndpointTestRequest(BaseModel):

class EndpointTestResponse(BaseModel):
ok: bool

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

EndpointTestResponse 模型同时包含了 ok: boolsuccess: bool 两个字段。这两个字段似乎是多余的,因为在成功的情况下它们都被设置为 True。为了简化数据模型并提高清晰度,我建议移除 ok 字段。您也需要在 test_model_endpoint 函数中移除对它的使用。

Comment on lines +1666 to +1674
def _dedup_citation_keys(keys: List[str]) -> List[str]:
"""Append a/b/c suffixes when keys collide."""
seen: Dict[str, int] = {}
result: List[str] = []
for k in keys:
count = seen.get(k, 0)
seen[k] = count + 1
result.append(k if count == 0 else f"{k}{chr(ord('a') + count)}")
return result

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

当前用于通过附加字符后缀(chr(ord('a') + count))来去重引用键的实现,在处理超过25个相同基本键的冲突时存在问题。如果 count 达到26或更高,它将开始附加非字母字符(例如 {, |),这可能导致无效的BibTeX键。虽然这是一个边缘情况,但更稳健的做法是处理它。例如,在'z'之后切换到数字后缀,或者使用类似Excel列的 a, b, ..., z, aa, ab, ... 风格的后缀。以下建议实现了后者,它能优雅地处理任意数量的冲突。此建议还将后缀序列更改为从 'a' 开始,这更为常规。

Suggested change
def _dedup_citation_keys(keys: List[str]) -> List[str]:
"""Append a/b/c suffixes when keys collide."""
seen: Dict[str, int] = {}
result: List[str] = []
for k in keys:
count = seen.get(k, 0)
seen[k] = count + 1
result.append(k if count == 0 else f"{k}{chr(ord('a') + count)}")
return result
def _dedup_citation_keys(keys: List[str]) -> List[str]:
"""Append a/b/c suffixes when keys collide."""
seen: Dict[str, int] = {}
result: List[str] = []
for k in keys:
count = seen.get(k, 0)
seen[k] = count + 1
if count == 0:
result.append(k)
continue
# Generate suffixes like a, b, ..., z, aa, ab, ...
suffix = ""
temp_count = count
while temp_count > 0:
temp_count, remainder = divmod(temp_count - 1, 26)
suffix = chr(ord('a') + remainder) + suffix
result.append(f"{k}{suffix}")
return result

@jerry609
jerry609 deleted the fix/encrypt-api-keys-at-rest branch March 4, 2026 08:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants