Skip to content

feat(dashboard): "New models" inbox across connected providers - #16864

Open
diegosouzapw wants to merge 10 commits into
release/v3.8.53from
feat/port-pr-3604-new-models-inbox
Open

diegosouzapw wants to merge 10 commits into
release/v3.8.53from
feat/port-pr-3604-new-models-inbox

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a persistent "New models" inbox: every successful model sync records the provider's listing in a new seen_models table, and models that appear in a later sync stay unread until acknowledged.
  • Exposes GET /api/models/new (unread models grouped by provider) and POST /api/models/new/acknowledge (selection or all), both management routes.
  • The Providers toolbar gets a "New models" button with an unread badge and a modal grouped by provider (Free tags, per-provider and mark-all-read actions).

Attribution

Thanks to @ErfanBagheri404 for the original implementation.

Changes

  • src/lib/db/migrations/206_seen_models.sql — new idempotent table seen_models(provider, model_id, is_free, first_seen_at, acknowledged_at) + index (next free number on the tip; check:migration-numbering OK).
  • src/lib/db/seenModels.ts — reconcileSeenModels, listUnseenModels, countUnseenModels, acknowledgeSeenModels. Seeding is per provider: the first reconcile of a provider records its whole listing as read, so an existing install, or a provider connected later, never floods the inbox. Rows are never deleted, so a model that disappears and comes back is not flagged again.
  • src/lib/newModelsInbox.ts — best-effort recorder used by the sync route (never throws, never fails a sync; free flag via isFreeModel() from src/shared/utils/freeModels.ts) and the grouped read model (provider names through getProviderById / getProviderDisplayName, compatible nodes resolved from provider_nodes).
  • src/app/api/providers/[id]/sync-models/route.ts — +3 lines: records the fetched listing on the regular discovery path and on the Volcano Ark console path, so the 6h auto-sync scheduler fills the inbox too.
  • src/app/api/models/new/route.ts, src/app/api/models/new/acknowledge/route.ts — requireManagementAuth, Zod .safeParse body ({ models: [{provider, modelId}] }, { all: true } or empty = all), errors through createErrorResponse (sanitized). /api/models/* already classifies as MANAGEMENT; no process spawn, so not local-only.
  • src/app/(dashboard)/dashboard/providers/components/NewModelsButton.tsx + 2-line mount in ProviderSummaryCard.tsx (keeps providers/page.tsx untouched).
  • src/i18n/messages/*.json — 10 new providers.newModels* keys, translated in all 66 locales (inserted in place, no key reordering).

Design note: the GET reads the table only instead of rebuilding the unified /v1/models catalog on every request, so it is cheap and needs no in-memory cache; the sync path is the single writer.

Test plan

  • node --import tsx/esm --test tests/unit/seen-models.test.ts — 13 tests: seed-on-first-reconcile, later model flagged, acknowledge + re-reconcile not re-flagged, disappear/reappear not re-flagged, per-provider seeding, blank/duplicate ids, acknowledge-all, raw-payload recorder, recorder never throws on DB failure, GET grouping, POST selection/all, POST 400 on invalid body, GET 500 error path leaks neither the DB path nor a stack frame. Failed before the port (modules missing).
  • node --import tsx/esm --test tests/unit/seen-models-sync-route.test.ts — real POST /api/providers/[id]/sync-models twice: first sync seeds (0 unread), second sync with one extra model leaves exactly that model unread. Verified it FAILS with the original route (ℹ pass 0 ℹ fail 1) and passes with the hook.
  • Related suites: seen-models, seen-models-sync-route, model-sync-route, model-sync-custom-preservation, model-sync-keeps-imported-media-models, siliconflow-model-sync, zai-web-model-sync-route, openference-sync-e2e-15762, sync-models-degraded-cached-catalog-9683, combo-stale-model-refs-13505, i18n-vi-completeness, i18n-placeholder-parity, i18n-key-completeness, i18n-locale-surfaces-parity → ℹ tests 139 ℹ pass 139 ℹ fail 0.
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files> → clean; npx prettier --check → clean.
  • npm run typecheck:core → 57 errors, all pre-existing on the base (none in the changed files).
  • npm run check:migration-numbering → OK (203 migrations); check:route-validation:t06 → PASS; check:openapi-coverage → PASS; check:openapi-routes → OK; check:route-guard-membership → OK; check:file-size → OK.
  • npm run i18n:check-keys → OK (66 locales, all 13765 keys); i18n:check-ratio → OK; check-new-key-coverage → PASS; i18n:check-ui-coverage → PASS.
  • check-pr-test-policy → PASS; check:test-masking → OK.

⚠️ base-red inherited: #15306 — unit/vitest/integration/package-artifact gates on release/v3.8.52

diegosouzapw and others added 2 commits October 16, 2026 14:12
Every successful model sync now records the provider's listing in a new
seen_models table (migration 206, src/lib/db/seenModels.ts). The first sync
of a provider seeds its baseline as already read, so existing installs and
freshly connected providers never flood the inbox; models that show up in
later syncs stay unread until acknowledged. Rows are never deleted, so a
model that drops out of a listing and comes back is not flagged again.

- GET /api/models/new returns the unread models grouped by provider
  (read from the table only, no catalog rebuild per request).
- POST /api/models/new/acknowledge marks a selection, or everything, read
  (Zod-validated body; errors sanitized via createErrorResponse).
- The Providers toolbar gets a "New models" button with an unread badge and
  a modal grouped by provider, with Free tags from the shared free-model
  knowledge and per-provider / mark-all-read actions.
- Recording is best-effort and never fails a sync; it runs for the regular
  discovery path and the Volcano Ark console path, so the 6h auto-sync
  scheduler fills the inbox too.
- New UI strings translated in all 66 locales.

Co-authored-by: ErfanBagheri404 <mrenmityerf@gmail.com>
Inspired-by: decolua/9router#3604
@diegosouzapw diegosouzapw added the deferred-v3.8.53 Feat PRs held for v3.8.53. Keep open. Do not merge into v3.8.52. label Oct 16, 2026
@diegosouzapw diegosouzapw changed the title feat(dashboard): "New models" inbox across connected providers [defer] feat(dashboard): "New models" inbox across connected providers Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

CI Coverage Report

  • Coverage job: skipped
  • PR test policy: success

Coverage artifact was not available for this run.

@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.52 to release/v3.8.53 October 19, 2026 13:24
@diegosouzapw

Copy link
Copy Markdown
Collaborator Author

Re-homed to release/v3.8.53: v3.8.52 entered its release freeze, so the branch now belongs to the release captain and development continues on the next cycle. Nothing is wrong with this PR — it just needed a live base. No action needed from you; CI will re-run against the new base.

@diegosouzapw diegosouzapw removed the deferred-v3.8.53 Feat PRs held for v3.8.53. Keep open. Do not merge into v3.8.52. label Oct 20, 2026
@diegosouzapw diegosouzapw changed the title [defer] feat(dashboard): "New models" inbox across connected providers feat(dashboard): "New models" inbox across connected providers Oct 20, 2026
diegosouzapw and others added 8 commits October 27, 2026 13:44
…pr-3604-new-models-inbox

# Conflicts:
#	src/app/(dashboard)/dashboard/providers/components/ProviderSummaryCard.tsx
The seen_models migration was renumbered 206 -> 215 on the merge with
release/v3.8.53 (206 is taken by 206_codex_wakeup_tasks), which makes the
shipped SQL file count 212. AGENTS.md and llm.txt carry the same count but
are protected agent-instruction surfaces and are left for the operator.
README Database row resolved to 214 migrations (213 on the release + this PR's seen_models). docs/i18n mirrors take the release side; they are refreshed after merge.

Co-authored-by: ErfanBagheri404 <mrenmityerf@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
… 215/216

release/v3.8.53 shipped 215_team_cost_centers.sql and 216_team_cost_shapes.sql, so this PR's 215_seen_models.sql collided. The migration body is unchanged (idempotent CREATE ... IF NOT EXISTS).

Co-authored-by: ErfanBagheri404 <mrenmityerf@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: ErfanBagheri404 <mrenmityerf@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
… 217

release/v3.8.53 shipped 217_quota_recovery_tickets.sql (#14982) after the previous renumber, so 217_seen_models.sql collided again. README Database row now reflects the 215 SQL migrations on the merged tree. Migration body unchanged.

Co-authored-by: ErfanBagheri404 <mrenmityerf@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
# Conflicts:
#	src/app/(dashboard)/dashboard/providers/components/ProviderSummaryCard.tsx
#	src/i18n/messages/am.json
#	src/i18n/messages/ar.json
#	src/i18n/messages/az.json
#	src/i18n/messages/bg.json
#	src/i18n/messages/bn.json
#	src/i18n/messages/bs.json
#	src/i18n/messages/cs.json
#	src/i18n/messages/da.json
#	src/i18n/messages/de.json
#	src/i18n/messages/el.json
#	src/i18n/messages/en.json
#	src/i18n/messages/es.json
#	src/i18n/messages/et.json
#	src/i18n/messages/fa.json
#	src/i18n/messages/fi.json
#	src/i18n/messages/fr.json
#	src/i18n/messages/ga.json
#	src/i18n/messages/gu.json
#	src/i18n/messages/ha.json
#	src/i18n/messages/he.json
#	src/i18n/messages/hi.json
#	src/i18n/messages/hr.json
#	src/i18n/messages/hu.json
#	src/i18n/messages/hy.json
#	src/i18n/messages/id.json
#	src/i18n/messages/ig.json
#	src/i18n/messages/it.json
#	src/i18n/messages/ja.json
#	src/i18n/messages/ka.json
#	src/i18n/messages/km.json
#	src/i18n/messages/kn.json
#	src/i18n/messages/ko.json
#	src/i18n/messages/lt.json
#	src/i18n/messages/lv.json
#	src/i18n/messages/ml.json
#	src/i18n/messages/mr.json
#	src/i18n/messages/ms.json
#	src/i18n/messages/mt.json
#	src/i18n/messages/my.json
#	src/i18n/messages/ne.json
#	src/i18n/messages/nl.json
#	src/i18n/messages/no.json
#	src/i18n/messages/or.json
#	src/i18n/messages/pa.json
#	src/i18n/messages/phi.json
#	src/i18n/messages/pl.json
#	src/i18n/messages/pt-BR.json
#	src/i18n/messages/pt.json
#	src/i18n/messages/ro.json
#	src/i18n/messages/ru.json
#	src/i18n/messages/si.json
#	src/i18n/messages/sk.json
#	src/i18n/messages/sl.json
#	src/i18n/messages/sr.json
#	src/i18n/messages/sv.json
#	src/i18n/messages/sw.json
#	src/i18n/messages/ta.json
#	src/i18n/messages/te.json
#	src/i18n/messages/th.json
#	src/i18n/messages/tr.json
#	src/i18n/messages/uk-UA.json
#	src/i18n/messages/ur.json
#	src/i18n/messages/uz.json
#	src/i18n/messages/vi.json
#	src/i18n/messages/yo.json
#	src/i18n/messages/zh-CN.json
#	src/i18n/messages/zh-TW.json
…ase 217)

Co-authored-by: ErfanBagheri404 <mrenmityerf@gmail.com>
@diegosouzapw diegosouzapw added the deferred-v3.8.54 Feat PRs held for v3.8.54. Keep open. Do not merge into v3.8.53. label Nov 2, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deferred-v3.8.54 Feat PRs held for v3.8.54. Keep open. Do not merge into v3.8.53.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant