1970 Commits

Author SHA1 Message Date
yuheng.huang
39c9985c3b refactor(logger): support LOG_FORMAT template and harden level coloring
- Add CustomFormatter.Template driven by LOG_FORMAT env var with
  placeholders %d/%level/%thread/%logger/%traceId/%msg; default
  format unchanged for backward compatibility.
- Replace chained ReplaceAll with strings.NewReplacer for single-pass
  substitution, avoiding accidental re-substitution when a field value
  contains a literal placeholder string.
- Inject ANSI color at the %level substitution step; removes the old
  whole-line ReplaceAll(line, "INFO", ...) which would mis-color
  literal level tokens appearing inside messages.
- Cache threadNeeded on the formatter so runtime.Stack only runs when
  the template references %thread.
2026-05-22 20:31:54 +08:00
wizardchen
bccc27b162 fix(frontend): keep X-Tenant-ID override when switching back to home
After re-login, the JWT is minted with the user's last-active tenant
(see userService.resolveLoginTenantID). Both TenantSelector and
UserMenu used to clear weknora_selected_tenant_id when "switching to
home", which made request.ts stop attaching X-Tenant-ID. Without the
header, the auth middleware fell back to the JWT-encoded tenant id —
i.e. the peer tenant the user just tried to leave — so the switch was
silently a no-op until the user manually wiped localStorage and
re-logged in.

Always write the active tenant id into selectedTenantId so the
"always attach X-Tenant-ID" invariant request.ts relies on stays
true. The server-side last_active_tenant_id preference is still
cleared when switching to home, so a clean re-login still lands the
user on home.

Also fix TenantSelector.defaultTenantId to read user.tenant_id (the
immutable home id) instead of authStore.tenant.id (the active tenant,
which is overwritten by /auth/me). This matches the contract spelled
out in useHomeTenant() and prevents switchingToHome from misfiring
when the active tenant differs from home.
2026-05-22 18:20:23 +08:00
ochan.kwon
8b7d00b260 feat: expose KB ↔ vector store binding in list, editor, and detail UI
Backend
-------
ListKnowledgeBases now enriches each row with the resolved vector_store
metadata (name / source / engine_type / status) via a new
buildKBListResponse helper. Store views are batch-resolved once per
request through BatchResolveStoreView so an N-KB list costs one
vector-store service call rather than N — closing the N+1 limitation
flagged in #1372's known-limitations section. Cross-tenant shared KBs
continue to render via SharedStoreDisplay so the owning tenant's store
inventory cannot be correlated across rows; the underlying vector_store_id
UUID is stripped from those responses.

Resolver failures degrade gracefully: bound KBs render as unavailable
instead of breaking the list. Test coverage pins the env / bound /
shared distinction, the batch-call-count invariant, and the
graceful-failure path.

Frontend
--------
KB editor modal gains a new "Vector Store" section. Create mode shows a
dropdown that combines the system default (env store) and the tenant's
configured user stores, fetched once at mount via the existing
listVectorStores API. Edit mode shows the bound store read-only via a
new VectorStoreBadge component with an explicit immutability hint —
matching the backend's `<-:create` GORM tag and the service-layer
UpdateKnowledgeBaseRequest DTO that already omit the field.

KB list cards surface a small engine-type badge for own-tenant bound
KBs, and a warning badge when the bound store is unavailable. Env-bound
and shared KBs render no badge (visual noise control). KB detail header
shows the bound store via the same VectorStoreBadge component; shared
KBs fall through to the badge's internal "shared" branch with no name /
engine / id rendered.

The KB editor's create-time error handler translates the typed
ErrVectorStoreBindingInvalid (2200) and ErrVectorStoreUnavailable
(2201) into localized messages and jumps the user back to the
VectorStore section so they can pick a different store or fall back to
the system default.

The KB row type gains five optional fields (vector_store_id / name /
engine_type / source / status). i18n: 18 new keys added to en-US,
ko-KR, zh-CN; ru-RU receives English placeholders pending translation
(consistent with prior PRs in this locale).

Part of #993 (Phase 2: Per-KB VectorStore Binding).
Phase 2 roadmap item: PR 5 (KB binding UI + list-response enrichment).
Depends on #994, #1310, #1372, #1386 (all backend in the Phase 2 series).
2026-05-22 17:40:10 +08:00
Phuong Tran
0f57bf3ff8 fix (chat history): #1431 user's multilines query formatting was lost 2026-05-22 17:26:05 +08:00
wizardchen
c0e4a1d2f1 fix(summary): preserve image caption/OCR text in document summaries
Documents whose only payload is an embedded image (e.g. a docx with a
single picture) intermittently produced the refusal line "No textual
content was extractable from this document." even though the vision
model had successfully extracted a caption.

Three coordinated fixes:

- Clarify the summary prompt that text inside `<image_caption>` and
  `<image_ocr>` is first-class extracted content, not an image
  reference, so the model only triggers the empty-content branch when
  the body is genuinely textless.
- For image-dominated documents (real text < 200 runes after stripping
  image markup) include OCR alongside captions so screenshots and
  scanned figures contribute their actual content; text-heavy
  documents continue to use caption-only enrichment to avoid OCR
  noise from incidental figures.
- Add `EnrichContentCaptionAndOCR` which embeds caption + OCR text
  inline next to the original Markdown image link, deliberately
  omitting the `<image url=...>` and `<image_original>` wrapper
  blocks. Those wrappers carry only opaque export hashes that consume
  tokens and have been observed to retrigger the LLM's "image
  reference with no extracted text" heuristic.
2026-05-22 17:25:39 +08:00
wizardchen
72e52f7258 fix(frontend): improve tenant settings and member list UX
Keep the member search input visible during empty/loading states, avoid layout jitter on search, simplify role select display, widen the role column, and use icon-only edit buttons for tenant name/description.
2026-05-21 23:28:48 +08:00
ChenRussell
d3832edea4 fix(repository): qualify tenant_id with table name to resolve ambiguous column reference 2026-05-21 21:22:41 +08:00
ChenRussell
09f0f5b8a9 fix(swagger): Fix some Swagger API endpoints returning 404 errors and regenerate the Swagger documentation 2026-05-21 21:13:40 +08:00
MarionMa
0a9a920460 perf(repository): exclude embedding field from Elasticsearch search response to reduce size 2026-05-21 21:11:19 +08:00
wizardchen
b0094ff479 docs(readme): flatten Latest Updates into single-line per-version list
Condense v0.6.0 highlights into one line and remove the collapsible
<details> wrapper so the full release history is visible by default
across all four READMEs (EN/CN/JA/KO).
v0.6.0
2026-05-21 17:17:39 +08:00
wizardchen
cdfc9ce23a chore(release): v0.6.0
Tenant RBAC headline release: 4-tier role matrix (Owner/Admin/
Contributor/Viewer), per-KB resource ownership, per-tenant audit
log, tenant member management, self-service workspaces.

Also: CLI v0.3/v0.4 GA, KB retrieval fan-out across vector stores,
AES-256-GCM credential at-rest, docreader gRPC TLS+Token, Zhipu
embedding, Huawei OBS, vLLM URL for MinerU, Apache Doris compat
modes, server-side user preferences, Go 1.26.0.

See CHANGELOG.md for the full list.

docs(rbac): wire RBAC screenshots into READMEs and RBAC guide

- README.md / README_CN.md / README_JA.md / README_KO.md: replace the
  single member-management thumbnail under the v0.6.0 RBAC highlight
  with a 2×2 showcase (member management, workspace switcher,
  self-service workspace creation, pending invitations).
- docs/RBAC说明.md: add the member-management screenshot to the
  existing 前端实际界面 showcase so the guide is self-contained
  and no longer cross-references README for it.

feat(rbac-ui): link tenant member page to RBAC guide

Add an inline doc-link in the Tenant Members settings page that
opens docs/RBAC说明.md on GitHub in a new tab, complementing the
existing in-app role-matrix popover. New i18n key
tenantMember.learnRbacGuide covered for zh-CN / en-US / ko-KR /
ru-RU.
2026-05-21 16:56:19 +08:00
Thinh Tran
e1ce4270d0 fix(repository): correct query syntax to get correctly built-in models 2026-05-21 16:11:22 +08:00
Thinh Tran
3db5b0d126 fix(chat): adjust user message container to support pre-wrapped text 2026-05-21 16:11:22 +08:00
孙常熙
0dd6126826 fix(frontend): guard knowledge list against stale updates 2026-05-21 16:10:32 +08:00
wizardchen
7ebb29cd3e docs: add Chinese RBAC guide and link with shared space docs
Replace the English docs/rbac.md with a comprehensive Chinese
docs/RBAC说明.md and a wiki-style summary under docs/wiki/安全认证/.
Explain how tenant RBAC relates to the shared space feature (they
are orthogonal: tenant RBAC is the vertical defense, shared space
is the horizontal collaboration channel) and cross-link the two
docs in both the flat and wiki trees. Update inbound references in
.env.example, docker-compose.yml, and the auth legacy env test to
point at the new file name.
2026-05-21 12:28:31 +08:00
wizardchen
7aca1017db fix(docparser): address review feedback on PR #1404
Three follow-up fixes on top of the MinerU markdown preservation work:

- Stop applying normalizeMinerUMarkdown inside ResolveAndStore. The
  helper is already called by MinerUReader.Read, and ResolveAndStore is
  shared by every parser (docreader, session attachments, ...). Running
  the heading/image unescape regexes globally would silently rewrite
  content (including inside fenced code blocks) for non-MinerU sources.

- Recognize MinerU image references whose path contains spaces, e.g.
  "images/第 1 页.jpg". The previous regex used in
  extractImageRefsFromContent disallowed whitespace in the URL group,
  so such images were never matched and never persisted. Use a
  whitespace-tolerant pattern aligned with ResolveAndStore's own
  imgPattern.

- Deduplicate uploads when the same MinerU image is referenced under
  multiple path forms (e.g. "images/foo.png" vs "./images/foo.png").
  saveReferencedImage now caches by ref.Filename in addition to the
  raw ref path, so the second variant reuses the previously stored
  ServingURL instead of writing the same bytes to object storage
  again.

Tests added:
- TestProcessImagesMatchesPathsWithSpaces
- TestResolveAndStoreDedupsSameImageRefVariants
2026-05-21 11:42:56 +08:00
MidnightSun
6210f44fb6 fix(docparser): preserve MinerU markdown and persist relative images
MinerU already returns markdown with embedded HTML blocks, but the current\nreader runs the whole document back through html-to-markdown. That\nsecond conversion escapes valid headings and image syntax, so chunk\nprofiling sees plain text instead of markdown structure and relative\nimage references stop matching the storage pipeline.\n\nKeep MinerU output in its original markdown form and only apply narrow\ncompatibility normalization for the specific over-escaped patterns we\nactually need to recover. The converter now matches image refs by the\npaths that are really present in markdown or embedded HTML instead of\nassuming a single images/<name> form.\n\nExtend ImageResolver so relative HTML <img src=...> references share the\nsame storage rewrite path as markdown images, deduplicate repeated saves,\nand keep the frontend sanitizer compatible with MinerU's details/summary\nblocks. Add focused docparser tests that cover escaped markdown repair,\nvariant image path matching, and relative HTML image persistence.
2026-05-21 11:39:57 +08:00
wizardchen
9f6148784a fix(frontend): improve offline and legacy browser support 2026-05-21 11:19:44 +08:00
wizardchen
8f4626158d fix(chat): improve history rendering stability 2026-05-21 11:19:44 +08:00
wolfkill
5bdaf58d45 fix(session): scope wiki fixer to shared KB tenant 2026-05-21 11:19:19 +08:00
qingfhuang
6a6513caba fix(client): align UpdateAgent request types with internal API
Sync AgentConfig, UpdateAgentRequest, and CreateAgentRequest JSON fields
with internal/types.CustomAgentConfig and handler request structs so SDK
clients can send and receive the full agent configuration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 22:41:21 +08:00
wolfkill
bb95488ac8 fix(knowledge): complete indexed documents immediately 2026-05-20 22:40:48 +08:00
ochan.kwon
fd3d9f547c feat(search): fan-out KB retrieval across bound vector stores
Multi-KB hybrid search now groups KBs by their bound VectorStore (partition
key (storeID, owner_tenant_id)), retrieves in parallel via errgroup with a
SetLimit(4) cap and a per-group timeout (MULTI_STORE_RETRIEVE_TIMEOUT_SEC,
default 30s), and merges results. When the collected results span more than
one engine type, an EngineAwareNormalizer rescales vector scores to [0, 1];
keyword (BM25) scores pass through to the existing RRF fusion. Single-group
calls take the fast path with zero fan-out overhead, preserving today's
behavior for deployments where every KB has vector_store_id = NULL.

Embedding-model consistency is now enforced explicitly via
ResolveEmbeddingModelKeys. Multi-KB searches across KBs whose resolved
model identities differ return BadRequest instead of silently producing
incomparable scores. Cross-tenant Organization-shared KBs are preserved by
partitioning on KB.TenantID so the factory's ownership lookup runs against
the source tenant. Foreign-tenant KB UUIDs injected via the request body
are rejected via kbShareService.HasTenantKBPermission (Plan 3 of #1303,
3-D capped) before any retrieval; rejected scopes surface as 404 to avoid
leaking foreign KB existence.

Service-layer typed AppErrors (ErrVectorStoreBindingInvalid 2200 /
ErrVectorStoreUnavailable 2201) are mapped from PR2 sentinel hierarchy and
preserved end-to-end: the iterative FAQ path returns them rather than
swallowing, and the HybridSearch handler routes typed AppErrors to the
client unchanged instead of downgrading to 500.

Part of #993 (Phase 2: Per-KB VectorStore Binding).
Phase 2 roadmap item: PR 4 (Multi-store fan-out search).
Depends on #994, #1310, #1372.
2026-05-20 22:25:39 +08:00
wizardchen
07afec1499 fix(chat): stabilize history pagination and keep message order 2026-05-20 21:03:39 +08:00
wizardchen
3475af1707 feat(frontend): configure API proxy target for development environment
Updated the Vite configuration to allow dynamic setting of the API proxy target based on environment variables. The default target is now configurable via VITE_DEV_PROXY_TARGET or FRONTEND_BACKEND_URL, enhancing flexibility for different development setups. Additionally, the development script logs the current API proxy target for better visibility during startup.
2026-05-20 21:00:16 +08:00
wizardchen
f3c7281f47 refactor(agent): simplify grep_chunks tool to a single regex query
The grep_chunks tool previously accepted an array of regex queries (1-5)
and an optional knowledge_base_ids filter and limit. In practice the LLM
either fired multiple near-duplicate calls or split synonyms across
entries instead of using POSIX alternation, and KB scoping plus result
limit are server-side concerns the model should not control.

Reshape the contract to match `grep -E -i` semantics:

- Schema accepts a single required `query` string. Combine concepts with
  `|` alternation in one regex instead of multiple calls.
- Drop `knowledge_base_ids` and `limit` from the schema; the tool now
  always searches the full agent scope and uses a fixed internal cap.
- Legacy `pattern`, `queries`, `patterns`, `max_results` keys are still
  accepted and joined into a single alternation regex so older callers
  and in-flight model outputs keep working.
- Update the agent system prompt template to document the new single
  `query` field.
- Frontend tool title now reads `query`/`queries`/`pattern`/`patterns`
  in that order so the search text is shown again under the new schema.
- Add a dedicated `grepSearch` / `grepSearchFailed` tool status (zh-CN,
  en-US, ko-KR, ru-RU) and rename the zh-CN tool label to "搜索关键词"
  so the UI no longer prefixes the call with a generic "调用 ..." label.
2026-05-20 19:34:27 +08:00
wizardchen
31f560ecf1 fix(frontend): mirror cross-tenant superuser Admin role in UI gates
When a CanAccessAllTenants user switches into a tenant without a
tenant_members row, the backend grants temporary Admin via
resolveTenantRole step2 but currentTenantRole stayed empty, hiding
all mutation controls. Fall back to admin for UI gating only; Owner
surfaces remain hidden to match the server cap.
2026-05-20 16:55:39 +08:00
wizardchen
5fcdc30914 chore: update Go version to 1.26.0 in go.mod 2026-05-20 16:55:18 +08:00
knight
513e589494 feat: add vLLM server URL configuration for MinerU
Add support for configuring vLLM server URL when using vlm-http-client or hybrid-http-client backend in MinerU.

Changes:
- internal/types/tenant.go: Add MinerUVLMServerURL field to ParserEngineConfig
- internal/infrastructure/docparser/mineru_converter.go: Pass server_url to MinerU API when backend is vlm-http-client or hybrid-http-client
- frontend/src/api/system/index.ts: Add TypeScript type definition
- frontend/src/views/settings/ParserEngineSettings.vue: Add vLLM server URL input field
- frontend/src/i18n/locales/*.ts: Add translations (zh-CN, en-US, ko-KR)
2026-05-20 16:33:12 +08:00
Miles Lai
e62c0563aa doris: add configurable compatibility modes and guard mode switches
Problem:
The hard-coded Doris vector function implementation (cosine_distance_approximate with
UNIQUE KEY ANN tables) fails on SelectDB 4.0.2-rc01 and other Doris builds lacking that
specific function support. Users had no way to adapt without code changes.

Root cause:
There is assumption all Doris deployments support the same vector function API, but different
builds (Doris OSS, SelectDB, Doris Cloud) ship with different function variants and table
key constraints. No capability detection or user configuration existed.

Solution:
Implement DORIS_COMPAT_MODE environment variable with three modes:

  * auto (default/recommended): probe Doris server on first use to detect available vector
    functions; prefer inner_product_duplicate (modern Doris 4.0+), fall back to legacy
    (older builds lacking inner_product_approximate)

  * legacy: hard-set to cosine_distance_approximate + UNIQUE KEY (for older Doris/SelectDB
    builds without inner_product_approximate support)

  * inner_product_duplicate: hard-set to inner_product_approximate + DUPLICATE KEY
    (for modern Doris 4.0+ and current SelectDB with normalized embeddings)

Implementation details:
- add compat.go with one-time mode resolution (sync.Once) and capability probing
- inspect existing weknora_embeddings_* table DDL via SHOW CREATE TABLE to detect and
  enforce schema compatibility; prevents silent mismatches
- fail fast with clear error message when configured mode does not match existing tables,
  with explicit remediation steps (recreate tables or change env var)
- branch all query paths (inner_product_approximate vs cosine_distance_approximate),
  DDL generation (DUPLICATE KEY vs UNIQUE KEY), write paths (embed normalization),
  and chunk updates (Stream Load vs read-modify-write) by resolved compat mode
- add comprehensive repository tests for mode selection, auto-detection, and mismatch
  scenarios; all tests pass
- expose DORIS_COMPAT_MODE in docker-compose.yml with auto as default
- document in .env.example with clear mode decision guidance
- log all mode decisions (requested, detected, probed, final) at INFO/WARN level

Key guarantee:
⚠️ DORIS_COMPAT_MODE is NOT interchangeable after embedding tables are created.
App will reject mode switches that conflict with existing table layout, preventing
silent data mismatches and query failures.
2026-05-19 17:15:59 +08:00
wolfkill
eb52caf033 fix(chunker): keep top-level heading chunks separate 2026-05-19 17:08:56 +08:00
wizardchen
1cb522e621 fix(i18n): escape '@' in invite email placeholder
vue-i18n treats a bare '@' as the start of a linked-message reference,
so the placeholder "invitee@example.com" failed to compile with
"Invalid linked format". The whole email t-form-item then failed to
render, leaving the invite-member popup with only the role row.

Escape the literal '@' as {'@'} across all four locales so vue-i18n
emits the original text and the email input renders again.
2026-05-18 22:56:11 +08:00
wizardchen
ee74dcb545 Revert "style(tenant-members): unify role identity with a colored chip across surfaces"
This reverts commit a6949155c9.
2026-05-18 22:31:20 +08:00
wizardchen
7bb38f81b6 feat(session): persist last request state for UI restoration
Sessions now record the input-bar state used for the most recent QA
request (agent, model, KB scope, web search). The chat UI hydrates
those settings on session reopen so users see the same configuration
they used last time, instead of the global default.

The state is stored in the existing sessions.agent_config JSONB column
to avoid a new migration. Frontend snapshots the user's global defaults
on session enter and restores them on session leave, so opening an old
session does not pollute new-chat defaults.
2026-05-18 22:28:00 +08:00
wizardchen
8dd9b67df9 fix(menu): stop truncating session titles when extra space is available
The session list items in the sidebar forced the title's max-width to
155px on hover and on the active item, while .menu-more-wrap was pushed
to the right edge with margin-left:auto. This left a large gap between
the truncated title and the kebab button on rows whose title actually
fit within the row.

Switch the title to a flex layout (flex: 1; min-width: 0) so it grows
to fill the available space and only shows an ellipsis when the text
genuinely overflows. The kebab wrapper keeps flex-shrink: 0 so its
opacity-controlled visibility no longer needs to reshape the layout.
2026-05-18 22:25:11 +08:00
wizardchen
6f00056572 fix(menu): avoid refreshing session list when opening an existing chat
The route watcher in the sidebar treated any navigation away from the
creatChat pages as "a new session was created" and called
getMessageList(), which clears menuArr and re-fetches page 1. As a
result, clicking an existing session from /platform/creatChat caused
the whole session list to flicker.

Only refresh when the target chat id is not already in the list, which
is the real signal that a brand-new session was just created.
2026-05-18 21:55:47 +08:00
wizardchen
4845955e9a style(menu): tighten session list density in sidebar
Reduce vertical spacing in the chat session list so more sessions fit
on screen without feeling cramped: row height 38→34px, item height
32→30px, and slimmer timeline group headers.
2026-05-18 21:47:43 +08:00
yuheng.huang
28e595734f fix(moonshot): pin temperature=1 for models that reject other values moonshot-v1-* and kimi-k2.5/k2.6 reject any temperature ≠ 1 with HTTP 400. Detect these models in BuildChatCompletionRequest and force Temperature=1 while leaving kimi-k2/k2-turbo/k2-thinking unaffected. 2026-05-18 21:28:40 +08:00
yuheng.huang
00d39e006d fix(docparser): unescape MinerU markdown image syntax html-to-markdown over-escapes to on MinerU'smixed-Markdown+HTML output, breaking downstream image extraction. Restore the canonical image syntax after conversion so images are persisted. 2026-05-18 21:27:53 +08:00
wizardchen
7444c2190d chore(rbac): update default behavior for tenant RBAC configuration
Refactor the tenant RBAC configuration to change the default value from false to true, enabling role enforcement by default. This change allows operators to opt into a logging-only rollout window by explicitly setting the configuration to false.

Updates include:
- Modifications to .env.example and docker-compose.yml to reflect the new default.
- Adjustments in rbac.md documentation to clarify the new default behavior and the opt-in process.
- Code changes across various files to utilize the new pointer-based configuration for EnableRBAC, ensuring nil safety and clearer intent.

No functional changes were introduced; the adjustments primarily enhance clarity and maintainability of the RBAC feature.
2026-05-18 21:24:28 +08:00
wizardchen
633106c5ef chore(env): expose tenant RBAC + per-user tenant cap as env knobs
Surface two existing config.go env overrides to the canonical
deployment artifacts so operators can flip them without reading the
Go source:

* WEKNORA_TENANT_ENABLE_RBAC — observe / enforce switch for
  tenant-level role enforcement (PR 1303). Default false keeps the
  current behaviour; flip to true once role assignments have been
  audited per docs/rbac.md.
* WEKNORA_TENANT_MAX_OWNED_PER_USER — cap on tenants a single
  non-superuser can self-create. Uses the existing <0 / 0 / >0
  sentinel semantics documented on TenantConfig.MaxOwnedPerUser.

docker-compose.yml passes both through to the app container, and
.env.example gains a "Tenant / RBAC" section with the default
values and the same sentinel rules inline so the example is the
sole reference operators need.

No functional change — both env vars were already honoured by
config.go.applyAuthAndTenantDefaults.
2026-05-18 21:24:28 +08:00
wizardchen
49f3ad185a fix(auth): make DISABLE_REGISTRATION drive registration_mode too
DISABLE_REGISTRATION=true used to block /auth/register at the handler
layer only, leaving /auth/config still reporting self_serve. The
frontend therefore kept showing the Register entry even when the env
var was set — clicking it just hit the 403. Two gates, out of sync.

Wire DISABLE_REGISTRATION=true through applyAuthAndTenantDefaults so
it coerces auth.registration_mode to invite_only (env wins over YAML,
matching docs/rbac.md). The handler-side os.Getenv check is now
redundant with IsInviteOnly() and is removed, leaving a single
enforcement path.

Add config tests pinning down the env/YAML matrix, including the
explicit-self_serve override case that would otherwise be the easy
regression to ship.
2026-05-18 21:15:01 +08:00
wizardchen
2ab54f1542 chore(auth): drop redundant WEKNORA_AUTH_REGISTRATION_MODE env override
DISABLE_REGISTRATION=true (handler layer) and
WEKNORA_AUTH_REGISTRATION_MODE=invite_only (config layer) were two
env-level ways of saying the same thing: block /auth/register.
Keeping both invites confusion about which wins and risks operators
setting one while expecting the other.

Remove the WEKNORA_AUTH_REGISTRATION_MODE env override so the env
layer exposes a single knob (DISABLE_REGISTRATION). The
auth.registration_mode config field stays — operators who want the
richer behaviour (frontend hides the registration entry via
/auth/config in addition to the server-side 403) flip it in
config.yaml.

No behaviour change for deployments that did not set the env var.
Update docs/rbac.md to document the deliberate split.
2026-05-18 21:15:01 +08:00
wizardchen
4344765cf9 feat(i18n): enhance notification templates with raw message handling
Updated the notification system to utilize the `tm` function for template resolution, ensuring that placeholders like `{name}` and `{role}` remain intact for proper rendering. This change improves the display of workspace and role information in login and tenant switch notifications, allowing for better localization and visual consistency across the application. Adjustments were made in `App.vue`, `Login.vue`, and related utility functions to accommodate the new template handling.
2026-05-18 20:37:57 +08:00
wizardchen
559b5a6e20 feat(ui): render workspace-context notifications with styled chips
The login-success and tenant-switch toasts previously showed the
workspace name and role inline as bracketed plain text, which read
flat and made the two key data points hard to scan at a glance.

Render them through a shared `renderWorkspaceNotifyContent` helper
that produces a NotifyPlugin VNode with:

* a neutral rounded chip for the workspace name (with a generic
  workspace icon), and
* a role-coloured chip for the role (with the existing per-role icon
  from useRoleLabel), so owner / admin / contributor / viewer each
  get a distinct visual identity that matches the role badges used
  elsewhere in the product.

The i18n templates lose the surrounding 「」 / "" / «» quote marks
because the chip itself now provides the visual delineation; double
emphasis looked off. Translators control the surrounding wording via
`{name}` and `{role}` placeholders and the renderer splits on those
markers to interleave plain text and chips in the original order.

The PendingTenantSwitchToast payload (sessionStorage-stashed for the
hard-reload survival trick) grows a `roleEnum` field so the post-
reload toast can colour the chip — without that, the switch toast
would fall back to a neutral chip while the login toast got the
colour treatment.

Note on icon resolution: the chip renderer imports TDesign's `Icon`
component directly rather than referencing it by string tag in `h()`
— Vue's render function does not auto-resolve component names the
way templates do, so `h('t-icon', ...)` would silently render an
unknown HTML element.
2026-05-18 20:37:57 +08:00
wizardchen
60a579d869 feat(auth): rich workspace-aware notification on successful login
Replace the plain "Login successful" toast with a TDesign Notify card
that surfaces the workspace the user just landed in and their role
there, so users immediately see which space they're working in —
especially useful now that login can drop them into a remembered
non-home tenant.

A small shared helper (utils/loginNotify.ts) handles both the password
login path (views/auth/Login.vue) and the OIDC callback path
(App.vue handleGlobalOIDCCallback), so the two flows stay consistent.
Role label goes through the same useRoleLabel composable used by the
rest of the role-aware UI, so locale + future role-name tweaks live in
one place. The membership lookup falls back gracefully (no role line)
when the active tenant isn't in the response's memberships list — e.g.
the auto-setup path that synthesises a single owner row.

The legacy auth.loginSuccess key is left in place; nothing else
references it, so a future cleanup PR can drop it safely.
2026-05-18 20:37:57 +08:00
wizardchen
0cdb922d84 feat(tenant): remember user's last-active workspace across logins
Until now a fresh login (new device, expired refresh token, cleared
browser) always dropped the user into their home tenant, even if they
spend most of their time in a peer workspace. The session-local
X-Tenant-ID override in localStorage gave a "same browser sticky"
effect, but never crossed devices or new sessions.

This adds a small server-side preference, `users.preferences.
last_active_tenant_id`, persisted in the existing jsonb column (no
new migration), and threads it through:

* Backend
  * `UserPreferences` gains `LastActiveTenantID *uint64` with sentinel
    semantics (`*0` from the PATCH endpoint = clear preference).
  * `resolveLoginTenantID` validates the stored id (tenant still
    exists + active membership, or CanAccessAllTenants) and falls
    back to home on any failure, best-effort clearing the stale
    preference so subsequent logins don't pay for it again.
  * `Login` and `LoginWithOIDC` resolve once and use the result for
    both the JWT `tenant_id` claim and the returned `active_tenant`,
    keeping the two in sync. `RefreshToken` rides through
    `GenerateTokens` so refresh rotations also land the user back in
    their preferred tenant instead of bouncing to home.
  * `UpdateUserPreferences` learns to merge the new key.
  * `PUT /auth/me/preferences` accepts the new field.

* Frontend
  * `Login.vue` now writes the user's HOME tenant id into
    `user.tenant_id` (matching the field's documented semantics) and
    expresses any active-vs-home divergence via `setSelectedTenant`,
    so `useHomeTenant` and the "current"/"home" badges stay correct
    after the backend honours a remembered preference. `App.vue`'s
    OIDC sync does the same reconciliation.
  * `TenantSelector`, `UserMenu` and the post-tenant-create handlers
    fire `persistLastActiveTenantPreference` after every successful
    user-initiated switch (switching to home sends `0` to clear).
    The call is raced against the existing reload-grace window so
    most writes finish before the page tears down; lost writes are
    recoverable on the next switch.

No new UI. Users will simply notice that, after re-logging in on
another device, they land back in the workspace they were last
using rather than always in their home tenant.

Note: `make docs` is unrelated-broken on `main` (audit_log.go
references `errors.AppError` which swag can't resolve), so the
Swagger artifacts under docs/ are intentionally not regenerated
in this PR. The handler code is the source of truth.
2026-05-18 20:37:57 +08:00
wizardchen
a6949155c9 style(tenant-members): unify role identity with a colored chip across surfaces
Wrap the role icon in a small colored circular chip and reuse the same
chip inline inside the role tags in the members and invitations
tables, so the permission-matrix popover, the members grid and the
invitations grid all share the same visual language for each role.
The chip color is keyed by role, making role recognition faster at a
glance without depending on the tag's surrounding text.
2026-05-18 19:39:53 +08:00
wizardchen
0655b545be feat(tenant): show rich workspace-switch confirmation that survives reload
Replace the lightweight "switch success" message with a richer
NotifyPlugin notification that reports the new workspace name and the
user's role in it. Because the post-switch flow does a hard reload,
the notification is stashed in sessionStorage before navigation and
consumed by App.vue on the next mount, so the toast actually appears
on the destination page with its full 6s duration instead of being
torn down a frame after it is shown.

For the cross-tenant superuser path (TenantSelector), the role line is
omitted when the user has no membership row in the target tenant,
avoiding a misleading empty/raw role value. Role labels are formatted
through the shared useRoleLabel composable so any future role text
change still lives in one place.

i18n keys updated across zh-CN, en-US, ko-KR, ru-RU.
2026-05-18 19:39:53 +08:00
Li Xianggang
c19d3543c8 feat(url): 支持docreader不上传替换白名单url的图片 2026-05-18 19:39:30 +08:00