Add self-service password change that re-hashes the credential and bumps a
per-user session version so all existing access/refresh tokens are rejected.
Session version is tracked in Redis with an in-memory fallback and enforced in
middleware and refresh.
Scope prompt rules and rule groups to a brand: new brand_id column (migrated to
a "未归属" fallback brand for existing rows), brand-filtered queries/indexes, and
brand-aware admin-web tabs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the scheduler-polled KnowledgeDeletedCleanupWorker with an
event-driven KnowledgeDeletedCleanupEventWorker in tenant-api; cleanup
events are now enqueued transactionally at delete time instead of swept
periodically. Also fixes statement_timeout parameterization in
MonitoringRetentionWorker and propagates errors from cleanupDeletedKnowledgeItem.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Introduces Aliyun OSS as an alternative to MinIO; deploy scripts and CI
workflows now read object_storage.provider from config and conditionally
include or skip MinIO resources in both Docker Compose and k3s paths.
Also adds ops scheduler domain and its migration tables.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Remove industry, target_audience, content_goal, tone, and length_goal from the imitation form; brand_name is now mandatory with autocomplete from brand library, keywords are pre-populated from the selected brand's search keywords, and output length is hardcoded to ~2000 characters in the prompt.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
When a desktop monitoring task fails with a non-retryable authorization failure
(login expired, challenge required, risk control), all pending same-client/same-platform
tasks for the same business day are immediately bulk-failed as non-retryable, avoiding
wasted execution attempts. Adds preflight authorization checks before task execution,
enriches failure payloads with disposition metadata (code, category, retryable flags),
and triggers account health reports on auth state degradation.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Port admin-web's chunk-reload guard to ops-web (vite:preloadError +
router.onError) so a stale tab landing on a lazy-loaded route triggers
a single reload instead of a hard 404. Also force index.html through
revalidation on both apps so the reload actually fetches a fresh entry
referencing the new chunk hashes; hashed asset URLs keep their long
immutable cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multi-replica deployments need every Pod/container to share one RSA private key, otherwise public keys handed to the browser may not match the key that decrypts the resulting ciphertext. Introduce a `*.local.yaml` override layer that ops-config and tenant-config both load on top of the base config, ship example overrides plus compose/k3s wiring, and tolerate PEM bodies that arrive folded or escaped from Secrets.
Unknown URLs previously rendered a blank page. Add a catch-all route
under AppShell that shows the requested path plus back / workspace
actions, with zh-CN and en-US copy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Shrink desktop presence TTL to 30s and heartbeat to 15s so unexpected
client exits surface within one TTL even if the explicit offline call
is missed; keep migration default aligned.
- Trust a known presence miss in resolveDesktopClientOnline instead of
falling back to the recent-heartbeat heuristic, so a still-cached
LastSeenAt cannot mask an offline client.
- Release the runtime session before clearing renderer desktop sessions
on shutdown to avoid leaving stale leases behind.
- Auto-refresh the MediaView account list every 5s and revamp card
layout (inline platform badge, status tags, UID row, meta box).
- Let the brand-question AI wizard accept multiple seed topics via a
tag-style input; candidates from each topic are merged and deduped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- add /questions/combination-fill endpoint with AI-driven, IP-region-aware matrix fill
- extract ip2region resolver from ops/app into shared/ipregion for cross-service use
- thread question_id filter through dashboard composite, citation summary, and collect-now
- switch template wizard from keyword inputs to brand-question selection (primary + supplemental)
- pass brand_question and supplemental_questions through assist/title/outline prompts
- add AiWaitingModal + 45s client timeout and request_timeout error mapping for long AI flows
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Implemented QuestionExpansionService for generating and materializing questions based on combinations and AI distillation.
- Added question metadata classification logic to infer intent and layer of questions.
- Created API handlers for question expansion operations including combination preview, AI distillation, metadata classification, and materialization.
- Introduced database migrations to support new question-related fields and constraints in brand_questions and brand_keywords tables.
- Added caching mechanism for AI distillation results to improve performance.
- Defined JSON schemas for question distillation responses to ensure data integrity.
Add auth.login_guard.enabled config (default on) so the tenant login
brute-force / rate-limit guard can be disabled per environment.
Surfaces an AUTH_LOGIN_GUARD_ENABLED env override and a startup warning
when disabled. Wired through Diff() so hot reload picks up changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Polling analyze/title/outline task results every 1.2s was over-eager
for AI tasks that typically run 15-30s. Bump interval to 3s and lower
max attempts to 40, extending total timeout from 72s to 120s while
roughly halving request volume.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When ops staff reset a tenant's current-plan usage, the existing
'refunded' terminal status conflated user-initiated refunds with
ops-driven resets, and tenants still saw the old balance in the top-nav
chip until the quota-summary cache TTL expired.
- Introduce a dedicated 'reset' status for quota_reservations, separate
from 'refunded'. Both the initial and an incremental migration widen
the CHECK constraint to allow it; the down migration folds 'reset'
rows back into 'refunded' before tightening the constraint.
- resetQuotaReservations / resetAIPointReservations now write 'reset'
without touching refunded_amount, and stop cascading into
ai_point_usage_logs (carried no audit value for ops resets).
- AdminUserService gains an optional cache dependency and calls
invalidateQuotaSummaryCache(tenant_id) immediately after a successful
reset so the tenant's top-nav chip refreshes on the next request
without waiting on TTL.
- Wire the existing appCache into AdminUserService at boot.
- Drop the now-unused ai_point_usage_logs field from the reset response
and from the ops-web AdminUsersView type.
- Add a unit test covering the cache-key invalidation contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Top-nav quota chip (会员到期 / 生成文章 / AI 点数) only refreshes when its
query is invalidated. Several mutations and stream-based flows that debit
points or article quota on the server were not invalidating it, so users
had to refresh the page to see updated balances.
- TemplateWizardView: invalidate `['workspace']` inside `endAssistTask()`,
covering analyze / title / outline assist tasks in one place.
- FreeCreateView: invalidate after `articlesApi.create` success.
- GenerateTaskDrawer: add `['workspace']` to the instant-generate
`Promise.all` alongside the existing `articles` / `instantTasks` keys.
- KolPromptEditor: invalidate in the `finally` of both `runAiAssistStream`
(generate) and `generateSelectionAiPreview` (optimize) so abort / error
paths also refresh.
- ArticleEditorCanvas: bring in `useQueryClient` and invalidate in the
selection-optimize stream `finally`.
Together with the existing invalidations in TemplateWizard.generate,
ImitationGenerate, KolGenerate, and article-regenerate, every quota-
debiting call site now feeds the cache refresh.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Introduce `navigateRemoteURL` / `showLocalPage` / `showErrorPage` helpers
that centralize remote navigation, watchdog wiring, and error fallback,
replacing the ad-hoc logic inside `loadWindowURLSafely`.
- Show a localized loading page (`loadingPageDataURL`) while a remote URL
is fetching, so the window no longer sits on blank white during slow
navigations.
- Add a 25s remote-load timeout watchdog that switches the window to the
error page when the target never finishes loading, plus an active load
token so stale watchdogs from earlier navigations cannot fire on the
current attempt.
- Track `localPageKind` and `activeTargetURL` per window so blank-page
retries, did-fail-load handlers, and ready-for-detection checks behave
correctly when the window is currently showing a local loading/error
page.
- Escalate the blank-page watchdog: after the retry attempt, if the page
is still blank, surface the error page instead of looping silent
reloads.
- Restyle the error page (drop gradients, use a neutral light/dark token
palette, single primary retry button) and add a dedicated loading page
template with the same look.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add `ai_points_insufficient` / `ai_points_unavailable` Chinese mappings in
errors.ts so the toast copy is localized.
- Add `showAiPointsInsufficientModal()` helper that renders a centered
`Modal.confirm` with localized title/body and a "查看点数明细" action that
routes to `/account/ai-points`. The helper is debounced so concurrent
failed requests do not stack multiple modals.
- Trigger the modal globally from the admin-web response interceptor when
the backend returns `ai_points_insufficient`, so every AI-generating
endpoint surfaces the same prominent prompt without per-callsite changes.
- Drop the English `detail` for ai_points_insufficient in `formatError` so
the secondary toast stays pure Chinese.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Carry `from=workspace` query from the workbench into the wizard so cancel
and post-generate redirects return to the workbench instead of always
landing on /articles/templates.
- AppShell now resolves an effective navKey from `route.query.from`, so the
sidebar highlight and breadcrumb track the entry source while inside the
wizard.
- Redesign the brand-info AI analyze CTA into a standalone banner with
template-aware copy (default / product_review / research_report) and add
the matching i18n keys.
- Drop the unused 批量生成文章 button and its styling from the templates
page.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LinkReader 500 (or other trigger_status) used to be gated by an extra
allowed_domains whitelist on the tenant API side, so URLs like
m.baidu.com/bh/m/detail/... fell straight through to the user with a raw
"webpage parser failed" message. Drop the whitelist gate — enabled +
trigger_status is enough — and surface a friendly Chinese prompt when
both LinkReader and the lightpanda fallback give up. Full error chain
is kept in the logger for debugging.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Listen for vite:preloadError and router onError, force a one-shot reload
(10s cooldown via sessionStorage) when a hashed lazy-loaded module 404s
because a new deploy replaced it. Eliminates the user-visible "Failed to
fetch dynamically imported module" error on login and route navigation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add browser-fetch to backend-ci image list, NAS compose/K3s deploy options
and rollout loops, and the offline package bundle so the new service ships
through the same pipelines as the rest of the backend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Turn the lightpanda-backed fetch service on, bump worker concurrency to
3, and add baidu.com plus weixin.sogou.com to the allowed domains so the
knowledge URL fallback can resolve those sources without a config edit.