diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 32196a89..ae6a0b32 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -71,7 +71,7 @@ body: Please provide the concrete version number you are running, **not** `latest`, `main` or `master`. You can find it on the **Settings → System Info** page (both "App Version" and "UI Version" — if they differ, please report both). If you built from source, include the commit SHA instead. - placeholder: "e.g., App Version: v0.5.2, UI Version: v0.5.2 (or commit abc1234)" + placeholder: "e.g., App Version: v0.6.0, UI Version: v0.6.0 (or commit abc1234)" validations: required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index cac7ac47..449686e4 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -57,8 +57,8 @@ body: label: Other Environment Info description: Please provide other relevant environment information placeholder: | - - WeKnora App Version: [concrete version from Settings → System Info, e.g., v0.5.2 — NOT "latest"] - - WeKnora UI Version: [concrete version from Settings → System Info, e.g., v0.5.2] + - WeKnora App Version: [concrete version from Settings → System Info, e.g., v0.6.0 — NOT "latest"] + - WeKnora UI Version: [concrete version from Settings → System Info, e.g., v0.6.0] - Deployment: [e.g., Docker, build from source] - Other relevant info... diff --git a/CHANGELOG.md b/CHANGELOG.md index f622c8bd..3d40d639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,115 @@ All notable changes to this project will be documented in this file. +## [0.6.0] - 2026-05-21 + +### New Features + +- **NEW**: **Tenant RBAC (Role-Based Access Control)** — the headline of this release (#1303). WeKnora now enforces a per-tenant role matrix on every mutating route, with per-KB resource ownership. Highlights: + - **4-tier role matrix**: `Owner` (one per tenant; can additionally delete the tenant) ⊃ `Admin` ⊃ `Contributor` (full owner of own resources, read-only on others) ⊃ `Viewer` (read-only). Two exceptions: cross-tenant superuser (`User.CanAccessAllTenants=true`) is implicit Admin in any tenant they switch into; API-Key-synthesized virtual users are pinned Admin in their owning tenant. + - **Per-KB resource ownership**: `chunk → knowledge → kb → creator_id`; same chain applies to FAQ entries, generated questions, KB tags and wiki pages. `custom_agents.creator_id` + `custom_agents.runnable_by_viewer` (default true) control agent ownership and viewer-callability. + - **Two guard families**: role guards (`Viewer()` / `Contributor()` / `Admin()` / `Owner()`) for tenant-level infra (models, vector stores, IM channels, …) and ownership guards (`OwnedKBOrAdmin()`, `OwnedAgentOrAdmin()`, `OwnedChunkKBOrAdmin()`, …) for resource writes. KB-access guard wired at the route layer for chunk / knowledge / knowledgebase routes (no per-handler helpers). + - **Tenant members**: invite / remove / role-change endpoints; new `/leave` endpoint; per-tenant audit log with daily retention sweep (default 90 days, `audit_logs.created_at` indexed); `tenant_members` table now drives membership (lifted from per-user to per-tenant in Plan 3); cross-tenant share managed by source-tenant Admin+. + - **Configurable**: `tenant.enable_rbac` (default `true`); `false` enters an "audit-only" grace window. New env knobs `WEKNORA_TENANT_ENABLE_RBAC`, `WEKNORA_TENANT_MAX_PER_USER`. RBAC state logged at startup. See [`docs/RBAC说明.md`](./docs/RBAC说明.md). +- **NEW**: **Tenant Member Management & Multi-Workspace UX** — invite-only gate, member listing UI with role chips, tenant identity surfaces reworked; tenant switcher in the user menu; tenant switch always redirects to KB list and clears tenant-scoped client state; last-active workspace persisted across logins; pending invitations dialog with polling + global invitation bell; rich workspace-aware notifications on login / tenant switch (raw-message handling, styled chips, survives page reload); QuickNav entry for members; "leave workspace" surfaced in i18n. +- **NEW**: **Self-Service Workspaces** — any user can create their own tenant (capped per user via env knob); creation dialog with i18n; tenant name + description editable inline; cross-tenant superuser mirrored as Admin role chip in the UI. +- **NEW**: **`weknora` CLI v0.3 / v0.4 (GA)** — graduates from preview to GA with comprehensive verb-noun subtree coverage: + - `agent` subtree: list / view / invoke / check / status / edit / delete / create (full agent CRUD with config rendering). + - `chunk` subtree: list / view / delete (with curation rationale). + - `session` subtree: list / view / delete. + - `search` subtree: chunks / kb / docs / sessions (replaces flat `search`). + - `kb`: new `edit`, `pin`, `empty`, `check`, `status` verbs; `delete` and other commands harmonized. + - `doc`: new `download`, `view`, `wait` (multi-target wait-all), `unlink`, `upload --recursive`; `upload` flag expansion; `delete` accepts multiple IDs. + - `auth`: new `refresh` and `token` verbs; transparent 401 retry transport. + - `context` CRUD: add / list / remove / use. + - `link` / `unlink` for project-level KB binding. + - `mcp serve` — curated stdio MCP server so AI clients (Claude Code, Cursor, …) can drive WeKnora directly; includes MCP `chunk_list` tool. + - **Globals**: `--format`, `--json` field-select, `--jq`, `--paginate`, `--all-pages` (canonical catch-up), `--input`, `--log-level`, `--from-url`, NDJSON output, bare-JSON output path, signal-aware contexts. + - **Removed**: envelope infrastructure (errors → stderr); `--dry-run`; `internal/agent` aiclient package; v0.0 scaffolding. +- **NEW**: **KB Retrieval Fan-out Across Vector Stores** — a single KB can now bind to multiple vector stores; retrieval engine fans out queries across all bound stores and merges results. KB editor validates bindings on create / copy / delete. Retriever resolution introduces a factory pattern for KB-scoped engine selection. +- **NEW**: **AES-256-GCM At-Rest Encryption** for MCP and Data Source credentials with graceful key-rotation handling. Sensitive fields redacted in API responses; new `/credentials` subresource pattern prevents credential loss on edit. +- **NEW**: **Docreader gRPC TLS + Token Auth** (#1359) — app → docreader connection can be hardened with TLS + bearer-token authentication; docreader gRPC port is no longer published to the host by default; `grpcio` floor bumped to 1.78.0 to match generated proto. +- **NEW**: **Zhipu AI Embedder** — first-class Zhipu embedding provider. +- **NEW**: **Huawei Cloud OBS** object storage joins Local / MinIO / AWS S3 / Volcengine TOS / Alibaba Cloud OSS / Kingsoft Cloud KS3 / Huawei OBS. +- **NEW**: **vLLM URL configuration for MinerU** doc parser. +- **NEW**: **Apache Doris compatibility modes** — configurable Doris compat modes with mode-switch guards. +- **NEW**: **Docreader image URL whitelist** — trusted URLs can be served as-is without re-uploading into WeKnora storage. +- **NEW**: **Server-Side User Preferences** — per-user font / theme / memory-feature toggle persisted on the server; per-user KB pinning replaces tenant-wide pin model; "Shared by me" label across surfaces. +- **NEW**: **User favorites & recents** under the user menu. +- **NEW**: **`creator_name` on agents and knowledge bases** for visibility across surfaces. +- **NEW**: **Per-session last-request state persistence** for UI restoration after reload. +- **NEW**: **Knowledge document tag selector redesign**. +- **NEW**: `vue-i18n` notification templates support raw message handling with styled chips. +- **NEW**: Custom agent service supports KB sharing. + +### Improvements + +- **IMPROVED**: Frontend offline + legacy browser support hardened. +- **IMPROVED**: Chat history rendering stability — pagination preserves message order; menu no longer refreshes the session list when opening an existing chat; session titles no longer truncate when extra horizontal space is available; session list density tightened in sidebar. +- **IMPROVED**: Session — wiki fixer now scoped to shared KB tenant; session access scoped by user (security hardening); `agent-chat` rejects requests early when `agent_id` is missing. +- **IMPROVED**: KB — indexed documents complete immediately instead of waiting for an extra sweep; vector store bindings validated on create / copy / delete; `ErrKnowledgeBaseNotFound` mapped to HTTP 404 across all handlers; `ErrSessionNotFound` mapped to HTTP 404 across all handlers. +- **IMPROVED**: `audit_log.Stop()` no longer deadlocks when `Start()` is never called. +- **IMPROVED**: Organization searchable join no longer bypasses invite code expiry. +- **IMPROVED**: Chunker no longer merges top-level heading chunks. +- **IMPROVED**: Moonshot models — `moonshot-v1-*` / `kimi-k2.5` / `k2.6` now pin `temperature=1` automatically (they return HTTP 400 for any other value); `kimi-k2` / `k2-turbo` / `k2-thinking` left untouched. +- **IMPROVED**: MinerU markdown image syntax unescape — `\!\[\]\(\)` is restored to `![]()` so downstream image extraction works. +- **IMPROVED**: Test-connection — surfaces upstream and SSRF errors verbatim; falls back to stored apiKey when test-connecting an existing model. +- **IMPROVED**: Test infrastructure — vector store tests now use a fake Elasticsearch server; knowledge base repository gains user pinning methods. +- **IMPROVED**: Embedding pipeline — Zhipu AI embedder lands; broken comment in Zhipu embedder repaired. +- **IMPROVED**: Sqlite test DDL augmented with `wiki_config` + `indexing_strategy`. +- **IMPROVED**: `agent` exclude processing docs from prompt. +- **IMPROVED**: LLM response — guard against empty `choices` and `message=None`. +- **IMPROVED**: Configurable API proxy target for frontend dev environment. +- **IMPROVED**: `DISABLE_REGISTRATION` now drives `registration_mode` too; removed redundant `WEKNORA_AUTH_REGISTRATION_MODE` env override. +- **IMPROVED**: Tenant RBAC + per-user tenant cap exposed as env knobs. +- **IMPROVED**: Auth — JWT `tenant_id` claim honored in middleware; tenant-scoped client state cleared on tenant change. +- **IMPROVED**: gin per-route logs silenced; env config banner emitted at startup. +- **IMPROVED**: Frontend — hide UI mutation surfaces for Viewer / non-creator; tenant switcher mirrors cross-tenant superuser Admin role in UI gates; role-aware UI gates no longer leak write affordances after tenant switch; agent editor `rerank` model now optional; Ollama tip hidden for remote models. +- **IMPROVED**: System Info page surfaces UI build version, DB migration errors with troubleshooting links. +- **IMPROVED**: Logger — `logger.CloneContext` propagates `TenantRole`. +- **IMPROVED**: SSE / fetch paths — dropped insecure `X-Tenant-ID` short-circuit. +- **IMPROVED**: Settings sidebar nav items grouped into labeled sections. + +### Bug Fixes + +- **FIXED**: API — `agent-chat` early reject when `agent_id` missing; deprecated tenant `ConversationConfig` field and KV write path removed. +- **FIXED**: RBAC — chunk-id ownership chain for generated-question delete; sharing routes gated, tenant-disable shared agent → Admin+; ungated mutating routes plugged; FAQ + tag mutating routes aligned with KB ownership matrix; org-tenant gate gaps from Plan 3 closed; cross-tenant superuser organization owner pinned in DB instead of derived at runtime; remaining organization mutating routes gated with Admin+; dedup pending join/upgrade requests per (org, tenant, type); allow source-tenant Admin+ to manage cross-tenant shares; rbac-ui org owner row identified by `tenant_id` (not `user_id`). +- **FIXED**: Client — `UpdateAgent` request types aligned with internal API. +- **FIXED**: Frontend — input field agent selection logic improved for shared agents; permissions enhanced across KB and agent views; security — command-palette recent searches namespaced per (user, tenant); tenant switch away from tenant-scoped routes; tenant-members inline editing input attributes; `chat`/`enableMemoryOverride` simplified. +- **FIXED**: i18n — `@` escaped in invite email placeholder; "Shared by me" label added; chat titles and "leave workspace" updates across multiple languages; RBAC messages for tenant admin requirements. +- **FIXED**: Docparser — MinerU markdown image syntax unescaped. +- **FIXED**: Migrations — `pg_trgm` created before trigram index in 000041. +- **FIXED**: Compose — docreader gRPC port no longer published to the host. +- **FIXED**: Credentials — redact sensitive fields and prevent credential loss on edit. +- **FIXED**: Auth — connection to docreader supports auth; gRPC TLS/Token rollout from #1359 hardened. + +### Refactoring + +- **REFACTOR**: `knowledgebase` — removed `TogglePinKnowledgeBase` from `KnowledgeBaseRepository` interface (replaced by per-user pinning). +- **REFACTOR**: Tenant switch navigation unified to always redirect to KB list. +- **REFACTOR**: Tenant member — tenant ID resolution simplified in handlers; tenant-access guards centralized in middleware. +- **REFACTOR**: Custom-agent — KB sharing support split out. +- **REFACTOR**: Organization — tenant-based access control; tenant-level membership transitions. +- **REFACTOR**: Retriever — factory pattern for KB-scoped engine resolution. +- **REFACTOR**: Agent — `grep_chunks` tool simplified to a single regex query. +- **REFACTOR**: Frontend — `GlobalCommandPalette`, `InputField`, sidebar, menu, `UserMenu` templates streamlined for readability. +- **REFACTOR**: CLI — comprehensive v0.3 / v0.4 cleanup: dropped `--dry-run`, dropped envelope infrastructure (errors to stderr), introduced bare-JSON output path, dropped `internal/agent` aiclient package (Go 1.26), `--limit` / `--all-pages` canonical pagination, auth security audit (gh CLI parity hardening), pre-PR audit fixes. +- **REFACTOR**: Credentials — `/credentials` subresource pattern introduced. + +### Infrastructure & Build + +- **BUILD**: Go bumped to **1.26.0** in `go.mod`. +- **BUILD**: `grpcio` floor bumped to 1.78.0 to match generated proto. +- **BUILD**: Migrations — `audit_logs.created_at` index added; daily retention sweep job. +- **BUILD**: Frontend — skill registration directory updated. + +### Documentation + +- **DOC**: New `docs/RBAC说明.md` (Chinese RBAC guide) and `docs/wiki/安全认证/RBAC说明.md`, linked with shared space docs. +- **DOC**: `docs/RBAC` documents Contributor vs `OwnedXxxOrAdmin` selection rule. +- **DOC**: Issue templates require concrete app/UI versions (not "latest"). +- **DOC**: CLI — `cli/README.md`, `cli/AGENTS.md` + `cli/CHANGELOG.md` brought in sync with v0.3 / v0.4 surface; stale e2e refs cleared; CI parity test added. + ## [0.5.2] - 2026-05-13 ### 🚀 New Features diff --git a/README.md b/README.md index 019d3f2a..2b8c3c21 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ License - Version + Version

@@ -50,13 +50,43 @@ [**WeKnora**](https://weknora.weixin.qq.com) is an open-source, LLM-powered knowledge framework built for enterprise-grade document understanding, semantic retrieval, and autonomous reasoning. -It is organized around three core capabilities: **RAG-based Quick Q&A** for everyday lookups, a **ReAct Agent** that autonomously orchestrates retrieval, MCP tools and web search to handle complex multi-step tasks, and a brand-new **Wiki Mode** in which agents distill raw documents into a self-maintaining, interlinked markdown knowledge base with an interactive knowledge graph. Combined with multi-source ingestion (Feishu / Notion / Yuque, and growing), 20+ LLM provider integrations, full Langfuse observability, and a fully self-hostable modular architecture, WeKnora turns scattered documents into a queryable, reasoning-capable, continuously evolving knowledge asset. +It is organized around three core capabilities: **RAG-based Quick Q&A** for everyday lookups, a **ReAct Agent** that autonomously orchestrates retrieval, MCP tools and web search to handle complex multi-step tasks, and a brand-new **Wiki Mode** in which agents distill raw documents into a self-maintaining, interlinked markdown knowledge base with an interactive knowledge graph. Combined with multi-source ingestion (Feishu / Notion / Yuque, and growing), 20+ LLM provider integrations, full Langfuse observability, **enterprise-ready multi-tenant RBAC** (4-tier role matrix + per-resource ownership + per-tenant audit log), and a fully self-hostable modular architecture, WeKnora turns scattered documents into a queryable, reasoning-capable, continuously evolving knowledge asset. The framework supports auto-syncing knowledge from Feishu, Notion, and Yuque (more data sources coming soon), handles 10+ document formats including PDF, Word, images, and Excel, and can serve Q&A directly through IM channels like WeCom, Feishu, Slack, and Telegram. It is compatible with major LLM providers including OpenAI, DeepSeek, Qwen (Alibaba Cloud), Zhipu, Hunyuan, Gemini, MiniMax, NVIDIA, and Ollama. Its fully modular design allows swapping LLMs, vector databases, and storage backends, with support for local and private cloud deployment ensuring complete data sovereignty. WeKnora also integrates with **Langfuse** for comprehensive observability into agent reasoning, token usage, and pipeline tracing. ## ✨ Latest Updates +**v0.6.0 Highlights:** + +- **Tenant RBAC (Role-Based Access Control)** — the headline of this release. WeKnora now enforces a 4-tier per-tenant role matrix (`Owner` / `Admin` / `Contributor` / `Viewer`) on every mutating route, with per-KB resource ownership: `chunk → knowledge → kb → creator_id`. Contributors are full owners of resources they create and read-only on other people's resources; Admins manage the whole tenant; Owners can additionally delete the tenant. See [`docs/RBAC说明.md`](./docs/RBAC说明.md). + + + + + + + + + + +
Tenant Member Management
Tenant Member Management
Workspace Switcher
Workspace Switcher
Self-Service Workspace Creation
Create Workspace
Pending Invitations
Pending Invitations
+ +- **Tenant Member Management & Multi-Workspace UX** — invite / remove members, role updates, `/leave` endpoint, invite-only gate; pending-invitations dialog + global invitation bell; tenant switcher in the user menu with role-aware UI gates; last-active workspace persisted across logins; rich workspace-aware notifications on login / tenant switch. +- **Self-Service Workspaces** — any user can create their own tenant (capped via env knob); cross-tenant superusers see an Admin role chip in the UI when switched. +- **Per-Tenant RBAC Audit Log** — every RBAC-relevant event is recorded with a daily retention sweep (default 90 days, indexed on `created_at`); cross-tenant superuser actions are pinned to the source tenant. +- **`weknora` CLI v0.3 / v0.4 (GA)** — graduates from preview to GA with verb-noun subtrees across every major resource: `agent` (CRUD + invoke / check / status), `chunk`, `session`, `search` (chunks / kb / docs / sessions), `kb` (edit / pin / empty / check / status), `doc` (download / upload --recursive / view / wait), `auth` (refresh / token), `context`, `link` / `unlink`. New `weknora mcp serve` ships a curated stdio MCP server so AI clients (Claude Code, Cursor, …) can drive WeKnora directly. Globals: `--format`, `--json` field-select, `--jq`, `--paginate`, `--all-pages`, `--input`, `--log-level`, `--from-url`, NDJSON output, transparent 401 retry, signal-aware contexts. +- **KB Retrieval Fan-out Across Vector Stores** — a single KB can now bind to multiple vector stores; the retrieval engine fans out queries across all bound stores and merges results. KB editor validates bindings on create / copy / delete to prevent inconsistent state. +- **AES-256-GCM At-Rest for MCP & Data Source Credentials** — graceful key-rotation handling; sensitive fields redacted in API responses; new `/credentials` subresource pattern prevents credential loss on edit. +- **Docreader gRPC Hardening** — docreader connection supports TLS + Token auth; gRPC port no longer published to the host by default; `grpcio` floor bumped to 1.78.0 to match the generated proto. +- **More Backends**: Zhipu AI embedder; Huawei Cloud OBS object storage; configurable vLLM URL for the MinerU doc parser; Apache Doris compatibility modes with mode-switch guards; whitelist that lets docreader skip re-uploading trusted image URLs. +- **User Preferences (Server-Side)** — per-user font / theme / memory-feature toggle persisted on the server; per-user KB pinning replaces the previous tenant-wide pin model; "Shared by me" label and creator name surfaced across knowledge bases and agents. +- **Other Improvements**: User favorites + recents; member quick-nav entry; refreshed sidebar density; inline-editable tenant info with description; knowledge document tag selector redesign; UI build version on the System Info page; Moonshot models pin `temperature=1` for `moonshot-v1-*` / `kimi-k2.5` / `k2.6` (which reject other values with HTTP 400); MinerU markdown image syntax unescape so downstream image extraction works; `ErrSessionNotFound` / `ErrKnowledgeBaseNotFound` map to HTTP 404 across all handlers; session access scoped by user; Go bumped to 1.26.0. +- **Bug Fixes**: `audit_log.Stop()` deadlock when `Start()` is never called; organization searchable join no longer bypasses invite code expiry; chunker no longer merges top-level heading chunks; infinite-scroll race condition causing missing documents fixed; indexed documents complete immediately instead of waiting for an extra sweep; offline + legacy browser support on the frontend; chat history rendering / pagination stability; test-connection falls back to the stored API key when test-connecting an existing model. + +
+Earlier Releases + **v0.5.2 Highlights:** - **Wiki Mode at Scale**: Wiki ingest now handles tens-of-thousands-document KBs via a generic task queue with dead-letter handling; the page-link graph gains a subgraph API + interactive exploration UI. @@ -70,9 +100,6 @@ The framework supports auto-syncing knowledge from Feishu, Notion, and Yuque (mo - **Other Improvements**: Per-tenant RRF tuning, a dedicated query-understanding model, batch KB management, user-scoped session pinning, a tenant-wide IM channels overview, per-user font / theme preferences, a new OpenMaiC Classroom agent skill, and a full API-docs / Swagger / Client-SDK overhaul. - **Bug Fixes**: Embedder `(nil, nil)` SIGSEGV fixed; Mimo / DeepSeek `reasoning_content` round-trip restored; multi-turn agent history rebuilt from DB (with attachment replay); OIDC login fixed; many Wiki ingest reliability fixes; FAQ no longer hallucinates summaries from filenames on empty PDFs. -
-Earlier Releases - **v0.4.0 Highlights:** - **[Knowledge Assistant](https://weknora.weixin.qq.com/platform)**: Cloud-hosted knowledge assistant service for quick onboarding without local deployment @@ -234,9 +261,9 @@ Fully modular pipeline from document parsing, vectorization, and retrieval to LL | Capability | Details | |------------|---------| | LLMs | OpenAI / Azure OpenAI / Anthropic (Claude) / DeepSeek / Qwen (Alibaba Cloud) / Zhipu / Hunyuan / Doubao (Volcengine) / Gemini / MiniMax / NVIDIA / Novita AI / SiliconFlow / OpenRouter / Ollama | -| Embeddings | Ollama / BGE / GTE / OpenAI-compatible APIs | +| Embeddings | Ollama / BGE / GTE / Zhipu / OpenAI-compatible APIs | | Vector DBs | PostgreSQL (pgvector) / Elasticsearch / Milvus / Weaviate / Qdrant / Apache Doris / Tencent VectorDB | -| Object Storage | Local / MinIO / AWS S3 / Volcengine TOS / Alibaba Cloud OSS / Kingsoft Cloud KS3 | +| Object Storage | Local / MinIO / AWS S3 / Volcengine TOS / Alibaba Cloud OSS / Kingsoft Cloud KS3 / Huawei Cloud OBS | | IM Channels | WeCom / Feishu / Slack / Telegram / DingTalk / Mattermost / WeChat | | Web Search | DuckDuckGo / Bing / Google / Tavily / Baidu / Ollama / SearXNG | @@ -246,6 +273,8 @@ Fully modular pipeline from document parsing, vectorization, and retrieval to LL |------------|---------| | Deployment | Local / Docker / Kubernetes (Helm) with private and offline support | | UI | Web UI / RESTful API / CLI (`weknora`) / Chrome Extension / WeChat Mini Program | +| Access Control | Tenant RBAC with 4-tier role matrix (Owner / Admin / Contributor / Viewer), per-KB resource ownership, per-tenant audit log, invite-only workspaces, self-service tenant creation, cross-tenant superuser | +| Security | AES-256-GCM at-rest encryption for API keys and MCP / data-source credentials with graceful key rotation; gRPC TLS + Token between app and docreader; SSRF-safe HTTP client; sandbox isolation for agent skills | | Observability | Integrated Langfuse for ReAct loops, token tracking, tool calls, and pipeline tracing | | Task Management | MQ async tasks, automatic database migration on version upgrade | | Model Management | Centralized config, per-knowledge-base model selection, multi-tenant built-in model sharing, WeKnora Cloud hosted models and parsing | diff --git a/README_CN.md b/README_CN.md index 83204367..e5387841 100644 --- a/README_CN.md +++ b/README_CN.md @@ -28,7 +28,7 @@ License - 版本 + 版本

@@ -50,12 +50,42 @@ **[WeKnora(维娜拉)](https://weknora.weixin.qq.com)** 是一款开源的、基于大语言模型(LLM)的知识管理框架,专为企业级文档理解、语义检索与智能推理场景打造。 -框架围绕三大核心能力构建:**RAG 快速问答**适合日常知识查询,**ReAct Agent 智能推理**自主编排知识检索、MCP 工具与网络搜索完成复杂多步任务,全新的 **Wiki 模式**则让 Agent 从原始文档中自治生成相互链接的 Markdown 知识库与可视化知识图谱。结合多源数据接入(飞书 / Notion / 语雀,更多持续接入中)、二十余家主流模型厂商集成、Langfuse 全链路可观测性,以及完全可私有化部署的模块化架构,WeKnora 帮助团队把分散文档沉淀为可查询、可推理、可持续演进的专属知识资产。 +框架围绕三大核心能力构建:**RAG 快速问答**适合日常知识查询,**ReAct Agent 智能推理**自主编排知识检索、MCP 工具与网络搜索完成复杂多步任务,全新的 **Wiki 模式**则让 Agent 从原始文档中自治生成相互链接的 Markdown 知识库与可视化知识图谱。结合多源数据接入(飞书 / Notion / 语雀,更多持续接入中)、二十余家主流模型厂商集成、Langfuse 全链路可观测性、**企业级多租户 RBAC(四级角色矩阵 + 资源归属 + 租户审计日志)**,以及完全可私有化部署的模块化架构,WeKnora 帮助团队把分散文档沉淀为可查询、可推理、可持续演进的专属知识资产。 框架支持从飞书、Notion 及语雀等外部平台自动同步知识(更多数据源持续接入中),覆盖 PDF、Word、图片、Excel 等十余种文档格式,并可通过企业微信、飞书、Slack、Telegram 等 IM 频道直接提供问答服务。模型层面兼容 OpenAI、DeepSeek、Qwen(阿里云)、智谱、混元、Gemini、MiniMax、NVIDIA、Ollama 等主流厂商。全流程模块化设计,大模型、向量数据库、存储等组件均可灵活替换,支持本地与私有云部署,数据完全自主可控。WeKnora 还无缝集成了 **Langfuse**,为 Agent 运行、Token 使用及任务流水线提供了全面的可观测性追踪。 ## ✨ 最新更新 +**v0.6.0 版本亮点:** + +- **租户 RBAC(多租户角色权限体系)** —— 本版本的核心特性。WeKnora 现已在所有写入路由上强制执行四级租户内角色矩阵(`Owner` / `Admin` / `Contributor` / `Viewer`),并通过 `chunk → knowledge → kb → creator_id` 的归属链实现按知识库的资源所有权。Contributor 对自己创建的资源完全自治,对他人资源只读;Admin 管理整个租户;Owner 额外拥有删除租户的权限。详见 [`docs/RBAC说明.md`](./docs/RBAC说明.md)。 + + + + + + + + + + +
成员管理
成员管理
工作区切换器
工作区切换器
自助创建工作区
创建新空间
待处理邀请
待处理邀请
+ +- **租户成员管理 + 多工作区 UX**:邀请 / 移除成员、修改角色、`/leave` 退出端点、可选的 invite-only 准入开关;待处理邀请弹窗 + 全局邀请铃铛;用户菜单内的租户切换器与按角色显隐的 UI 守卫;登录后自动恢复到上次活跃工作区;登录 / 切换工作区时展示富文本工作区通知。 +- **自助创建工作区**:任何用户都可以自助创建租户(通过环境变量限制每用户上限);跨租户超级管理员在 UI 内会展示 Admin 角色徽章。 +- **每租户 RBAC 审计日志**:所有 RBAC 相关事件均会记录,默认 90 天滚动清理(`created_at` 列建索引);跨租户超管的操作会固定记录到目标租户。 +- **`weknora` CLI v0.3 / v0.4(正式版)**:从 Preview 升级为正式版,按 verb-noun 模式覆盖所有主资源:`agent`(CRUD + invoke / check / status)、`chunk`、`session`、`search`(chunks / kb / docs / sessions)、`kb`(edit / pin / empty / check / status)、`doc`(download / upload --recursive / view / wait)、`auth`(refresh / token)、`context`、`link / unlink`。新增 `weknora mcp serve` 提供策划式 stdio MCP 服务,方便 Claude Code / Cursor 等 AI 客户端直接驱动 WeKnora。全局参数:`--format`、`--json` 字段选择、`--jq`、`--paginate`、`--all-pages`、`--input`、`--log-level`、`--from-url`,NDJSON 输出,透明的 401 重试,信号感知 context。 +- **多向量库扇出检索**:单个知识库可绑定多个向量库,检索引擎自动 fan-out 到所有绑定的向量库并合并结果。知识库创建 / 复制 / 删除时会校验向量库绑定关系,避免不一致状态。 +- **MCP 与数据源凭据 AES-256-GCM 静态加密**:支持平滑的密钥轮换;接口响应自动脱敏;新增 `/credentials` 子资源模式,避免编辑时凭据丢失。 +- **Docreader gRPC 加固**:app → docreader 连接支持 TLS + Token 鉴权;默认不再把 docreader gRPC 端口暴露到宿主机;`grpcio` 最低版本提升到 1.78.0 以匹配生成的 proto。 +- **更多后端集成**:智谱 AI Embedding;华为云 OBS 对象存储;MinerU 文档解析支持自定义 vLLM URL;Apache Doris 新增兼容模式开关与守卫;docreader 支持 URL 白名单(白名单内的图片不再重新上传)。 +- **服务端用户偏好**:字体 / 主题 / 记忆功能开关持久化到服务端;知识库置顶改为用户维度(替换原租户维度);知识库与 Agent 列表显示创建人名称与「我分享的」标识。 +- **其他改进**:用户收藏与最近访问;成员快捷导航入口;侧边栏密度精修;租户信息支持行内编辑(含描述字段);知识文档标签选择器重设计;系统信息页展示 UI 构建版本;Moonshot 模型自动对 `moonshot-v1-*` / `kimi-k2.5` / `k2.6` 强制 `temperature=1`(这些模型拒绝其它取值,会返回 HTTP 400);修复 MinerU markdown 图片语法过度转义导致下游图片提取失败;`ErrSessionNotFound` / `ErrKnowledgeBaseNotFound` 全部正确映射为 HTTP 404;会话访问按用户隔离(安全加固);Go 升级至 1.26.0。 +- **重要修复**:`audit_log.Stop()` 在 `Start()` 未调用时不再死锁;组织可搜索加入不再绕过邀请码过期校验;分块器不再合并顶层标题块;修复无限滚动加载丢失文档的竞态;建索引完成的文档立即标记完成;前端离线 / 旧浏览器兼容;对话历史渲染与分页稳定性提升;模型测试连接在编辑既有模型时会回退到已存储的 API Key。 + +
+更早版本 + **v0.5.2 版本亮点:** - **Wiki 模式规模化**:Wiki 入库通过通用任务队列 + 死信队列支撑万级文档知识库;页面链接图新增子图 API + 交互式探索 UI。 @@ -69,9 +99,6 @@ - **其他改进**:租户级 RRF 调参;查询理解专用模型;知识库批量管理与置顶分组;用户维度的会话置顶与关键词搜索;租户级 IM 频道总览;按用户保存的字体 / 主题偏好;新增 OpenMaiC 微课堂 Agent 技能;API 文档 / Swagger / Client SDK 全量整改。 - **重要修复**:修复 Embedder 在连接失败时返回 `(nil, nil)` 导致 SIGSEGV 的问题;Mimo / DeepSeek 类提供商 `reasoning_content` 正确回传;Agent 多轮历史改为从 DB 重建并修复附件跨轮丢失;修复 OIDC 登录;多个 Wiki 入库可靠性问题;空 PDF 不再凭文件名编造摘要。 -
-更早版本 - **v0.4.0 版本亮点:** - **[知识助理](https://weknora.weixin.qq.com/platform)**:云端托管的知识助理服务,无需本地部署即可快速体验 @@ -233,7 +260,8 @@ |------|------| | 模型厂商 | OpenAI / Azure OpenAI / Anthropic(Claude)/ DeepSeek / Qwen(阿里云)/ 智谱 / 混元 / 豆包(火山引擎)/ Gemini / MiniMax / NVIDIA / Novita AI / SiliconFlow / OpenRouter / Ollama | | 向量数据库 | PostgreSQL (pgvector) / Elasticsearch / Milvus / Weaviate / Qdrant / Apache Doris / 腾讯云 VectorDB | -| 对象存储 | 本地 / 腾讯云COS / 火山引擎 TOS / MinIO / AWS S3 / 阿里云 OSS / 金山云 KS3 | +| Embedding | Ollama / BGE / GTE / 智谱 / OpenAI 兼容接口 | +| 对象存储 | 本地 / 腾讯云COS / 火山引擎 TOS / MinIO / AWS S3 / 阿里云 OSS / 金山云 KS3 / 华为云 OBS | | IM 集成 | 企业微信 / 飞书 / Slack / Telegram / 钉钉 / Mattermost / 微信 | | 网络搜索 | DuckDuckGo / Bing / Google / Tavily / Baidu / Ollama / SearXNG | @@ -244,6 +272,8 @@ |------|------| | 部署 | 本地 / Docker / Kubernetes (Helm),支持私有化离线部署 | | 界面 | Web UI / RESTful API / 命令行(`weknora`)/ Chrome Extension / 微信小程序 | +| 权限控制 | 租户 RBAC 四级角色矩阵(Owner / Admin / Contributor / Viewer),按知识库的资源归属,每租户审计日志,invite-only 准入,自助创建工作区,跨租户超级管理员 | +| 安全 | API Key 与 MCP / 数据源凭据 AES-256-GCM 静态加密、支持平滑密钥轮换;app ↔ docreader gRPC TLS + Token;防 SSRF HTTP 客户端;Agent 技能沙箱隔离 | | 可观测性 | 集成 Langfuse 以追踪 ReAct 循环、Token 消耗、工具调用和任务流水线 | | 任务管理 | MQ 异步任务,版本升级自动数据库迁移 | | 模型管理 | 集中配置,知识库级别模型选择,多租户共享内置模型,WeKnora Cloud 托管模型与文档解析 | diff --git a/README_JA.md b/README_JA.md index 59325a90..a195ab4e 100644 --- a/README_JA.md +++ b/README_JA.md @@ -28,7 +28,7 @@ License - バージョン + バージョン

@@ -50,12 +50,42 @@ [**WeKnora(ウィーノラ)**](https://weknora.weixin.qq.com) は、大規模言語モデル(LLM)をベースとしたオープンソースのナレッジフレームワークで、エンタープライズ級の文書理解、セマンティック検索、自律推論シナリオ向けに設計されています。 -本フレームワークは **3 つのコア能力** を中心に構築されています:日常的な検索に最適な **RAG ベースのクイック Q&A**、ナレッジ検索・MCP ツール・Web 検索を自律的にオーケストレーションし複雑なマルチステップタスクを処理する **ReAct Agent 推論**、そして Agent が生のドキュメントから相互リンクされた Markdown ナレッジベースとインタラクティブなナレッジグラフを自律生成・維持する全く新しい **Wiki モード**。さらに、多様なデータソース連携(Feishu / Notion / Yuque、随時拡充中)、20 以上の LLM プロバイダー統合、Langfuse による全体可観測性、完全セルフホスト可能なモジュラーアーキテクチャと組み合わせることで、WeKnora は散在する文書を「検索可能・推論可能・継続的に進化する」専用ナレッジ資産へと昇華させます。 +本フレームワークは **3 つのコア能力** を中心に構築されています:日常的な検索に最適な **RAG ベースのクイック Q&A**、ナレッジ検索・MCP ツール・Web 検索を自律的にオーケストレーションし複雑なマルチステップタスクを処理する **ReAct Agent 推論**、そして Agent が生のドキュメントから相互リンクされた Markdown ナレッジベースとインタラクティブなナレッジグラフを自律生成・維持する全く新しい **Wiki モード**。さらに、多様なデータソース連携(Feishu / Notion / Yuque、随時拡充中)、20 以上の LLM プロバイダー統合、Langfuse による全体可観測性、**エンタープライズ向けマルチテナント RBAC(4 階層ロールマトリクス + リソース所有権 + テナント監査ログ)**、完全セルフホスト可能なモジュラーアーキテクチャと組み合わせることで、WeKnora は散在する文書を「検索可能・推論可能・継続的に進化する」専用ナレッジ資産へと昇華させます。 Feishu、Notion、Yuqueなどの外部プラットフォームからのナレッジ自動同期(他のデータソースも順次対応中)に対応し、PDF、Word、画像、Excelなど10以上の文書フォーマットをサポート。WeChat Work、Feishu、Slack、TelegramなどのIMチャネルから直接Q&Aサービスを提供できます。モデル層ではOpenAI、DeepSeek、Qwen(Alibaba Cloud)、Zhipu、Hunyuan、Gemini、MiniMax、NVIDIA、Ollamaなど主要プロバイダーに対応。全プロセスをモジュラー設計し、大規模モデル、ベクトルデータベース、ストレージなどのコンポーネントを柔軟に差し替え可能。ローカルおよびプライベートクラウドデプロイに対応し、データは完全に自己管理可能です。さらにWeKnoraは **Langfuse** とシームレスに統合され、Agentの推論、トークン消費、パイプラインに対する包括的な可観測性(オブザーバビリティ)を提供します。 ## ✨ 最新アップデート +**v0.6.0 バージョンのハイライト:** + +- **テナント RBAC(ロールベースアクセス制御)** — 本リリースの目玉機能。WeKnora は全ての書き込み系ルートに対し、テナント単位で 4 階層のロールマトリクス(`Owner` / `Admin` / `Contributor` / `Viewer`)を強制します。`chunk → knowledge → kb → creator_id` の所有権チェーンにより、KB 単位のリソース所有権を実現。Contributor は自分が作ったリソースには完全な権限を持ち、他人のリソースには読み取り専用。Admin はテナント全体を管理、Owner はさらにテナント削除権限を持ちます。詳細は [`docs/RBAC说明.md`](./docs/RBAC说明.md) を参照。 + + + + + + + + + + +
テナントメンバー管理
テナントメンバー管理
ワークスペース切替
ワークスペース切替
セルフサービスでのワークスペース作成
ワークスペース作成
保留中の招待
保留中の招待
+ +- **テナントメンバー管理 + マルチワークスペース UX**:メンバー招待 / 削除 / ロール変更、`/leave` エンドポイント、招待制(invite-only)ゲート;保留中招待ダイアログ + グローバル招待ベル;ユーザーメニュー内のテナント切替とロール認識型 UI ガード;最後にアクティブだったワークスペースをログイン間で復元;ログイン / テナント切替時のリッチなワークスペース通知。 +- **セルフサービスでのワークスペース作成**:任意のユーザーが自身のテナントを作成可能(環境変数で上限制御);クロステナント・スーパー管理者には UI 上で Admin ロールチップを表示。 +- **テナントごとの RBAC 監査ログ**:全 RBAC 関連イベントを記録、毎日のリテンションスイープでデフォルト 90 日保持(`created_at` にインデックス);クロステナント・スーパー管理者のアクションは発行元テナントにピン留め。 +- **`weknora` CLI v0.3 / v0.4(GA)**:プレビューから GA へ昇格。主要リソースを verb-noun サブツリーで網羅:`agent`(CRUD + invoke / check / status)、`chunk`、`session`、`search`(chunks / kb / docs / sessions)、`kb`(edit / pin / empty / check / status)、`doc`(download / upload --recursive / view / wait)、`auth`(refresh / token)、`context`、`link / unlink`。新規 `weknora mcp serve` がキュレーション済み stdio MCP サーバーを提供し、Claude Code / Cursor などの AI クライアントから WeKnora を直接操作可能。グローバルオプション:`--format`、`--json` フィールド選択、`--jq`、`--paginate`、`--all-pages`、`--input`、`--log-level`、`--from-url`、NDJSON 出力、透過的 401 リトライ、シグナル対応コンテキスト。 +- **複数ベクター DB を横断する KB 検索ファンアウト**:1 つの KB を複数のベクター DB にバインド可能。検索エンジンは全バインド先に対しクエリをファンアウトし結果をマージ。KB エディタは create / copy / delete 時にバインディングを検証し不整合を防止。 +- **MCP / データソース資格情報の AES-256-GCM 静的暗号化**:スムーズなキーローテーションをサポート;API レスポンスで機密フィールドを自動マスク;新しい `/credentials` サブリソースパターンで編集時の資格情報喪失を防止。 +- **Docreader gRPC ハードニング**:app → docreader 接続が TLS + Token 認証をサポート;docreader gRPC ポートをデフォルトでホストに公開しない;生成プロト互換のため `grpcio` 最低バージョンを 1.78.0 に。 +- **新規バックエンド統合**:Zhipu AI Embedder;華為雲 OBS オブジェクトストレージ;MinerU ドキュメントパーサーで vLLM URL を設定可能;Apache Doris に互換モードと切替ガード;docreader URL ホワイトリスト(ホワイトリスト内画像は再アップロードしない)。 +- **サーバーサイドユーザー設定**:フォント / テーマ / メモリ機能トグルをサーバーに永続化;KB ピン留めをユーザー単位に変更(従来はテナント全体共有);KB / Agent 一覧に作成者名と「自分が共有」ラベルを表示。 +- **その他の改善**:ユーザーお気に入り + 最近使用;メンバー向けクイックナビ;サイドバー密度のリフレッシュ;テナント情報のインライン編集(description フィールド付き);ナレッジドキュメントタグセレクタ再設計;System Info ページに UI ビルドバージョン表示;Moonshot 系モデル(`moonshot-v1-*` / `kimi-k2.5` / `k2.6` — 他の値で HTTP 400 を返す)に対し `temperature=1` を強制;MinerU markdown 画像構文の過剰エスケープ修正で下流の画像抽出が機能;`ErrSessionNotFound` / `ErrKnowledgeBaseNotFound` を全ハンドラで HTTP 404 にマッピング;セッションアクセスをユーザー単位にスコープ;Go を 1.26.0 にアップグレード。 +- **バグ修正**:`Start()` 未呼び出し時の `audit_log.Stop()` デッドロック;検索可能な組織参加が招待コード期限切れをバイパスしていた問題;チャンカーの最上位見出しチャンク統合バグ;無限スクロール競合でドキュメントが欠落する問題;インデックス完了済みドキュメントの即時完了;フロントエンドオフライン / レガシーブラウザ対応;チャット履歴レンダリング / ページネーション安定性向上;既存モデルのテスト接続時に保存済み API キーへフォールバック。 + +
+過去のリリース + **v0.5.2 バージョンのハイライト:** - **Wiki モードのスケール強化**:Wiki インジェストが汎用タスクキュー + デッドレターキューにより万件規模の KB に対応。ページリンクグラフはサブグラフ API + インタラクティブ探索 UI を追加。 @@ -69,9 +99,6 @@ Feishu、Notion、Yuqueなどの外部プラットフォームからのナレッ - **その他の改善**:テナント単位の RRF 調整;クエリ理解用の専用モデル;KB の一括管理;ユーザー単位のセッションピン留めとキーワード検索;テナント全体の IM チャネル概観;ユーザー単位で保存されるフォント / テーマ設定;OpenMaiC マイクロクラスルームの新規 Agent スキル;API ドキュメント / Swagger / Client SDK の全面リフレッシュ。 - **バグ修正**:Embedder が接続失敗時に `(nil, nil)` を返して SIGSEGV に至る問題を修正;Mimo / DeepSeek 系プロバイダーの `reasoning_content` ラウンドトリップ復元;Agent 多ターン履歴を DB から再構築(添付ファイル replay 含む);OIDC ログイン修正;Wiki インジェストの信頼性向上多数;空 PDF でファイル名から要約を捏造しないよう修正。 -
-過去のリリース - **v0.4.0 バージョンのハイライト:** - **[知識アシスタント](https://weknora.weixin.qq.com/platform)**:クラウドホスティング型知識アシスタントサービス、ローカルデプロイ不要で即座に利用可能 diff --git a/README_KO.md b/README_KO.md index 63ef7e60..f590810a 100644 --- a/README_KO.md +++ b/README_KO.md @@ -28,7 +28,7 @@ License - 버전 + 버전

@@ -50,12 +50,42 @@ [**WeKnora**](https://weknora.weixin.qq.com)는 엔터프라이즈급 문서 이해, 시맨틱 검색, 자율 추론 시나리오를 위해 설계된 오픈소스 LLM 기반 지식 프레임워크입니다. -본 프레임워크는 **세 가지 핵심 역량**을 중심으로 구성됩니다. 일상 검색에 최적화된 **RAG 기반 빠른 Q&A**, 지식 검색·MCP 도구·웹 검색을 자율적으로 오케스트레이션하여 복잡한 다단계 작업을 처리하는 **ReAct Agent 추론**, 그리고 Agent가 원본 문서에서 상호 연결된 마크다운 지식베이스와 인터랙티브 지식 그래프를 스스로 생성·유지하는 완전히 새로운 **Wiki 모드**입니다. 다양한 데이터 소스 연동(Feishu / Notion / Yuque, 지속 확장 중), 20개 이상의 LLM 프로바이더 통합, Langfuse 기반 풀스택 관측 가능성, 완전 셀프호스팅이 가능한 모듈형 아키텍처를 결합하여, WeKnora는 흩어진 문서를 검색·추론 가능하며 지속적으로 진화하는 전용 지식 자산으로 탈바꿈시킵니다. +본 프레임워크는 **세 가지 핵심 역량**을 중심으로 구성됩니다. 일상 검색에 최적화된 **RAG 기반 빠른 Q&A**, 지식 검색·MCP 도구·웹 검색을 자율적으로 오케스트레이션하여 복잡한 다단계 작업을 처리하는 **ReAct Agent 추론**, 그리고 Agent가 원본 문서에서 상호 연결된 마크다운 지식베이스와 인터랙티브 지식 그래프를 스스로 생성·유지하는 완전히 새로운 **Wiki 모드**입니다. 다양한 데이터 소스 연동(Feishu / Notion / Yuque, 지속 확장 중), 20개 이상의 LLM 프로바이더 통합, Langfuse 기반 풀스택 관측 가능성, **엔터프라이즈 멀티 테넌트 RBAC(4단계 역할 매트릭스 + 리소스 소유권 + 테넌트 감사 로그)**, 완전 셀프호스팅이 가능한 모듈형 아키텍처를 결합하여, WeKnora는 흩어진 문서를 검색·추론 가능하며 지속적으로 진화하는 전용 지식 자산으로 탈바꿈시킵니다. Feishu, Notion, Yuque 등 외부 플랫폼에서 지식 자동 동기화를 지원하며(추가 데이터 소스 개발 중), PDF, Word, 이미지, Excel 등 10가지 이상의 문서 포맷을 처리합니다. WeChat Work, Feishu, Slack, Telegram 등의 IM 채널을 통해 Q&A 서비스를 직접 제공할 수 있습니다. 모델 레이어에서 OpenAI, DeepSeek, Qwen(Alibaba Cloud), Zhipu, Hunyuan, Gemini, MiniMax, NVIDIA, Ollama 등 주요 프로바이더를 지원합니다. 전체 프로세스가 모듈화 설계되어 LLM, 벡터 DB, 스토리지 등 구성 요소를 유연하게 교체 가능하며, 로컬 및 프라이빗 클라우드 배포를 지원하여 데이터 완전 자체 관리가 가능합니다. 또한 WeKnora는 **Langfuse**와 원활하게 통합되어 Agent 추론, 토큰 사용량 및 파이프라인에 대한 포괄적인 관측 가능성(Observability)을 제공합니다. ## ✨ 최신 업데이트 +**v0.6.0 하이라이트:** + +- **테넌트 RBAC(역할 기반 접근 제어)** — 이번 릴리스의 핵심 기능. WeKnora는 이제 모든 변경 라우트에 대해 4단계 테넌트 역할 매트릭스(`Owner` / `Admin` / `Contributor` / `Viewer`)를 강제하며, `chunk → knowledge → kb → creator_id` 체인으로 KB 단위 리소스 소유권을 구현합니다. Contributor는 자신이 만든 리소스에 대해 완전한 권한, 다른 사람의 리소스는 읽기 전용. Admin은 테넌트 전체를 관리, Owner는 추가로 테넌트 삭제 권한을 가집니다. 자세한 내용은 [`docs/RBAC说明.md`](./docs/RBAC说明.md). + + + + + + + + + + +
테넌트 멤버 관리
테넌트 멤버 관리
워크스페이스 전환기
워크스페이스 전환기
셀프 서비스 워크스페이스 생성
워크스페이스 생성
보류 중 초대
보류 중 초대
+ +- **테넌트 멤버 관리 및 멀티 워크스페이스 UX**: 멤버 초대 / 삭제 / 역할 변경, `/leave` 엔드포인트, 초대 전용(invite-only) 게이트; 보류 중 초대 다이얼로그 + 글로벌 초대 알림 벨; 사용자 메뉴 내 테넌트 전환기와 역할 인식 UI 가드; 로그인 시 마지막 활성 워크스페이스 자동 복원; 로그인 / 테넌트 전환 시 워크스페이스 컨텍스트가 담긴 풍부한 알림. +- **셀프 서비스 워크스페이스 생성**: 모든 사용자가 자신의 테넌트를 만들 수 있음(환경 변수로 사용자별 상한 제어); 크로스 테넌트 슈퍼 관리자에게는 UI에서 Admin 역할 칩 표시. +- **테넌트별 RBAC 감사 로그**: 모든 RBAC 관련 이벤트를 기록, 일일 리텐션 스윕으로 기본 90일 보관(`created_at` 인덱싱); 크로스 테넌트 슈퍼 관리자 작업은 원본 테넌트에 고정. +- **`weknora` CLI v0.3 / v0.4(GA)**: 프리뷰에서 정식 버전으로 승격, 모든 주요 리소스에 대해 verb-noun 서브트리 커버리지: `agent`(CRUD + invoke / check / status), `chunk`, `session`, `search`(chunks / kb / docs / sessions), `kb`(edit / pin / empty / check / status), `doc`(download / upload --recursive / view / wait), `auth`(refresh / token), `context`, `link / unlink`. 새 `weknora mcp serve`로 큐레이팅된 stdio MCP 서버 제공, Claude Code / Cursor 같은 AI 클라이언트가 WeKnora를 직접 구동 가능. 글로벌 옵션: `--format`, `--json` 필드 선택, `--jq`, `--paginate`, `--all-pages`, `--input`, `--log-level`, `--from-url`, NDJSON 출력, 투명 401 재시도, 시그널 인식 컨텍스트. +- **여러 벡터 저장소에 걸친 KB 검색 팬아웃**: 단일 KB가 여러 벡터 저장소에 바인딩 가능; 검색 엔진이 모든 바인딩된 저장소에 쿼리를 팬아웃하고 결과를 병합. KB 에디터는 create / copy / delete 시 바인딩을 검증해 불일치 상태를 방지. +- **MCP 및 데이터 소스 자격 증명 AES-256-GCM 정적 암호화**: 매끄러운 키 로테이션 지원; API 응답에서 민감 필드 자동 마스킹; 편집 시 자격 증명 손실을 방지하는 새로운 `/credentials` 서브리소스 패턴. +- **Docreader gRPC 하드닝**: app → docreader 연결이 TLS + Token 인증 지원; 기본적으로 docreader gRPC 포트를 호스트에 노출하지 않음; 생성된 proto와 일치시키기 위해 `grpcio` 최소 버전을 1.78.0으로 상향. +- **신규 백엔드 통합**: Zhipu AI 임베더; 화웨이 클라우드 OBS 오브젝트 스토리지; MinerU 문서 파서용 vLLM URL 설정 가능; Apache Doris 호환성 모드 + 모드 전환 가드; docreader URL 화이트리스트(화이트리스트 내 이미지는 재업로드하지 않음). +- **서버 사이드 사용자 환경설정**: 폰트 / 테마 / 메모리 기능 토글을 서버에 영속화; KB 핀 고정을 사용자 단위로(기존 테넌트 전체 공유 모델 대체); KB / Agent 목록에 생성자 이름과 "내가 공유" 라벨 표시. +- **기타 개선**: 사용자 즐겨찾기 + 최근 사용; 멤버용 빠른 탐색 진입점; 사이드바 밀도 리프레시; 테넌트 정보 인라인 편집(description 필드 포함); 지식 문서 태그 선택기 재설계; System Info 페이지에 UI 빌드 버전 표시; Moonshot 모델(`moonshot-v1-*` / `kimi-k2.5` / `k2.6` — 다른 값은 HTTP 400 반환)에 대해 `temperature=1` 강제; MinerU markdown 이미지 구문 과도 이스케이프 수정으로 하류 이미지 추출 정상화; `ErrSessionNotFound` / `ErrKnowledgeBaseNotFound`를 모든 핸들러에서 HTTP 404로 매핑; 세션 액세스를 사용자 단위로 스코프; Go 1.26.0으로 업그레이드. +- **버그 수정**: `Start()` 미호출 시 `audit_log.Stop()` 데드락; 검색 가능한 조직 가입이 초대 코드 만료를 우회하던 문제; 청커가 최상위 헤딩 청크를 병합하던 버그; 무한 스크롤 경쟁으로 문서가 누락되던 문제; 인덱싱 완료된 문서가 즉시 완료되도록; 프론트엔드 오프라인 / 레거시 브라우저 지원; 채팅 히스토리 렌더링 / 페이지네이션 안정성 향상; 기존 모델 테스트 연결 시 저장된 API 키로 폴백. + +
+이전 릴리스 + **v0.5.2 하이라이트:** - **Wiki 모드 대규모 확장**: Wiki 인제스트가 일반 작업 큐 + 데드레터 큐로 만 건 규모 KB까지 확장; 페이지 링크 그래프에 서브그래프 API + 인터랙티브 탐색 UI 추가. @@ -69,9 +99,6 @@ Feishu, Notion, Yuque 등 외부 플랫폼에서 지식 자동 동기화를 지 - **기타 개선**: 테넌트별 RRF 튜닝; 쿼리 이해 전용 모델; KB 일괄 관리; 사용자 단위 세션 고정과 키워드 검색; 테넌트 전체 IM 채널 개요; 사용자별 저장되는 글꼴 / 테마 설정; 새로운 OpenMaiC 마이크로 클래스룸 Agent 스킬; API 문서 / Swagger / Client SDK 전면 정비. - **버그 수정**: Embedder가 연결 실패 시 `(nil, nil)` 을 반환해 SIGSEGV를 유발하던 문제 수정; Mimo / DeepSeek 계열 `reasoning_content` 라운드트립 복원; Agent 다중 턴 히스토리를 DB에서 재구성(첨부 replay 포함); OIDC 로그인 수정; Wiki 인제스트 신뢰성 다수 개선; 빈 PDF에서 파일명으로 요약을 환각하지 않도록 수정. -
-이전 릴리스 - **v0.4.0 하이라이트:** - **[지식 어시스턴트](https://weknora.weixin.qq.com/platform)**: 클라우드 호스팅 지식 어시스턴트 서비스, 로컬 배포 없이 빠르게 시작 가능 diff --git a/VERSION b/VERSION index cb0c939a..a918a2aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.2 +0.6.0 diff --git a/docs/QA.md b/docs/QA.md index 7fb417aa..2a10ceb9 100644 --- a/docs/QA.md +++ b/docs/QA.md @@ -203,5 +203,35 @@ Wiki 模式允许 Agent 根据原始文档自动生成并维护一套结构化 3. 当你向该知识库上传文档时,系统会自动触发异步任务,通过大模型提取文档中的实体与核心概念,并自动生成结构化的 Wiki 页面及页面间的知识图谱链接。 4. 你可以在该知识库的“Wiki”标签页中,使用专用的 Wiki 浏览器查阅、管理页面,并通过可视化的知识图谱查看不同内容之间的关联关系。 +## 11. 升级到 0.6.0 后,原本能做的操作变成了「权限不足」? + +0.6.0 引入了租户内 RBAC(角色矩阵 + 资源归属),所有写入接口都会按角色 + `creator_id` 鉴权。常见现象: + +- **看得到但点不动**:你大概率是该资源的 `Viewer` 或非创建者的 `Contributor`,UI 已经把写操作隐藏/置灰。检查 **用户菜单 → 当前工作区** 角色徽章。 +- **共享空间里的 KB / Agent**:他人共享给你的 KB 默认按 `Viewer` 看待;要写需要在源租户里被授予 `Admin+`。 +- **API Key 调用**:`X-API-Key` 合成虚拟用户固定为所属租户的 `Admin`(仅删除租户需 `Owner`),脚本一般无需迁移。 +- **跨租户超管**:要 `User.CanAccessAllTenants=true` 且 `enable_cross_tenant_access=true`,并通过 `X-Tenant-ID` 切租户。 + +如需临时回退到「仅审计、不拦截」灰度窗口,可在配置里设置 `tenant.enable_rbac=false`(或环境变量 `WEKNORA_TENANT_ENABLE_RBAC=false`)。完整的角色矩阵和归属链请见 [`docs/RBAC说明.md`](./RBAC说明.md)。 + +## 12. 为什么登录后没有自动回到上次的工作区? + +升级到 0.6.0 后系统会记住「最后活跃工作区」并在登录后自动恢复。若仍未恢复,通常是: + +1. 浏览器清理了 LocalStorage / 切换了浏览器; +2. 你最后访问的那个工作区已经把你移除(`/leave` 或被管理员剔除)— 系统会回退到默认租户; +3. JWT 中携带了 `tenant_id` 但已无效 — 退出重登录即可。 + +## 13. 如何让多人协作时正确分配权限? + +按照 [`docs/RBAC说明.md`](./RBAC说明.md) 的角色矩阵: + +- 只读用户 → `Viewer` +- 普通成员(上传文档、维护「自己」的 KB / Agent)→ `Contributor` +- 运维人员(管理共享模型、向量库、解析器等基础设施)→ `Admin` +- 租户所有者(拥有删除租户权限,每租户唯一)→ `Owner` + +如果你希望开启「invite-only」(不允许自助注册到本租户),可在租户设置里打开邀请制,并通过「邀请」入口签发邀请码或链接。 + ## P.S. 如果以上方式未解决问题,请在issue中描述您的问题,并提供必要的日志信息辅助我们进行问题排查 diff --git a/docs/RBAC说明.md b/docs/RBAC说明.md index 5b15c5a4..26b277a1 100644 --- a/docs/RBAC说明.md +++ b/docs/RBAC说明.md @@ -184,6 +184,37 @@ Pinia 中的 `authStore` 暴露: 这是后端守卫的镜像:**任何在后端会 403 的按钮,前端直接隐藏而不是让用户点了再吃错误。** +### 前端实际界面 + + + + + + + + + + + + +
+ 成员管理页
+ 成员管理 +
同时展示「待接受的邀请」和「空间成员」两组列表;只有 Owner 可以新增 / 移除成员;右上角的「审计日志」入口跳转到 audit_logs 视图。 +
+ 用户菜单 + 工作区切换器
+ 用户菜单 + 切换空间 +
左侧:当前空间角色徽章 / 设置入口 / 退出;右侧:切换到其它空间,「当前」角标标识活跃工作区。 +
+ 自助创建工作区
+ 创建新空间 +
任何用户都可以自助创建租户,创建后自动成为新空间的 Owner(受 WEKNORA_TENANT_MAX_PER_USER 上限保护)。 +
+ 待处理邀请弹窗
+ 我的邀请 +
用户菜单上的邀请铃铛会展示来自其它空间的待处理邀请,可直接「接受 / 拒绝」;7 天未响应自动过期。 +
+ ## 九、常见问题 ### 升级后所有人都变成了 Contributor,找不到 Admin? diff --git a/docs/docs.go b/docs/docs.go index b5febc90..71f656e7 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1942,6 +1942,30 @@ const docTemplate = `{ } } }, + "/auth/config": { + "get": { + "description": "返回当前部署的注册模式等公开认证配置,供前端决定是否展示注册入口", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "获取认证配置", + "responses": { + "200": { + "description": "认证配置", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/auth/login": { "post": { "description": "用户登录并获取访问令牌", @@ -2052,6 +2076,58 @@ const docTemplate = `{ } } }, + "/auth/me/preferences": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "按 PATCH 语义合并用户偏好(仅覆盖请求体里出现的字段,其余字段保持不变),\n数据存放在 users.preferences (JSON),跨设备/浏览器自动同步。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "更新当前用户的个性化设置", + "parameters": [ + { + "description": "Preferences patch", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.updateMyPreferencesRequest" + } + } + ], + "responses": { + "200": { + "description": "更新后的偏好", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "请求参数错误", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "401": { + "description": "未授权", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, "/auth/oidc/callback": { "get": { "description": "接收OIDC provider回调并由后端完成code交换,随后重定向回前端登录页", @@ -2251,6 +2327,65 @@ const docTemplate = `{ } } }, + "/auth/switch-tenant": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "为当前用户在目标租户重新签发访问令牌;要求该用户在目标租户存在 active 成员关系", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "切换激活租户", + "parameters": [ + { + "description": "切换请求", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string" + }, + "tenant_id": { + "type": "integer" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.LoginResponse" + } + }, + "400": { + "description": "参数错误", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "403": { + "description": "无该租户成员关系", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, "/auth/validate": { "get": { "security": [ @@ -6942,6 +7077,122 @@ const docTemplate = `{ } } }, + "/mcp-services/{id}/credentials": { + "put": { + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "为指定字段写入新凭据;省略的字段保留原值;空字符串视为 no-op(如需删除请用 DELETE)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "MCP服务" + ], + "summary": "设置 MCP 服务凭据", + "parameters": [ + { + "type": "string", + "description": "MCP 服务 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "{api_key?: string, token?: string}", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "写入后的凭据状态", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "请求参数错误", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "404": { + "description": "服务不存在", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, + "/mcp-services/{id}/credentials/{field}": { + "delete": { + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "删除指定字段的存储凭据;删除已为空的字段是幂等的", + "produces": [ + "application/json" + ], + "tags": [ + "MCP服务" + ], + "summary": "移除 MCP 服务的单个凭据字段", + "parameters": [ + { + "type": "string", + "description": "MCP 服务 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "字段名(api_key | token)", + "name": "field", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "字段名非法", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "404": { + "description": "服务不存在", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, "/mcp-services/{id}/resources": { "get": { "security": [ @@ -7153,6 +7404,136 @@ const docTemplate = `{ } } }, + "/me/invitations": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "返回当前登录用户的待接受邀请(默认仅 pending),用于头像入口和 /invitations 收件箱页。", + "produces": [ + "application/json" + ], + "tags": [ + "我的邀请" + ], + "summary": "列出我的待接受邀请", + "parameters": [ + { + "type": "boolean", + "description": "是否包含已处理 / 已过期等终止态行(默认 false)", + "name": "include_terminal", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/me/invitations/pending-count": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "轻量级 endpoint,返回当前登录用户的 pending 邀请数,用于头像旁的角标轮询。", + "produces": [ + "application/json" + ], + "tags": [ + "我的邀请" + ], + "summary": "获取我的待处理邀请数", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/me/invitations/{inv_id}/accept": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "当前登录用户接受一条 pending 邀请;服务端会同时写入 tenant_members 行。", + "produces": [ + "application/json" + ], + "tags": [ + "我的邀请" + ], + "summary": "接受邀请", + "parameters": [ + { + "type": "string", + "description": "邀请 ID", + "name": "inv_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/me/invitations/{inv_id}/decline": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "当前登录用户拒绝一条 pending 邀请;不创建 tenant_members 行。", + "produces": [ + "application/json" + ], + "tags": [ + "我的邀请" + ], + "summary": "拒绝邀请", + "parameters": [ + { + "type": "string", + "description": "邀请 ID", + "name": "inv_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/messages/chat-history-stats": { "get": { "security": [ @@ -7662,7 +8043,7 @@ const docTemplate = `{ "Bearer": [] } ], - "description": "获取当前用户所属的所有组织,并附带各空间内知识库/智能体数量", + "description": "获取当前租户所属的所有组织,并附带各空间内知识库/智能体数量", "produces": [ "application/json" ], @@ -8362,7 +8743,7 @@ const docTemplate = `{ "Bearer": [] } ], - "description": "获取组织的所有成员", + "description": "获取组织的所有成员(按租户)", "produces": [ "application/json" ], @@ -8389,14 +8770,14 @@ const docTemplate = `{ } } }, - "/organizations/{id}/members/{user_id}": { + "/organizations/{id}/members/{tenant_id}": { "put": { "security": [ { "Bearer": [] } ], - "description": "更新组织成员的角色(需要管理员权限)", + "description": "更新组织成员(租户)的角色(需要管理员权限)", "consumes": [ "application/json" ], @@ -8417,8 +8798,8 @@ const docTemplate = `{ }, { "type": "string", - "description": "用户ID", - "name": "user_id", + "description": "成员租户ID", + "name": "tenant_id", "in": "path", "required": true }, @@ -8454,7 +8835,7 @@ const docTemplate = `{ "Bearer": [] } ], - "description": "从组织中移除成员(需要管理员权限)", + "description": "从组织中移除成员租户(需要管理员权限)", "tags": [ "组织管理" ], @@ -8469,8 +8850,8 @@ const docTemplate = `{ }, { "type": "string", - "description": "用户ID", - "name": "user_id", + "description": "成员租户ID", + "name": "tenant_id", "in": "path", "required": true } @@ -8545,21 +8926,21 @@ const docTemplate = `{ } } }, - "/organizations/{id}/search-users": { + "/organizations/{id}/search-tenants": { "get": { "security": [ { "Bearer": [] } ], - "description": "搜索用户(排除已有成员)用于邀请加入组织", + "description": "搜索租户(排除已加入的租户)用于邀请加入组织;按租户去重,附带代表用户", "produces": [ "application/json" ], "tags": [ "组织管理" ], - "summary": "搜索可邀请的用户", + "summary": "搜索可邀请的租户", "parameters": [ { "type": "string", @@ -8570,7 +8951,7 @@ const docTemplate = `{ }, { "type": "string", - "description": "搜索关键词(用户名或邮箱)", + "description": "搜索关键词(租户名、用户名或邮箱)", "name": "q", "in": "query", "required": true @@ -8600,6 +8981,12 @@ const docTemplate = `{ } } }, + "/organizations/{id}/search-users": { + "get": { + "deprecated": true, + "responses": {} + } + }, "/organizations/{id}/shared-agents": { "get": { "security": [ @@ -9780,7 +10167,7 @@ const docTemplate = `{ "Bearer": [] } ], - "description": "创建新的租户", + "description": "创建新的租户。任意已登录用户均可调用以建立自己的新工作区,\n调用方会被自动设为该租户的 Owner。跨租户超管仍可像以前一样\n通过本接口创建任意租户。", "consumes": [ "application/json" ], @@ -9798,7 +10185,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant" + "$ref": "#/definitions/internal_handler.createTenantRequest" } } ], @@ -9854,82 +10241,6 @@ const docTemplate = `{ } } }, - "/tenants/kv/agent-config": { - "get": { - "security": [ - { - "Bearer": [] - }, - { - "ApiKeyAuth": [] - } - ], - "description": "获取租户的全局Agent配置(默认应用于所有会话)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "租户管理" - ], - "summary": "获取租户Agent配置", - "responses": { - "200": { - "description": "Agent配置", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "400": { - "description": "请求参数错误", - "schema": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" - } - } - } - } - }, - "/tenants/kv/conversation-config": { - "get": { - "security": [ - { - "Bearer": [] - }, - { - "ApiKeyAuth": [] - } - ], - "description": "获取租户的全局对话配置(默认应用于普通模式会话)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "租户管理" - ], - "summary": "获取租户对话配置", - "responses": { - "200": { - "description": "对话配置", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "400": { - "description": "请求参数错误", - "schema": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" - } - } - } - } - }, "/tenants/kv/prompt-templates": { "get": { "security": [ @@ -10016,7 +10327,7 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "获取租户级别的KV配置(支持agent-config、web-search-config、conversation-config)", + "description": "获取租户级别的KV配置(支持web-search-config、prompt-templates、parser-engine-config、storage-engine-config、chat-history-config、retrieval-config)", "consumes": [ "application/json" ], @@ -10061,7 +10372,7 @@ const docTemplate = `{ "ApiKeyAuth": [] } ], - "description": "更新租户级别的KV配置(支持agent-config、web-search-config、conversation-config)", + "description": "更新租户级别的KV配置(支持web-search-config、parser-engine-config、storage-engine-config、chat-history-config、retrieval-config)", "consumes": [ "application/json" ], @@ -10369,6 +10680,535 @@ const docTemplate = `{ } } }, + "/tenants/{id}/audit-log": { + "get": { + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "返回该租户最近的审计事件,按 id 倒序。游标分页:将上次响应的 next_cursor 作为下一次请求的 after_id。", + "produces": [ + "application/json" + ], + "tags": [ + "审计日志" + ], + "summary": "获取租户审计日志", + "parameters": [ + { + "type": "string", + "description": "租户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "游标:返回 id 小于此值的记录(默认从最新开始)", + "name": "after_id", + "in": "query" + }, + { + "type": "integer", + "description": "页大小,1-100,默认 50", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "按 action 精确过滤(如 rbac.member_added / rbac.access_denied)", + "name": "action", + "in": "query" + }, + { + "type": "string", + "description": "按 outcome 精确过滤(success / denied)", + "name": "outcome", + "in": "query" + }, + { + "type": "string", + "description": "按 actor_user_id 精确过滤", + "name": "actor", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/internal_handler.auditLogListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, + "/tenants/{id}/invitations": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "按 tenant 列出待接受 / 历史邀请。query include_terminal=true 时附带 accepted/declined/revoked/expired。", + "produces": [ + "application/json" + ], + "tags": [ + "租户邀请" + ], + "summary": "列出租户邀请", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "是否包含终止态行(默认 false)", + "name": "include_terminal", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "页码(从 1 起)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "每页数量", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Owner 通过邮箱邀请已注册用户加入当前租户;被邀请人需要在 /me/invitations 接受后才会成为成员。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "租户邀请" + ], + "summary": "发出租户邀请", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "邀请请求", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.createInvitationRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/tenants/{id}/invitations/{inv_id}": { + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Owner 取消一条还在 pending 的邀请;已 accepted/declined/revoked/expired 的行不可再撤销。", + "produces": [ + "application/json" + ], + "tags": [ + "租户邀请" + ], + "summary": "撤销待接受邀请", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "邀请 ID", + "name": "inv_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/tenants/{id}/leave": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "调用方主动退出当前租户。等价于以自己的 user_id 调 RemoveMember,", + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "退出当前租户", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/tenants/{id}/members": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "分页返回当前租户内 active 成员(含每位成员的角色、邮箱、头像);支持 q 按邮箱/用户名筛选", + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "列出租户成员", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "按邮箱/用户名模糊筛选", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "页码(从 1 起)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "每页数量(最大 100)", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "直接添加租户成员(直加路径)", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "邀请请求", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.addMemberRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/tenants/{id}/members/{user_id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Owner 修改某位成员在当前租户内的角色;不能将最后一位 Owner 降级", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "修改租户成员角色", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "用户 ID", + "name": "user_id", + "in": "path", + "required": true + }, + { + "description": "目标角色", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.updateMemberRoleRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Owner 将某位成员从当前租户中移除(软删除 tenant_members 行);不能移除最后一位 Owner", + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "移除租户成员", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "用户 ID", + "name": "user_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/user/favorites": { + "get": { + "description": "Lists this user's starred resources in the current tenant for a given type", + "tags": [ + "User" + ], + "summary": "List my favorites", + "parameters": [ + { + "type": "string", + "description": "Resource type (kb | agent)", + "name": "type", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "tags": [ + "User" + ], + "summary": "Star a resource", + "parameters": [ + { + "description": "Type + id", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.AddFavoriteRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/user/favorites/{type}/{id}": { + "delete": { + "tags": [ + "User" + ], + "summary": "Unstar a resource", + "parameters": [ + { + "type": "string", + "description": "Resource type", + "name": "type", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Resource id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/vector-stores": { "get": { "security": [ @@ -11366,7 +12206,9 @@ const docTemplate = `{ 2100, 2101, 2102, - 2103 + 2103, + 2200, + 2201 ], "x-enum-varnames": [ "ErrBadRequest", @@ -11388,7 +12230,9 @@ const docTemplate = `{ "ErrAgentMissingThinkingModel", "ErrAgentMissingAllowedTools", "ErrAgentInvalidMaxIterations", - "ErrAgentInvalidTemperature" + "ErrAgentInvalidTemperature", + "ErrVectorStoreBindingInvalid", + "ErrVectorStoreUnavailable" ] }, "github_com_Tencent_WeKnora_internal_infrastructure_chunker.DocProfile": { @@ -11502,133 +12346,6 @@ const docTemplate = `{ } } }, - "github_com_Tencent_WeKnora_internal_types.AgentConfig": { - "type": "object", - "properties": { - "allowed_skills": { - "description": "Skill names whitelist (empty = allow all)", - "type": "array", - "items": { - "type": "string" - } - }, - "allowed_tools": { - "description": "List of allowed tool names", - "type": "array", - "items": { - "type": "string" - } - }, - "history_turns": { - "description": "Number of history turns to keep in context", - "type": "integer" - }, - "knowledge_bases": { - "description": "Accessible knowledge base IDs", - "type": "array", - "items": { - "type": "string" - } - }, - "knowledge_ids": { - "description": "Accessible knowledge IDs (individual documents)", - "type": "array", - "items": { - "type": "string" - } - }, - "llm_call_timeout": { - "description": "LLM call timeout in seconds (default: 120). Controls the maximum time for a single LLM call.", - "type": "integer" - }, - "max_context_tokens": { - "description": "Maximum context window tokens for the agent (default: 200000).\nThe agent compresses older messages to stay within this limit,\npreserving tool_call/tool_result pairs.", - "type": "integer" - }, - "max_iterations": { - "description": "Maximum number of ReAct iterations", - "type": "integer" - }, - "max_tool_output_chars": { - "description": "Maximum character length for tool output (default: 16000).\nOutputs exceeding this limit are truncated with head + tail preservation.", - "type": "integer" - }, - "mcp_selection_mode": { - "description": "MCP service selection", - "type": "string" - }, - "mcp_services": { - "description": "Selected MCP service IDs (when mode is \"selected\")", - "type": "array", - "items": { - "type": "string" - } - }, - "multi_turn_enabled": { - "description": "Whether multi-turn conversation is enabled", - "type": "boolean" - }, - "parallel_tool_calls": { - "description": "Whether to execute independent tool calls in parallel (default: false).\nWhen enabled and the LLM returns multiple tool calls, they run concurrently via errgroup.", - "type": "boolean" - }, - "retain_retrieval_history": { - "description": "Whether to retain retrieval history (like wiki_read_page results) across turns (default: false)", - "type": "boolean" - }, - "retrieve_kb_only_when_mentioned": { - "description": "Whether to retrieve knowledge base only when explicitly mentioned with @ (default: false)", - "type": "boolean" - }, - "skill_dirs": { - "description": "Directories to search for skills", - "type": "array", - "items": { - "type": "string" - } - }, - "skills_enabled": { - "description": "Skills configuration (Progressive Disclosure pattern)", - "type": "boolean" - }, - "system_prompt": { - "description": "Unified system prompt (uses web_search_status placeholder for dynamic behavior)", - "type": "string" - }, - "system_prompt_web_disabled": { - "description": "Deprecated: Custom prompt when web search is disabled", - "type": "string" - }, - "system_prompt_web_enabled": { - "description": "Deprecated: Use SystemPrompt instead. Kept for backward compatibility during migration.", - "type": "string" - }, - "temperature": { - "description": "LLM temperature for agent", - "type": "number" - }, - "thinking": { - "description": "Whether to enable thinking mode (for models that support extended thinking)", - "type": "boolean" - }, - "use_custom_system_prompt": { - "description": "Whether to use custom system prompt instead of default", - "type": "boolean" - }, - "web_search_enabled": { - "description": "Whether web search tool is enabled", - "type": "boolean" - }, - "web_search_max_results": { - "description": "Maximum number of web search results (default: 5)", - "type": "integer" - }, - "web_search_provider_id": { - "description": "WebSearchProviderEntity ID (resolved from agent config)", - "type": "string" - } - } - }, "github_com_Tencent_WeKnora_internal_types.AgentStep": { "type": "object", "properties": { @@ -11636,6 +12353,10 @@ const docTemplate = `{ "description": "Iteration number (0-indexed)", "type": "integer" }, + "reasoning_content": { + "description": "ReasoningContent stores the OpenAI-protocol reasoning_content emitted by the\nmodel in this round. Persisted on AgentStep so cross-turn replay can put it\nback on the assistant message — required by MiMo / DeepSeek V3.2+ thinking\nmode, ignored by providers that don't recognize the field.", + "type": "string" + }, "thought": { "description": "LLM's reasoning/thinking (Think phase)", "type": "string" @@ -11664,6 +12385,91 @@ const docTemplate = `{ "AnswerStrategyRandom" ] }, + "github_com_Tencent_WeKnora_internal_types.AuditAction": { + "type": "string", + "enum": [ + "rbac.member_added", + "rbac.member_removed", + "rbac.member_role_changed", + "rbac.member_left", + "rbac.access_denied", + "rbac.invitation_sent", + "rbac.invitation_accepted", + "rbac.invitation_declined", + "rbac.invitation_revoked", + "rbac.invitation_expired" + ], + "x-enum-varnames": [ + "AuditActionMemberAdded", + "AuditActionMemberRemoved", + "AuditActionMemberRoleChanged", + "AuditActionMemberLeft", + "AuditActionAccessDenied", + "AuditActionInvitationSent", + "AuditActionInvitationAccepted", + "AuditActionInvitationDeclined", + "AuditActionInvitationRevoked", + "AuditActionInvitationExpired" + ] + }, + "github_com_Tencent_WeKnora_internal_types.AuditLog": { + "type": "object", + "properties": { + "action": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.AuditAction" + }, + "actor_role": { + "type": "string" + }, + "actor_user_id": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "integer" + } + }, + "id": { + "type": "integer" + }, + "outcome": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.AuditOutcome" + }, + "request_method": { + "type": "string" + }, + "request_path": { + "type": "string" + }, + "target_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "target_user_id": { + "type": "string" + }, + "tenant_id": { + "type": "integer" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.AuditOutcome": { + "type": "string", + "enum": [ + "success", + "denied" + ], + "x-enum-varnames": [ + "AuditOutcomeSuccess", + "AuditOutcomeDenied" + ] + }, "github_com_Tencent_WeKnora_internal_types.COSEngineConfig": { "type": "object", "properties": { @@ -11851,76 +12657,6 @@ const docTemplate = `{ } } }, - "github_com_Tencent_WeKnora_internal_types.ConversationConfig": { - "type": "object", - "properties": { - "context_template": { - "description": "ContextTemplate is the prompt template for summarizing retrieval results", - "type": "string" - }, - "embedding_top_k": { - "type": "integer" - }, - "enable_query_expansion": { - "type": "boolean" - }, - "enable_rewrite": { - "type": "boolean" - }, - "fallback_prompt": { - "type": "string" - }, - "fallback_response": { - "type": "string" - }, - "fallback_strategy": { - "description": "Fallback strategy", - "type": "string" - }, - "keyword_threshold": { - "type": "number" - }, - "max_completion_tokens": { - "description": "MaxTokens is the maximum number of tokens to generate", - "type": "integer" - }, - "max_rounds": { - "description": "Retrieval \u0026 strategy parameters", - "type": "integer" - }, - "prompt": { - "description": "Prompt is the system prompt for normal mode", - "type": "string" - }, - "rerank_model_id": { - "type": "string" - }, - "rerank_threshold": { - "type": "number" - }, - "rerank_top_k": { - "type": "integer" - }, - "rewrite_prompt_system": { - "description": "Rewrite prompts", - "type": "string" - }, - "rewrite_prompt_user": { - "type": "string" - }, - "summary_model_id": { - "description": "Model configuration", - "type": "string" - }, - "temperature": { - "description": "Temperature controls the randomness of the model output", - "type": "number" - }, - "vector_threshold": { - "type": "number" - } - } - }, "github_com_Tencent_WeKnora_internal_types.CreateOrganizationRequest": { "type": "object", "required": [ @@ -11993,6 +12729,10 @@ const docTemplate = `{ "description": "ContextTemplateID references a template ID in prompt_templates/ YAML files.\nIf set and ContextTemplate is empty, the template content will be resolved at startup.", "type": "string" }, + "data_analysis_enabled": { + "description": "===== Data Analysis Settings =====\nWhether to run the legacy in-pipeline DuckDB SQL data-analysis stage when\nthe retrieved chunks include CSV/Excel files. This issues an extra LLM\ncall to generate a SQL query and is disabled by default because most\nquick-answer / RAG-style agents do not want the added latency.", + "type": "boolean" + }, "embedding_top_k": { "description": "===== Retrieval Strategy Settings (for both modes) =====\nEmbedding/Vector retrieval top K", "type": "integer" @@ -12087,6 +12827,10 @@ const docTemplate = `{ "description": "===== Multi-turn Conversation Settings =====\nWhether multi-turn conversation is enabled", "type": "boolean" }, + "query_understand_model_id": { + "description": "Dedicated chat model ID for the query-understanding (rewrite + intent) step.\nWhen empty, the main conversation ModelID is used as a fallback.", + "type": "string" + }, "rerank_model_id": { "description": "ReRank model ID for retrieval", "type": "string" @@ -12640,10 +13384,13 @@ const docTemplate = `{ "github_com_Tencent_WeKnora_internal_types.InviteMemberRequest": { "type": "object", "required": [ - "role", - "user_id" + "role" ], "properties": { + "representative_user_id": { + "description": "RepresentativeUserID identifies the user attached to the OTM row for\ndisplay/audit. Optional: when unset, the handler picks a stable default\n(the user from the legacy UserID field, or the tenant's owner).", + "type": "string" + }, "role": { "description": "Role to assign: admin/editor/viewer", "allOf": [ @@ -12652,8 +13399,12 @@ const docTemplate = `{ } ] }, + "tenant_id": { + "description": "TenantID is the tenant to enrol as an org member. Preferred field.", + "type": "integer" + }, "user_id": { - "description": "User ID to invite", + "description": "UserID is retained for backward compatibility. When set without\nTenantID, the handler resolves the user's TenantID and uses this\nuser as the representative.", "type": "string" } } @@ -12883,6 +13634,14 @@ const docTemplate = `{ "description": "Creation time of the knowledge base", "type": "string" }, + "creator_id": { + "description": "CreatorID records the user ID of whoever originally created the KB.\nUsed by the tenant-level RBAC middleware to let Contributors edit\ntheir own KBs without granting them access to everyone else's.\nNullable for backward compatibility with rows created before the\nRBAC migration backfilled the column to the tenant Owner.", + "type": "string" + }, + "creator_name": { + "description": "CreatorName 是 CreatorID 对应用户的展示名(username / email 等),\n仅在列表场景由 handler 批量回填,不落库;为空表示创建者无法解析(用户已删除、\nCreatorID 为空的老数据等)。前端用它在卡片来源徽章上做 mine vs tenant 的二分。", + "type": "string" + }, "deleted_at": { "description": "Deletion time of the knowledge base", "allOf": [ @@ -12936,7 +13695,7 @@ const docTemplate = `{ ] }, "is_pinned": { - "description": "Whether this knowledge base is pinned to the top of the list", + "description": "IsPinned and PinnedAt are computed per-caller from user_kb_pins\n(see migration 000050). They used to be stored on the row itself,\nwhich made pinning a tenant-wide ordering decision gated behind\nthe kb-edit RBAC guard. The columns are still present in legacy\nschemas for rollback safety but are no longer read or written by\nthe application — both fields are tagged ` + "`" + `gorm:\"-\"` + "`" + ` so GORM\nignores them on every CRUD call and the list handler stamps them\nafter enriching with the caller's pin set.", "type": "boolean" }, "is_processing": { @@ -12956,7 +13715,7 @@ const docTemplate = `{ "type": "string" }, "pinned_at": { - "description": "Time when the knowledge base was pinned (nil if not pinned)", + "description": "PinnedAt records when the current caller pinned this knowledge\nbase; nil when they have not.", "type": "string" }, "processing_count": { @@ -13256,6 +14015,21 @@ const docTemplate = `{ "github_com_Tencent_WeKnora_internal_types.LoginResponse": { "type": "object", "properties": { + "active_tenant": { + "description": "ActiveTenant is the tenant whose ID is encoded in the issued JWT;\nfuture requests are scoped to it until the client calls /auth/switch-tenant.\nDefaults to the user's home tenant on a fresh login.", + "allOf": [ + { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant" + } + ] + }, + "memberships": { + "description": "Memberships lists every tenant the user can authenticate into,\nalong with their role in each. Always populated (length 1 for users\nwho only belong to their home tenant) so frontends can render a\ntenant switcher without a follow-up request. Serialised without\nomitempty so the field is always present as a JSON array (possibly\nempty) — the \"always populated\" contract relies on the server side\nguaranteeing a non-nil slice.", + "type": "array", + "items": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.Membership" + } + }, "message": { "type": "string" }, @@ -13265,9 +14039,6 @@ const docTemplate = `{ "success": { "type": "boolean" }, - "tenant": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant" - }, "token": { "type": "string" }, @@ -13492,6 +14263,20 @@ const docTemplate = `{ "MatchTypeDataAnalysis" ] }, + "github_com_Tencent_WeKnora_internal_types.Membership": { + "type": "object", + "properties": { + "role": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole" + }, + "tenant_id": { + "type": "integer" + }, + "tenant_name": { + "type": "string" + } + } + }, "github_com_Tencent_WeKnora_internal_types.MentionedItem": { "type": "object", "properties": { @@ -13839,6 +14624,32 @@ const docTemplate = `{ "ModelTypeASR" ] }, + "github_com_Tencent_WeKnora_internal_types.OBSEngineConfig": { + "type": "object", + "properties": { + "access_key": { + "type": "string" + }, + "bucket_name": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "path_prefix": { + "type": "string" + }, + "region": { + "type": "string" + }, + "secret_key": { + "type": "string" + }, + "use_ssl": { + "type": "boolean" + } + } + }, "github_com_Tencent_WeKnora_internal_types.OIDCAuthURLResponse": { "type": "object", "properties": { @@ -13930,12 +14741,18 @@ const docTemplate = `{ "joined_at": { "type": "string" }, + "representative_user_id": { + "type": "string" + }, "role": { "type": "string" }, "tenant_id": { "type": "integer" }, + "tenant_name": { + "type": "string" + }, "user_id": { "type": "string" }, @@ -13995,6 +14812,10 @@ const docTemplate = `{ "owner_id": { "type": "string" }, + "owner_tenant_id": { + "description": "OwnerTenantID is the persisted owner tenant of the organization\n(Plan 3, migration 000046). Frontend uses this to identify the\n\"owner row\" in the tenant-keyed members list — comparing\nmember.tenant_id against owner_tenant_id is the post-Plan-3\nequivalent of the old member.user_id == owner_id check.", + "type": "integer" + }, "pending_join_request_count": { "description": "待审批加入申请数(仅管理员可见)", "type": "integer" @@ -14056,6 +14877,10 @@ const docTemplate = `{ "mineru_model": { "description": "MinerU 自建解析参数", "type": "string" + }, + "mineru_vlm_server_url": { + "description": "vLLM 服务器地址 (vlm-http-client / hybrid-http-client)", + "type": "string" } } }, @@ -14369,6 +15194,9 @@ const docTemplate = `{ "endpoint": { "type": "string" }, + "force_path_style": { + "type": "boolean" + }, "path_prefix": { "type": "string" }, @@ -14377,6 +15205,9 @@ const docTemplate = `{ }, "secret_key": { "type": "string" + }, + "use_ssl": { + "type": "boolean" } } }, @@ -14563,6 +15394,14 @@ const docTemplate = `{ "description": "IsPinned indicates whether the session is pinned in the list.", "type": "boolean" }, + "last_request_state": { + "description": "LastRequestState records the input-bar state used the last time this\nsession sent a question (agent, model, KB scope, web search, MCPs).\nPersisted on every successful POST to /knowledge-chat or /agent-chat so\nthat reopening the session can restore the original request context to\nthe chat UI. Stored in the legacy sessions.agent_config JSONB column to\navoid a new migration; the shape used today is ` + "`" + `SessionLastRequestState` + "`" + `.", + "allOf": [ + { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.SessionLastRequestState" + } + ] + }, "pinned_at": { "description": "PinnedAt records when the session was pinned; nil when not pinned.", "type": "string" @@ -14584,6 +15423,35 @@ const docTemplate = `{ } } }, + "github_com_Tencent_WeKnora_internal_types.SessionLastRequestState": { + "type": "object", + "properties": { + "agent_enabled": { + "type": "boolean" + }, + "agent_id": { + "type": "string" + }, + "knowledge_base_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "knowledge_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "model_id": { + "type": "string" + }, + "web_search_enabled": { + "type": "boolean" + } + } + }, "github_com_Tencent_WeKnora_internal_types.ShareKnowledgeBaseRequest": { "type": "object", "required": [ @@ -14603,25 +15471,44 @@ const docTemplate = `{ "type": "object", "properties": { "app_id": { + "description": "App ID (COS specific)", "type": "string" }, "bucket_name": { + "description": "Bucket Name", "type": "string" }, + "endpoint": { + "description": "Endpoint (S3 specific) - e.g., s3.amazonaws.com, oss-cn-hangzhou.aliyuncs.com", + "type": "string" + }, + "force_path_style": { + "description": "ForcePathStyle (S3 specific) - whether to use path-style URLs", + "type": "boolean" + }, "path_prefix": { + "description": "Path Prefix", "type": "string" }, "provider": { + "description": "Provider: \"cos\", \"minio\", \"s3\"", "type": "string" }, "region": { + "description": "Region", "type": "string" }, "secret_id": { + "description": "Secret ID (COS) / Access Key ID (S3, MinIO)", "type": "string" }, "secret_key": { + "description": "Secret Key (COS) / Secret Access Key (S3, MinIO)", "type": "string" + }, + "use_ssl": { + "description": "UseSSL (S3 specific) - whether to use HTTPS", + "type": "boolean" } } }, @@ -14632,7 +15519,7 @@ const docTemplate = `{ "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.COSEngineConfig" }, "default_provider": { - "description": "\"local\", \"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\"", + "description": "\"local\", \"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\", \"obs\"", "type": "string" }, "ks3": { @@ -14644,6 +15531,9 @@ const docTemplate = `{ "minio": { "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.MinIOEngineConfig" }, + "obs": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.OBSEngineConfig" + }, "oss": { "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.OSSEngineConfig" }, @@ -14659,7 +15549,7 @@ const docTemplate = `{ "type": "object", "properties": { "provider": { - "description": "\"local\", \"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\"", + "description": "\"local\", \"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\", \"obs\"", "type": "string" } } @@ -14787,14 +15677,6 @@ const docTemplate = `{ "github_com_Tencent_WeKnora_internal_types.Tenant": { "type": "object", "properties": { - "agent_config": { - "description": "Deprecated: AgentConfig is deprecated, use CustomAgent (builtin-smart-reasoning) config instead.\nThis field is kept for backward compatibility and will be removed in future versions.", - "allOf": [ - { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.AgentConfig" - } - ] - }, "api_key": { "description": "API key", "type": "string" @@ -14819,14 +15701,6 @@ const docTemplate = `{ } ] }, - "conversation_config": { - "description": "Deprecated: ConversationConfig is deprecated, use CustomAgent (builtin-quick-answer) config instead.\nThis field is kept for backward compatibility and will be removed in future versions.", - "allOf": [ - { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.ConversationConfig" - } - ] - }, "created_at": { "description": "Creation time", "type": "string" @@ -14917,6 +15791,21 @@ const docTemplate = `{ } } }, + "github_com_Tencent_WeKnora_internal_types.TenantRole": { + "type": "string", + "enum": [ + "owner", + "admin", + "contributor", + "viewer" + ], + "x-enum-varnames": [ + "TenantRoleOwner", + "TenantRoleAdmin", + "TenantRoleContributor", + "TenantRoleViewer" + ] + }, "github_com_Tencent_WeKnora_internal_types.ToolCall": { "type": "object", "properties": { @@ -15071,6 +15960,14 @@ const docTemplate = `{ "description": "Whether the user is active", "type": "boolean" }, + "preferences": { + "description": "Per-user UI/feature preferences (memory toggle, future knobs).\nStored as JSON (jsonb on Postgres, TEXT on SQLite) via the\ndriver.Valuer / sql.Scanner methods on UserPreferences.", + "allOf": [ + { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.UserPreferences" + } + ] + }, "tenant": { "description": "Association relationship, not stored in the database", "allOf": [ @@ -15093,6 +15990,19 @@ const docTemplate = `{ } } }, + "github_com_Tencent_WeKnora_internal_types.UserPreferences": { + "type": "object", + "properties": { + "enable_memory": { + "description": "EnableMemory mirrors the \"开启记忆功能\" switch in General Settings.\nnil = preference never set (treat as feature default = false)\n*false / *true = user explicitly set the toggle.", + "type": "boolean" + }, + "last_active_tenant_id": { + "description": "LastActiveTenantID remembers the last tenant the user actively\nswitched into, so a fresh login (new device, cleared browser, new\nrefresh token) lands them back in that workspace instead of always\nbouncing to their home tenant. Login / RefreshToken validate that\nthe tenant still exists and the user still has an active membership\n(or CanAccessAllTenants) before honouring this preference; an\ninvalid pointer is best-effort cleared and the user falls back to\nhome.\n\nnil = no preference (use user.TenantID, i.e. home)\n*0 = \"clear preference\" sentinel for the partial-update endpoint\n (UpdateUserPreferences turns this into nil). Otherwise treat\n a stored *0 the same as nil.\n*N = preferred tenant id.", + "type": "integer" + } + } + }, "github_com_Tencent_WeKnora_internal_types.VLMConfig": { "type": "object", "properties": { @@ -15751,6 +16661,17 @@ const docTemplate = `{ } } }, + "internal_handler.AddFavoriteRequest": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "internal_handler.BatchDeleteKnowledgeRequest": { "type": "object", "required": [ @@ -16338,6 +17259,10 @@ const docTemplate = `{ "interfaceType": { "type": "string" }, + "modelId": { + "description": "ModelID, when set, instructs the handler to substitute any missing\nsecrets (APIKey, AppSecret via ExtraConfig) from the stored model\nrecord before assembling the test client. This lets the \"Test\nconnection\" button work on existing models without making the\nfrontend reload — and ship — the plaintext API key. Other fields\n(BaseURL, ModelName, etc.) on this request still override the\nstored values, so a user can validate a new endpoint against the\nexisting credentials in one click.", + "type": "string" + }, "modelName": { "type": "string" }, @@ -16553,6 +17478,10 @@ const docTemplate = `{ "interfaceType": { "type": "string" }, + "modelId": { + "description": "ModelID, when set, instructs the handler to substitute any missing\nsecrets (APIKey, AppSecret via ExtraConfig) from the stored model\nrecord before assembling the test client. This lets the \"Test\nconnection\" button work on existing models without making the\nfrontend reload — and ship — the plaintext API key. Other fields\n(BaseURL, ModelName, etc.) on this request still override the\nstored values, so a user can validate a new endpoint against the\nexisting credentials in one click.", + "type": "string" + }, "modelName": { "type": "string" }, @@ -16604,11 +17533,14 @@ const docTemplate = `{ "minio": { "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.MinIOEngineConfig" }, + "obs": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.OBSEngineConfig" + }, "oss": { "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.OSSEngineConfig" }, "provider": { - "description": "\"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\"", + "description": "\"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\", \"obs\"", "type": "string" }, "s3": { @@ -16815,6 +17747,21 @@ const docTemplate = `{ } } }, + "internal_handler.addMemberRequest": { + "type": "object", + "required": [ + "email", + "role" + ], + "properties": { + "email": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole" + } + } + }, "internal_handler.addSimilarQuestionsRequest": { "type": "object", "required": [ @@ -16830,6 +17777,58 @@ const docTemplate = `{ } } }, + "internal_handler.auditLogListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.AuditLog" + } + }, + "next_cursor": { + "type": "integer" + }, + "success": { + "type": "boolean" + } + } + }, + "internal_handler.createInvitationRequest": { + "type": "object", + "required": [ + "email", + "role" + ], + "properties": { + "email": { + "type": "string" + }, + "message": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole" + } + } + }, + "internal_handler.createTenantRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": "string", + "maxLength": 512 + }, + "name": { + "type": "string", + "maxLength": 128, + "minLength": 1 + } + } + }, "internal_handler.updateLastFAQImportResultDisplayStatusRequest": { "type": "object", "required": [ @@ -16845,6 +17844,29 @@ const docTemplate = `{ } } }, + "internal_handler.updateMemberRoleRequest": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole" + } + } + }, + "internal_handler.updateMyPreferencesRequest": { + "type": "object", + "properties": { + "enable_memory": { + "type": "boolean" + }, + "last_active_tenant_id": { + "description": "LastActiveTenantID lets the SPA persist \"after a fresh login,\ndrop me back into this workspace\" across devices. Send a positive\ntenant id to set / replace, or 0 to clear. Membership is validated\nat next login, not here. Nil = field omitted from the PATCH and\nstays untouched.", + "type": "integer" + } + } + }, "internal_handler_session.AttachmentUpload": { "type": "object", "properties": { @@ -16892,7 +17914,7 @@ const docTemplate = `{ "type": "boolean" }, "enable_memory": { - "description": "Whether memory feature is enabled for this request", + "description": "EnableMemory is the per-request override for the memory feature.\nPointer + omitempty so the request can distinguish three states:\n nil = client did not specify; backend falls back to the calling\n user's persisted preference (user.preferences.enable_memory),\n defaulting to false if that's also unset. This is the path\n used by the normal logged-in chat UI now that the toggle is\n stored server-side per user.\n *true / *false = explicit override. Embedded mode forces *false so a\n user's personal memory setting doesn't leak into a widget\n context; older clients that still send a literal bool also\n land here (back-compat).", "type": "boolean" }, "images": { diff --git a/docs/images/architecture.png b/docs/images/architecture.png index dfb06f9a..181e7f8b 100644 Binary files a/docs/images/architecture.png and b/docs/images/architecture.png differ diff --git a/docs/images/rbac-create-workspace.png b/docs/images/rbac-create-workspace.png new file mode 100644 index 00000000..a4000496 Binary files /dev/null and b/docs/images/rbac-create-workspace.png differ diff --git a/docs/images/rbac-member-management.png b/docs/images/rbac-member-management.png new file mode 100644 index 00000000..c82cbbe5 Binary files /dev/null and b/docs/images/rbac-member-management.png differ diff --git a/docs/images/rbac-pending-invitation.png b/docs/images/rbac-pending-invitation.png new file mode 100644 index 00000000..97477d37 Binary files /dev/null and b/docs/images/rbac-pending-invitation.png differ diff --git a/docs/images/rbac-workspace-switcher.png b/docs/images/rbac-workspace-switcher.png new file mode 100644 index 00000000..24fdaa68 Binary files /dev/null and b/docs/images/rbac-workspace-switcher.png differ diff --git a/docs/swagger.json b/docs/swagger.json index e7121f1f..cc48de52 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1935,6 +1935,30 @@ } } }, + "/auth/config": { + "get": { + "description": "返回当前部署的注册模式等公开认证配置,供前端决定是否展示注册入口", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "获取认证配置", + "responses": { + "200": { + "description": "认证配置", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/auth/login": { "post": { "description": "用户登录并获取访问令牌", @@ -2045,6 +2069,58 @@ } } }, + "/auth/me/preferences": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "按 PATCH 语义合并用户偏好(仅覆盖请求体里出现的字段,其余字段保持不变),\n数据存放在 users.preferences (JSON),跨设备/浏览器自动同步。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "更新当前用户的个性化设置", + "parameters": [ + { + "description": "Preferences patch", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.updateMyPreferencesRequest" + } + } + ], + "responses": { + "200": { + "description": "更新后的偏好", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "请求参数错误", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "401": { + "description": "未授权", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, "/auth/oidc/callback": { "get": { "description": "接收OIDC provider回调并由后端完成code交换,随后重定向回前端登录页", @@ -2244,6 +2320,65 @@ } } }, + "/auth/switch-tenant": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "为当前用户在目标租户重新签发访问令牌;要求该用户在目标租户存在 active 成员关系", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "认证" + ], + "summary": "切换激活租户", + "parameters": [ + { + "description": "切换请求", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string" + }, + "tenant_id": { + "type": "integer" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.LoginResponse" + } + }, + "400": { + "description": "参数错误", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "403": { + "description": "无该租户成员关系", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, "/auth/validate": { "get": { "security": [ @@ -6935,6 +7070,122 @@ } } }, + "/mcp-services/{id}/credentials": { + "put": { + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "为指定字段写入新凭据;省略的字段保留原值;空字符串视为 no-op(如需删除请用 DELETE)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "MCP服务" + ], + "summary": "设置 MCP 服务凭据", + "parameters": [ + { + "type": "string", + "description": "MCP 服务 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "{api_key?: string, token?: string}", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "additionalProperties": true + } + } + ], + "responses": { + "200": { + "description": "写入后的凭据状态", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "400": { + "description": "请求参数错误", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "404": { + "description": "服务不存在", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, + "/mcp-services/{id}/credentials/{field}": { + "delete": { + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "删除指定字段的存储凭据;删除已为空的字段是幂等的", + "produces": [ + "application/json" + ], + "tags": [ + "MCP服务" + ], + "summary": "移除 MCP 服务的单个凭据字段", + "parameters": [ + { + "type": "string", + "description": "MCP 服务 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "字段名(api_key | token)", + "name": "field", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "字段名非法", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + }, + "404": { + "description": "服务不存在", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, "/mcp-services/{id}/resources": { "get": { "security": [ @@ -7146,6 +7397,136 @@ } } }, + "/me/invitations": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "返回当前登录用户的待接受邀请(默认仅 pending),用于头像入口和 /invitations 收件箱页。", + "produces": [ + "application/json" + ], + "tags": [ + "我的邀请" + ], + "summary": "列出我的待接受邀请", + "parameters": [ + { + "type": "boolean", + "description": "是否包含已处理 / 已过期等终止态行(默认 false)", + "name": "include_terminal", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/me/invitations/pending-count": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "轻量级 endpoint,返回当前登录用户的 pending 邀请数,用于头像旁的角标轮询。", + "produces": [ + "application/json" + ], + "tags": [ + "我的邀请" + ], + "summary": "获取我的待处理邀请数", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/me/invitations/{inv_id}/accept": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "当前登录用户接受一条 pending 邀请;服务端会同时写入 tenant_members 行。", + "produces": [ + "application/json" + ], + "tags": [ + "我的邀请" + ], + "summary": "接受邀请", + "parameters": [ + { + "type": "string", + "description": "邀请 ID", + "name": "inv_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/me/invitations/{inv_id}/decline": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "当前登录用户拒绝一条 pending 邀请;不创建 tenant_members 行。", + "produces": [ + "application/json" + ], + "tags": [ + "我的邀请" + ], + "summary": "拒绝邀请", + "parameters": [ + { + "type": "string", + "description": "邀请 ID", + "name": "inv_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/messages/chat-history-stats": { "get": { "security": [ @@ -7655,7 +8036,7 @@ "Bearer": [] } ], - "description": "获取当前用户所属的所有组织,并附带各空间内知识库/智能体数量", + "description": "获取当前租户所属的所有组织,并附带各空间内知识库/智能体数量", "produces": [ "application/json" ], @@ -8355,7 +8736,7 @@ "Bearer": [] } ], - "description": "获取组织的所有成员", + "description": "获取组织的所有成员(按租户)", "produces": [ "application/json" ], @@ -8382,14 +8763,14 @@ } } }, - "/organizations/{id}/members/{user_id}": { + "/organizations/{id}/members/{tenant_id}": { "put": { "security": [ { "Bearer": [] } ], - "description": "更新组织成员的角色(需要管理员权限)", + "description": "更新组织成员(租户)的角色(需要管理员权限)", "consumes": [ "application/json" ], @@ -8410,8 +8791,8 @@ }, { "type": "string", - "description": "用户ID", - "name": "user_id", + "description": "成员租户ID", + "name": "tenant_id", "in": "path", "required": true }, @@ -8447,7 +8828,7 @@ "Bearer": [] } ], - "description": "从组织中移除成员(需要管理员权限)", + "description": "从组织中移除成员租户(需要管理员权限)", "tags": [ "组织管理" ], @@ -8462,8 +8843,8 @@ }, { "type": "string", - "description": "用户ID", - "name": "user_id", + "description": "成员租户ID", + "name": "tenant_id", "in": "path", "required": true } @@ -8538,21 +8919,21 @@ } } }, - "/organizations/{id}/search-users": { + "/organizations/{id}/search-tenants": { "get": { "security": [ { "Bearer": [] } ], - "description": "搜索用户(排除已有成员)用于邀请加入组织", + "description": "搜索租户(排除已加入的租户)用于邀请加入组织;按租户去重,附带代表用户", "produces": [ "application/json" ], "tags": [ "组织管理" ], - "summary": "搜索可邀请的用户", + "summary": "搜索可邀请的租户", "parameters": [ { "type": "string", @@ -8563,7 +8944,7 @@ }, { "type": "string", - "description": "搜索关键词(用户名或邮箱)", + "description": "搜索关键词(租户名、用户名或邮箱)", "name": "q", "in": "query", "required": true @@ -8593,6 +8974,12 @@ } } }, + "/organizations/{id}/search-users": { + "get": { + "deprecated": true, + "responses": {} + } + }, "/organizations/{id}/shared-agents": { "get": { "security": [ @@ -9773,7 +10160,7 @@ "Bearer": [] } ], - "description": "创建新的租户", + "description": "创建新的租户。任意已登录用户均可调用以建立自己的新工作区,\n调用方会被自动设为该租户的 Owner。跨租户超管仍可像以前一样\n通过本接口创建任意租户。", "consumes": [ "application/json" ], @@ -9791,7 +10178,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant" + "$ref": "#/definitions/internal_handler.createTenantRequest" } } ], @@ -9847,82 +10234,6 @@ } } }, - "/tenants/kv/agent-config": { - "get": { - "security": [ - { - "Bearer": [] - }, - { - "ApiKeyAuth": [] - } - ], - "description": "获取租户的全局Agent配置(默认应用于所有会话)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "租户管理" - ], - "summary": "获取租户Agent配置", - "responses": { - "200": { - "description": "Agent配置", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "400": { - "description": "请求参数错误", - "schema": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" - } - } - } - } - }, - "/tenants/kv/conversation-config": { - "get": { - "security": [ - { - "Bearer": [] - }, - { - "ApiKeyAuth": [] - } - ], - "description": "获取租户的全局对话配置(默认应用于普通模式会话)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "租户管理" - ], - "summary": "获取租户对话配置", - "responses": { - "200": { - "description": "对话配置", - "schema": { - "type": "object", - "additionalProperties": true - } - }, - "400": { - "description": "请求参数错误", - "schema": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" - } - } - } - } - }, "/tenants/kv/prompt-templates": { "get": { "security": [ @@ -10009,7 +10320,7 @@ "ApiKeyAuth": [] } ], - "description": "获取租户级别的KV配置(支持agent-config、web-search-config、conversation-config)", + "description": "获取租户级别的KV配置(支持web-search-config、prompt-templates、parser-engine-config、storage-engine-config、chat-history-config、retrieval-config)", "consumes": [ "application/json" ], @@ -10054,7 +10365,7 @@ "ApiKeyAuth": [] } ], - "description": "更新租户级别的KV配置(支持agent-config、web-search-config、conversation-config)", + "description": "更新租户级别的KV配置(支持web-search-config、parser-engine-config、storage-engine-config、chat-history-config、retrieval-config)", "consumes": [ "application/json" ], @@ -10362,6 +10673,535 @@ } } }, + "/tenants/{id}/audit-log": { + "get": { + "security": [ + { + "Bearer": [] + }, + { + "ApiKeyAuth": [] + } + ], + "description": "返回该租户最近的审计事件,按 id 倒序。游标分页:将上次响应的 next_cursor 作为下一次请求的 after_id。", + "produces": [ + "application/json" + ], + "tags": [ + "审计日志" + ], + "summary": "获取租户审计日志", + "parameters": [ + { + "type": "string", + "description": "租户ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "游标:返回 id 小于此值的记录(默认从最新开始)", + "name": "after_id", + "in": "query" + }, + { + "type": "integer", + "description": "页大小,1-100,默认 50", + "name": "limit", + "in": "query" + }, + { + "type": "string", + "description": "按 action 精确过滤(如 rbac.member_added / rbac.access_denied)", + "name": "action", + "in": "query" + }, + { + "type": "string", + "description": "按 outcome 精确过滤(success / denied)", + "name": "outcome", + "in": "query" + }, + { + "type": "string", + "description": "按 actor_user_id 精确过滤", + "name": "actor", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/internal_handler.auditLogListResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError" + } + } + } + } + }, + "/tenants/{id}/invitations": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "按 tenant 列出待接受 / 历史邀请。query include_terminal=true 时附带 accepted/declined/revoked/expired。", + "produces": [ + "application/json" + ], + "tags": [ + "租户邀请" + ], + "summary": "列出租户邀请", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "是否包含终止态行(默认 false)", + "name": "include_terminal", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "页码(从 1 起)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "每页数量", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Owner 通过邮箱邀请已注册用户加入当前租户;被邀请人需要在 /me/invitations 接受后才会成为成员。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "租户邀请" + ], + "summary": "发出租户邀请", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "邀请请求", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.createInvitationRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/tenants/{id}/invitations/{inv_id}": { + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Owner 取消一条还在 pending 的邀请;已 accepted/declined/revoked/expired 的行不可再撤销。", + "produces": [ + "application/json" + ], + "tags": [ + "租户邀请" + ], + "summary": "撤销待接受邀请", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "邀请 ID", + "name": "inv_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/tenants/{id}/leave": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "调用方主动退出当前租户。等价于以自己的 user_id 调 RemoveMember,", + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "退出当前租户", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/tenants/{id}/members": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "分页返回当前租户内 active 成员(含每位成员的角色、邮箱、头像);支持 q 按邮箱/用户名筛选", + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "列出租户成员", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "按邮箱/用户名模糊筛选", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "default": 1, + "description": "页码(从 1 起)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 20, + "description": "每页数量(最大 100)", + "name": "page_size", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "直接添加租户成员(直加路径)", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "邀请请求", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.addMemberRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/tenants/{id}/members/{user_id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Owner 修改某位成员在当前租户内的角色;不能将最后一位 Owner 降级", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "修改租户成员角色", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "用户 ID", + "name": "user_id", + "in": "path", + "required": true + }, + { + "description": "目标角色", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.updateMemberRoleRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "Owner 将某位成员从当前租户中移除(软删除 tenant_members 行);不能移除最后一位 Owner", + "produces": [ + "application/json" + ], + "tags": [ + "租户成员" + ], + "summary": "移除租户成员", + "parameters": [ + { + "type": "string", + "description": "租户 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "用户 ID", + "name": "user_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/user/favorites": { + "get": { + "description": "Lists this user's starred resources in the current tenant for a given type", + "tags": [ + "User" + ], + "summary": "List my favorites", + "parameters": [ + { + "type": "string", + "description": "Resource type (kb | agent)", + "name": "type", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "tags": [ + "User" + ], + "summary": "Star a resource", + "parameters": [ + { + "description": "Type + id", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/internal_handler.AddFavoriteRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/user/favorites/{type}/{id}": { + "delete": { + "tags": [ + "User" + ], + "summary": "Unstar a resource", + "parameters": [ + { + "type": "string", + "description": "Resource type", + "name": "type", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Resource id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "/vector-stores": { "get": { "security": [ @@ -11359,7 +12199,9 @@ 2100, 2101, 2102, - 2103 + 2103, + 2200, + 2201 ], "x-enum-varnames": [ "ErrBadRequest", @@ -11381,7 +12223,9 @@ "ErrAgentMissingThinkingModel", "ErrAgentMissingAllowedTools", "ErrAgentInvalidMaxIterations", - "ErrAgentInvalidTemperature" + "ErrAgentInvalidTemperature", + "ErrVectorStoreBindingInvalid", + "ErrVectorStoreUnavailable" ] }, "github_com_Tencent_WeKnora_internal_infrastructure_chunker.DocProfile": { @@ -11495,133 +12339,6 @@ } } }, - "github_com_Tencent_WeKnora_internal_types.AgentConfig": { - "type": "object", - "properties": { - "allowed_skills": { - "description": "Skill names whitelist (empty = allow all)", - "type": "array", - "items": { - "type": "string" - } - }, - "allowed_tools": { - "description": "List of allowed tool names", - "type": "array", - "items": { - "type": "string" - } - }, - "history_turns": { - "description": "Number of history turns to keep in context", - "type": "integer" - }, - "knowledge_bases": { - "description": "Accessible knowledge base IDs", - "type": "array", - "items": { - "type": "string" - } - }, - "knowledge_ids": { - "description": "Accessible knowledge IDs (individual documents)", - "type": "array", - "items": { - "type": "string" - } - }, - "llm_call_timeout": { - "description": "LLM call timeout in seconds (default: 120). Controls the maximum time for a single LLM call.", - "type": "integer" - }, - "max_context_tokens": { - "description": "Maximum context window tokens for the agent (default: 200000).\nThe agent compresses older messages to stay within this limit,\npreserving tool_call/tool_result pairs.", - "type": "integer" - }, - "max_iterations": { - "description": "Maximum number of ReAct iterations", - "type": "integer" - }, - "max_tool_output_chars": { - "description": "Maximum character length for tool output (default: 16000).\nOutputs exceeding this limit are truncated with head + tail preservation.", - "type": "integer" - }, - "mcp_selection_mode": { - "description": "MCP service selection", - "type": "string" - }, - "mcp_services": { - "description": "Selected MCP service IDs (when mode is \"selected\")", - "type": "array", - "items": { - "type": "string" - } - }, - "multi_turn_enabled": { - "description": "Whether multi-turn conversation is enabled", - "type": "boolean" - }, - "parallel_tool_calls": { - "description": "Whether to execute independent tool calls in parallel (default: false).\nWhen enabled and the LLM returns multiple tool calls, they run concurrently via errgroup.", - "type": "boolean" - }, - "retain_retrieval_history": { - "description": "Whether to retain retrieval history (like wiki_read_page results) across turns (default: false)", - "type": "boolean" - }, - "retrieve_kb_only_when_mentioned": { - "description": "Whether to retrieve knowledge base only when explicitly mentioned with @ (default: false)", - "type": "boolean" - }, - "skill_dirs": { - "description": "Directories to search for skills", - "type": "array", - "items": { - "type": "string" - } - }, - "skills_enabled": { - "description": "Skills configuration (Progressive Disclosure pattern)", - "type": "boolean" - }, - "system_prompt": { - "description": "Unified system prompt (uses web_search_status placeholder for dynamic behavior)", - "type": "string" - }, - "system_prompt_web_disabled": { - "description": "Deprecated: Custom prompt when web search is disabled", - "type": "string" - }, - "system_prompt_web_enabled": { - "description": "Deprecated: Use SystemPrompt instead. Kept for backward compatibility during migration.", - "type": "string" - }, - "temperature": { - "description": "LLM temperature for agent", - "type": "number" - }, - "thinking": { - "description": "Whether to enable thinking mode (for models that support extended thinking)", - "type": "boolean" - }, - "use_custom_system_prompt": { - "description": "Whether to use custom system prompt instead of default", - "type": "boolean" - }, - "web_search_enabled": { - "description": "Whether web search tool is enabled", - "type": "boolean" - }, - "web_search_max_results": { - "description": "Maximum number of web search results (default: 5)", - "type": "integer" - }, - "web_search_provider_id": { - "description": "WebSearchProviderEntity ID (resolved from agent config)", - "type": "string" - } - } - }, "github_com_Tencent_WeKnora_internal_types.AgentStep": { "type": "object", "properties": { @@ -11629,6 +12346,10 @@ "description": "Iteration number (0-indexed)", "type": "integer" }, + "reasoning_content": { + "description": "ReasoningContent stores the OpenAI-protocol reasoning_content emitted by the\nmodel in this round. Persisted on AgentStep so cross-turn replay can put it\nback on the assistant message — required by MiMo / DeepSeek V3.2+ thinking\nmode, ignored by providers that don't recognize the field.", + "type": "string" + }, "thought": { "description": "LLM's reasoning/thinking (Think phase)", "type": "string" @@ -11657,6 +12378,91 @@ "AnswerStrategyRandom" ] }, + "github_com_Tencent_WeKnora_internal_types.AuditAction": { + "type": "string", + "enum": [ + "rbac.member_added", + "rbac.member_removed", + "rbac.member_role_changed", + "rbac.member_left", + "rbac.access_denied", + "rbac.invitation_sent", + "rbac.invitation_accepted", + "rbac.invitation_declined", + "rbac.invitation_revoked", + "rbac.invitation_expired" + ], + "x-enum-varnames": [ + "AuditActionMemberAdded", + "AuditActionMemberRemoved", + "AuditActionMemberRoleChanged", + "AuditActionMemberLeft", + "AuditActionAccessDenied", + "AuditActionInvitationSent", + "AuditActionInvitationAccepted", + "AuditActionInvitationDeclined", + "AuditActionInvitationRevoked", + "AuditActionInvitationExpired" + ] + }, + "github_com_Tencent_WeKnora_internal_types.AuditLog": { + "type": "object", + "properties": { + "action": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.AuditAction" + }, + "actor_role": { + "type": "string" + }, + "actor_user_id": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "integer" + } + }, + "id": { + "type": "integer" + }, + "outcome": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.AuditOutcome" + }, + "request_method": { + "type": "string" + }, + "request_path": { + "type": "string" + }, + "target_id": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "target_user_id": { + "type": "string" + }, + "tenant_id": { + "type": "integer" + } + } + }, + "github_com_Tencent_WeKnora_internal_types.AuditOutcome": { + "type": "string", + "enum": [ + "success", + "denied" + ], + "x-enum-varnames": [ + "AuditOutcomeSuccess", + "AuditOutcomeDenied" + ] + }, "github_com_Tencent_WeKnora_internal_types.COSEngineConfig": { "type": "object", "properties": { @@ -11844,76 +12650,6 @@ } } }, - "github_com_Tencent_WeKnora_internal_types.ConversationConfig": { - "type": "object", - "properties": { - "context_template": { - "description": "ContextTemplate is the prompt template for summarizing retrieval results", - "type": "string" - }, - "embedding_top_k": { - "type": "integer" - }, - "enable_query_expansion": { - "type": "boolean" - }, - "enable_rewrite": { - "type": "boolean" - }, - "fallback_prompt": { - "type": "string" - }, - "fallback_response": { - "type": "string" - }, - "fallback_strategy": { - "description": "Fallback strategy", - "type": "string" - }, - "keyword_threshold": { - "type": "number" - }, - "max_completion_tokens": { - "description": "MaxTokens is the maximum number of tokens to generate", - "type": "integer" - }, - "max_rounds": { - "description": "Retrieval \u0026 strategy parameters", - "type": "integer" - }, - "prompt": { - "description": "Prompt is the system prompt for normal mode", - "type": "string" - }, - "rerank_model_id": { - "type": "string" - }, - "rerank_threshold": { - "type": "number" - }, - "rerank_top_k": { - "type": "integer" - }, - "rewrite_prompt_system": { - "description": "Rewrite prompts", - "type": "string" - }, - "rewrite_prompt_user": { - "type": "string" - }, - "summary_model_id": { - "description": "Model configuration", - "type": "string" - }, - "temperature": { - "description": "Temperature controls the randomness of the model output", - "type": "number" - }, - "vector_threshold": { - "type": "number" - } - } - }, "github_com_Tencent_WeKnora_internal_types.CreateOrganizationRequest": { "type": "object", "required": [ @@ -11986,6 +12722,10 @@ "description": "ContextTemplateID references a template ID in prompt_templates/ YAML files.\nIf set and ContextTemplate is empty, the template content will be resolved at startup.", "type": "string" }, + "data_analysis_enabled": { + "description": "===== Data Analysis Settings =====\nWhether to run the legacy in-pipeline DuckDB SQL data-analysis stage when\nthe retrieved chunks include CSV/Excel files. This issues an extra LLM\ncall to generate a SQL query and is disabled by default because most\nquick-answer / RAG-style agents do not want the added latency.", + "type": "boolean" + }, "embedding_top_k": { "description": "===== Retrieval Strategy Settings (for both modes) =====\nEmbedding/Vector retrieval top K", "type": "integer" @@ -12080,6 +12820,10 @@ "description": "===== Multi-turn Conversation Settings =====\nWhether multi-turn conversation is enabled", "type": "boolean" }, + "query_understand_model_id": { + "description": "Dedicated chat model ID for the query-understanding (rewrite + intent) step.\nWhen empty, the main conversation ModelID is used as a fallback.", + "type": "string" + }, "rerank_model_id": { "description": "ReRank model ID for retrieval", "type": "string" @@ -12633,10 +13377,13 @@ "github_com_Tencent_WeKnora_internal_types.InviteMemberRequest": { "type": "object", "required": [ - "role", - "user_id" + "role" ], "properties": { + "representative_user_id": { + "description": "RepresentativeUserID identifies the user attached to the OTM row for\ndisplay/audit. Optional: when unset, the handler picks a stable default\n(the user from the legacy UserID field, or the tenant's owner).", + "type": "string" + }, "role": { "description": "Role to assign: admin/editor/viewer", "allOf": [ @@ -12645,8 +13392,12 @@ } ] }, + "tenant_id": { + "description": "TenantID is the tenant to enrol as an org member. Preferred field.", + "type": "integer" + }, "user_id": { - "description": "User ID to invite", + "description": "UserID is retained for backward compatibility. When set without\nTenantID, the handler resolves the user's TenantID and uses this\nuser as the representative.", "type": "string" } } @@ -12876,6 +13627,14 @@ "description": "Creation time of the knowledge base", "type": "string" }, + "creator_id": { + "description": "CreatorID records the user ID of whoever originally created the KB.\nUsed by the tenant-level RBAC middleware to let Contributors edit\ntheir own KBs without granting them access to everyone else's.\nNullable for backward compatibility with rows created before the\nRBAC migration backfilled the column to the tenant Owner.", + "type": "string" + }, + "creator_name": { + "description": "CreatorName 是 CreatorID 对应用户的展示名(username / email 等),\n仅在列表场景由 handler 批量回填,不落库;为空表示创建者无法解析(用户已删除、\nCreatorID 为空的老数据等)。前端用它在卡片来源徽章上做 mine vs tenant 的二分。", + "type": "string" + }, "deleted_at": { "description": "Deletion time of the knowledge base", "allOf": [ @@ -12929,7 +13688,7 @@ ] }, "is_pinned": { - "description": "Whether this knowledge base is pinned to the top of the list", + "description": "IsPinned and PinnedAt are computed per-caller from user_kb_pins\n(see migration 000050). They used to be stored on the row itself,\nwhich made pinning a tenant-wide ordering decision gated behind\nthe kb-edit RBAC guard. The columns are still present in legacy\nschemas for rollback safety but are no longer read or written by\nthe application — both fields are tagged `gorm:\"-\"` so GORM\nignores them on every CRUD call and the list handler stamps them\nafter enriching with the caller's pin set.", "type": "boolean" }, "is_processing": { @@ -12949,7 +13708,7 @@ "type": "string" }, "pinned_at": { - "description": "Time when the knowledge base was pinned (nil if not pinned)", + "description": "PinnedAt records when the current caller pinned this knowledge\nbase; nil when they have not.", "type": "string" }, "processing_count": { @@ -13249,6 +14008,21 @@ "github_com_Tencent_WeKnora_internal_types.LoginResponse": { "type": "object", "properties": { + "active_tenant": { + "description": "ActiveTenant is the tenant whose ID is encoded in the issued JWT;\nfuture requests are scoped to it until the client calls /auth/switch-tenant.\nDefaults to the user's home tenant on a fresh login.", + "allOf": [ + { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant" + } + ] + }, + "memberships": { + "description": "Memberships lists every tenant the user can authenticate into,\nalong with their role in each. Always populated (length 1 for users\nwho only belong to their home tenant) so frontends can render a\ntenant switcher without a follow-up request. Serialised without\nomitempty so the field is always present as a JSON array (possibly\nempty) — the \"always populated\" contract relies on the server side\nguaranteeing a non-nil slice.", + "type": "array", + "items": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.Membership" + } + }, "message": { "type": "string" }, @@ -13258,9 +14032,6 @@ "success": { "type": "boolean" }, - "tenant": { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant" - }, "token": { "type": "string" }, @@ -13485,6 +14256,20 @@ "MatchTypeDataAnalysis" ] }, + "github_com_Tencent_WeKnora_internal_types.Membership": { + "type": "object", + "properties": { + "role": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole" + }, + "tenant_id": { + "type": "integer" + }, + "tenant_name": { + "type": "string" + } + } + }, "github_com_Tencent_WeKnora_internal_types.MentionedItem": { "type": "object", "properties": { @@ -13832,6 +14617,32 @@ "ModelTypeASR" ] }, + "github_com_Tencent_WeKnora_internal_types.OBSEngineConfig": { + "type": "object", + "properties": { + "access_key": { + "type": "string" + }, + "bucket_name": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "path_prefix": { + "type": "string" + }, + "region": { + "type": "string" + }, + "secret_key": { + "type": "string" + }, + "use_ssl": { + "type": "boolean" + } + } + }, "github_com_Tencent_WeKnora_internal_types.OIDCAuthURLResponse": { "type": "object", "properties": { @@ -13923,12 +14734,18 @@ "joined_at": { "type": "string" }, + "representative_user_id": { + "type": "string" + }, "role": { "type": "string" }, "tenant_id": { "type": "integer" }, + "tenant_name": { + "type": "string" + }, "user_id": { "type": "string" }, @@ -13988,6 +14805,10 @@ "owner_id": { "type": "string" }, + "owner_tenant_id": { + "description": "OwnerTenantID is the persisted owner tenant of the organization\n(Plan 3, migration 000046). Frontend uses this to identify the\n\"owner row\" in the tenant-keyed members list — comparing\nmember.tenant_id against owner_tenant_id is the post-Plan-3\nequivalent of the old member.user_id == owner_id check.", + "type": "integer" + }, "pending_join_request_count": { "description": "待审批加入申请数(仅管理员可见)", "type": "integer" @@ -14049,6 +14870,10 @@ "mineru_model": { "description": "MinerU 自建解析参数", "type": "string" + }, + "mineru_vlm_server_url": { + "description": "vLLM 服务器地址 (vlm-http-client / hybrid-http-client)", + "type": "string" } } }, @@ -14362,6 +15187,9 @@ "endpoint": { "type": "string" }, + "force_path_style": { + "type": "boolean" + }, "path_prefix": { "type": "string" }, @@ -14370,6 +15198,9 @@ }, "secret_key": { "type": "string" + }, + "use_ssl": { + "type": "boolean" } } }, @@ -14556,6 +15387,14 @@ "description": "IsPinned indicates whether the session is pinned in the list.", "type": "boolean" }, + "last_request_state": { + "description": "LastRequestState records the input-bar state used the last time this\nsession sent a question (agent, model, KB scope, web search, MCPs).\nPersisted on every successful POST to /knowledge-chat or /agent-chat so\nthat reopening the session can restore the original request context to\nthe chat UI. Stored in the legacy sessions.agent_config JSONB column to\navoid a new migration; the shape used today is `SessionLastRequestState`.", + "allOf": [ + { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.SessionLastRequestState" + } + ] + }, "pinned_at": { "description": "PinnedAt records when the session was pinned; nil when not pinned.", "type": "string" @@ -14577,6 +15416,35 @@ } } }, + "github_com_Tencent_WeKnora_internal_types.SessionLastRequestState": { + "type": "object", + "properties": { + "agent_enabled": { + "type": "boolean" + }, + "agent_id": { + "type": "string" + }, + "knowledge_base_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "knowledge_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "model_id": { + "type": "string" + }, + "web_search_enabled": { + "type": "boolean" + } + } + }, "github_com_Tencent_WeKnora_internal_types.ShareKnowledgeBaseRequest": { "type": "object", "required": [ @@ -14596,25 +15464,44 @@ "type": "object", "properties": { "app_id": { + "description": "App ID (COS specific)", "type": "string" }, "bucket_name": { + "description": "Bucket Name", "type": "string" }, + "endpoint": { + "description": "Endpoint (S3 specific) - e.g., s3.amazonaws.com, oss-cn-hangzhou.aliyuncs.com", + "type": "string" + }, + "force_path_style": { + "description": "ForcePathStyle (S3 specific) - whether to use path-style URLs", + "type": "boolean" + }, "path_prefix": { + "description": "Path Prefix", "type": "string" }, "provider": { + "description": "Provider: \"cos\", \"minio\", \"s3\"", "type": "string" }, "region": { + "description": "Region", "type": "string" }, "secret_id": { + "description": "Secret ID (COS) / Access Key ID (S3, MinIO)", "type": "string" }, "secret_key": { + "description": "Secret Key (COS) / Secret Access Key (S3, MinIO)", "type": "string" + }, + "use_ssl": { + "description": "UseSSL (S3 specific) - whether to use HTTPS", + "type": "boolean" } } }, @@ -14625,7 +15512,7 @@ "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.COSEngineConfig" }, "default_provider": { - "description": "\"local\", \"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\"", + "description": "\"local\", \"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\", \"obs\"", "type": "string" }, "ks3": { @@ -14637,6 +15524,9 @@ "minio": { "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.MinIOEngineConfig" }, + "obs": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.OBSEngineConfig" + }, "oss": { "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.OSSEngineConfig" }, @@ -14652,7 +15542,7 @@ "type": "object", "properties": { "provider": { - "description": "\"local\", \"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\"", + "description": "\"local\", \"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\", \"obs\"", "type": "string" } } @@ -14780,14 +15670,6 @@ "github_com_Tencent_WeKnora_internal_types.Tenant": { "type": "object", "properties": { - "agent_config": { - "description": "Deprecated: AgentConfig is deprecated, use CustomAgent (builtin-smart-reasoning) config instead.\nThis field is kept for backward compatibility and will be removed in future versions.", - "allOf": [ - { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.AgentConfig" - } - ] - }, "api_key": { "description": "API key", "type": "string" @@ -14812,14 +15694,6 @@ } ] }, - "conversation_config": { - "description": "Deprecated: ConversationConfig is deprecated, use CustomAgent (builtin-quick-answer) config instead.\nThis field is kept for backward compatibility and will be removed in future versions.", - "allOf": [ - { - "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.ConversationConfig" - } - ] - }, "created_at": { "description": "Creation time", "type": "string" @@ -14910,6 +15784,21 @@ } } }, + "github_com_Tencent_WeKnora_internal_types.TenantRole": { + "type": "string", + "enum": [ + "owner", + "admin", + "contributor", + "viewer" + ], + "x-enum-varnames": [ + "TenantRoleOwner", + "TenantRoleAdmin", + "TenantRoleContributor", + "TenantRoleViewer" + ] + }, "github_com_Tencent_WeKnora_internal_types.ToolCall": { "type": "object", "properties": { @@ -15064,6 +15953,14 @@ "description": "Whether the user is active", "type": "boolean" }, + "preferences": { + "description": "Per-user UI/feature preferences (memory toggle, future knobs).\nStored as JSON (jsonb on Postgres, TEXT on SQLite) via the\ndriver.Valuer / sql.Scanner methods on UserPreferences.", + "allOf": [ + { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.UserPreferences" + } + ] + }, "tenant": { "description": "Association relationship, not stored in the database", "allOf": [ @@ -15086,6 +15983,19 @@ } } }, + "github_com_Tencent_WeKnora_internal_types.UserPreferences": { + "type": "object", + "properties": { + "enable_memory": { + "description": "EnableMemory mirrors the \"开启记忆功能\" switch in General Settings.\nnil = preference never set (treat as feature default = false)\n*false / *true = user explicitly set the toggle.", + "type": "boolean" + }, + "last_active_tenant_id": { + "description": "LastActiveTenantID remembers the last tenant the user actively\nswitched into, so a fresh login (new device, cleared browser, new\nrefresh token) lands them back in that workspace instead of always\nbouncing to their home tenant. Login / RefreshToken validate that\nthe tenant still exists and the user still has an active membership\n(or CanAccessAllTenants) before honouring this preference; an\ninvalid pointer is best-effort cleared and the user falls back to\nhome.\n\nnil = no preference (use user.TenantID, i.e. home)\n*0 = \"clear preference\" sentinel for the partial-update endpoint\n (UpdateUserPreferences turns this into nil). Otherwise treat\n a stored *0 the same as nil.\n*N = preferred tenant id.", + "type": "integer" + } + } + }, "github_com_Tencent_WeKnora_internal_types.VLMConfig": { "type": "object", "properties": { @@ -15744,6 +16654,17 @@ } } }, + "internal_handler.AddFavoriteRequest": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, "internal_handler.BatchDeleteKnowledgeRequest": { "type": "object", "required": [ @@ -16331,6 +17252,10 @@ "interfaceType": { "type": "string" }, + "modelId": { + "description": "ModelID, when set, instructs the handler to substitute any missing\nsecrets (APIKey, AppSecret via ExtraConfig) from the stored model\nrecord before assembling the test client. This lets the \"Test\nconnection\" button work on existing models without making the\nfrontend reload — and ship — the plaintext API key. Other fields\n(BaseURL, ModelName, etc.) on this request still override the\nstored values, so a user can validate a new endpoint against the\nexisting credentials in one click.", + "type": "string" + }, "modelName": { "type": "string" }, @@ -16546,6 +17471,10 @@ "interfaceType": { "type": "string" }, + "modelId": { + "description": "ModelID, when set, instructs the handler to substitute any missing\nsecrets (APIKey, AppSecret via ExtraConfig) from the stored model\nrecord before assembling the test client. This lets the \"Test\nconnection\" button work on existing models without making the\nfrontend reload — and ship — the plaintext API key. Other fields\n(BaseURL, ModelName, etc.) on this request still override the\nstored values, so a user can validate a new endpoint against the\nexisting credentials in one click.", + "type": "string" + }, "modelName": { "type": "string" }, @@ -16597,11 +17526,14 @@ "minio": { "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.MinIOEngineConfig" }, + "obs": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.OBSEngineConfig" + }, "oss": { "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.OSSEngineConfig" }, "provider": { - "description": "\"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\"", + "description": "\"minio\", \"cos\", \"tos\", \"s3\", \"oss\", \"ks3\", \"obs\"", "type": "string" }, "s3": { @@ -16808,6 +17740,21 @@ } } }, + "internal_handler.addMemberRequest": { + "type": "object", + "required": [ + "email", + "role" + ], + "properties": { + "email": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole" + } + } + }, "internal_handler.addSimilarQuestionsRequest": { "type": "object", "required": [ @@ -16823,6 +17770,58 @@ } } }, + "internal_handler.auditLogListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.AuditLog" + } + }, + "next_cursor": { + "type": "integer" + }, + "success": { + "type": "boolean" + } + } + }, + "internal_handler.createInvitationRequest": { + "type": "object", + "required": [ + "email", + "role" + ], + "properties": { + "email": { + "type": "string" + }, + "message": { + "type": "string" + }, + "role": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole" + } + } + }, + "internal_handler.createTenantRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": "string", + "maxLength": 512 + }, + "name": { + "type": "string", + "maxLength": 128, + "minLength": 1 + } + } + }, "internal_handler.updateLastFAQImportResultDisplayStatusRequest": { "type": "object", "required": [ @@ -16838,6 +17837,29 @@ } } }, + "internal_handler.updateMemberRoleRequest": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "$ref": "#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole" + } + } + }, + "internal_handler.updateMyPreferencesRequest": { + "type": "object", + "properties": { + "enable_memory": { + "type": "boolean" + }, + "last_active_tenant_id": { + "description": "LastActiveTenantID lets the SPA persist \"after a fresh login,\ndrop me back into this workspace\" across devices. Send a positive\ntenant id to set / replace, or 0 to clear. Membership is validated\nat next login, not here. Nil = field omitted from the PATCH and\nstays untouched.", + "type": "integer" + } + } + }, "internal_handler_session.AttachmentUpload": { "type": "object", "properties": { @@ -16885,7 +17907,7 @@ "type": "boolean" }, "enable_memory": { - "description": "Whether memory feature is enabled for this request", + "description": "EnableMemory is the per-request override for the memory feature.\nPointer + omitempty so the request can distinguish three states:\n nil = client did not specify; backend falls back to the calling\n user's persisted preference (user.preferences.enable_memory),\n defaulting to false if that's also unset. This is the path\n used by the normal logged-in chat UI now that the toggle is\n stored server-side per user.\n *true / *false = explicit override. Embedded mode forces *false so a\n user's personal memory setting doesn't leak into a widget\n context; older clients that still send a literal bool also\n land here (back-compat).", "type": "boolean" }, "images": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9eb57092..f2415fd3 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -113,6 +113,8 @@ definitions: - 2101 - 2102 - 2103 + - 2200 + - 2201 type: integer x-enum-varnames: - ErrBadRequest @@ -135,6 +137,8 @@ definitions: - ErrAgentMissingAllowedTools - ErrAgentInvalidMaxIterations - ErrAgentInvalidTemperature + - ErrVectorStoreBindingInvalid + - ErrVectorStoreUnavailable github_com_Tencent_WeKnora_internal_infrastructure_chunker.DocProfile: properties: all_caps_short_line_count: @@ -211,117 +215,18 @@ definitions: model_id: type: string type: object - github_com_Tencent_WeKnora_internal_types.AgentConfig: - properties: - allowed_skills: - description: Skill names whitelist (empty = allow all) - items: - type: string - type: array - allowed_tools: - description: List of allowed tool names - items: - type: string - type: array - history_turns: - description: Number of history turns to keep in context - type: integer - knowledge_bases: - description: Accessible knowledge base IDs - items: - type: string - type: array - knowledge_ids: - description: Accessible knowledge IDs (individual documents) - items: - type: string - type: array - llm_call_timeout: - description: 'LLM call timeout in seconds (default: 120). Controls the maximum - time for a single LLM call.' - type: integer - max_context_tokens: - description: |- - Maximum context window tokens for the agent (default: 200000). - The agent compresses older messages to stay within this limit, - preserving tool_call/tool_result pairs. - type: integer - max_iterations: - description: Maximum number of ReAct iterations - type: integer - max_tool_output_chars: - description: |- - Maximum character length for tool output (default: 16000). - Outputs exceeding this limit are truncated with head + tail preservation. - type: integer - mcp_selection_mode: - description: MCP service selection - type: string - mcp_services: - description: Selected MCP service IDs (when mode is "selected") - items: - type: string - type: array - multi_turn_enabled: - description: Whether multi-turn conversation is enabled - type: boolean - parallel_tool_calls: - description: |- - Whether to execute independent tool calls in parallel (default: false). - When enabled and the LLM returns multiple tool calls, they run concurrently via errgroup. - type: boolean - retain_retrieval_history: - description: 'Whether to retain retrieval history (like wiki_read_page results) - across turns (default: false)' - type: boolean - retrieve_kb_only_when_mentioned: - description: 'Whether to retrieve knowledge base only when explicitly mentioned - with @ (default: false)' - type: boolean - skill_dirs: - description: Directories to search for skills - items: - type: string - type: array - skills_enabled: - description: Skills configuration (Progressive Disclosure pattern) - type: boolean - system_prompt: - description: Unified system prompt (uses web_search_status placeholder for - dynamic behavior) - type: string - system_prompt_web_disabled: - description: 'Deprecated: Custom prompt when web search is disabled' - type: string - system_prompt_web_enabled: - description: 'Deprecated: Use SystemPrompt instead. Kept for backward compatibility - during migration.' - type: string - temperature: - description: LLM temperature for agent - type: number - thinking: - description: Whether to enable thinking mode (for models that support extended - thinking) - type: boolean - use_custom_system_prompt: - description: Whether to use custom system prompt instead of default - type: boolean - web_search_enabled: - description: Whether web search tool is enabled - type: boolean - web_search_max_results: - description: 'Maximum number of web search results (default: 5)' - type: integer - web_search_provider_id: - description: WebSearchProviderEntity ID (resolved from agent config) - type: string - type: object github_com_Tencent_WeKnora_internal_types.AgentStep: properties: iteration: description: Iteration number (0-indexed) type: integer + reasoning_content: + description: |- + ReasoningContent stores the OpenAI-protocol reasoning_content emitted by the + model in this round. Persisted on AgentStep so cross-turn replay can put it + back on the assistant message — required by MiMo / DeepSeek V3.2+ thinking + mode, ignored by providers that don't recognize the field. + type: string thought: description: LLM's reasoning/thinking (Think phase) type: string @@ -342,6 +247,69 @@ definitions: x-enum-varnames: - AnswerStrategyAll - AnswerStrategyRandom + github_com_Tencent_WeKnora_internal_types.AuditAction: + enum: + - rbac.member_added + - rbac.member_removed + - rbac.member_role_changed + - rbac.member_left + - rbac.access_denied + - rbac.invitation_sent + - rbac.invitation_accepted + - rbac.invitation_declined + - rbac.invitation_revoked + - rbac.invitation_expired + type: string + x-enum-varnames: + - AuditActionMemberAdded + - AuditActionMemberRemoved + - AuditActionMemberRoleChanged + - AuditActionMemberLeft + - AuditActionAccessDenied + - AuditActionInvitationSent + - AuditActionInvitationAccepted + - AuditActionInvitationDeclined + - AuditActionInvitationRevoked + - AuditActionInvitationExpired + github_com_Tencent_WeKnora_internal_types.AuditLog: + properties: + action: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.AuditAction' + actor_role: + type: string + actor_user_id: + type: string + created_at: + type: string + details: + items: + type: integer + type: array + id: + type: integer + outcome: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.AuditOutcome' + request_method: + type: string + request_path: + type: string + target_id: + type: string + target_type: + type: string + target_user_id: + type: string + tenant_id: + type: integer + type: object + github_com_Tencent_WeKnora_internal_types.AuditOutcome: + enum: + - success + - denied + type: string + x-enum-varnames: + - AuditOutcomeSuccess + - AuditOutcomeDenied github_com_Tencent_WeKnora_internal_types.COSEngineConfig: properties: app_id: @@ -502,56 +470,6 @@ definitions: description: 'Summarize threshold: number of messages before summarization' type: integer type: object - github_com_Tencent_WeKnora_internal_types.ConversationConfig: - properties: - context_template: - description: ContextTemplate is the prompt template for summarizing retrieval - results - type: string - embedding_top_k: - type: integer - enable_query_expansion: - type: boolean - enable_rewrite: - type: boolean - fallback_prompt: - type: string - fallback_response: - type: string - fallback_strategy: - description: Fallback strategy - type: string - keyword_threshold: - type: number - max_completion_tokens: - description: MaxTokens is the maximum number of tokens to generate - type: integer - max_rounds: - description: Retrieval & strategy parameters - type: integer - prompt: - description: Prompt is the system prompt for normal mode - type: string - rerank_model_id: - type: string - rerank_threshold: - type: number - rerank_top_k: - type: integer - rewrite_prompt_system: - description: Rewrite prompts - type: string - rewrite_prompt_user: - type: string - summary_model_id: - description: Model configuration - type: string - temperature: - description: Temperature controls the randomness of the model output - type: number - vector_threshold: - type: number - type: object github_com_Tencent_WeKnora_internal_types.CreateOrganizationRequest: properties: avatar: @@ -614,6 +532,14 @@ definitions: ContextTemplateID references a template ID in prompt_templates/ YAML files. If set and ContextTemplate is empty, the template content will be resolved at startup. type: string + data_analysis_enabled: + description: |- + ===== Data Analysis Settings ===== + Whether to run the legacy in-pipeline DuckDB SQL data-analysis stage when + the retrieved chunks include CSV/Excel files. This issues an extra LLM + call to generate a SQL query and is disabled by default because most + quick-answer / RAG-style agents do not want the added latency. + type: boolean embedding_top_k: description: |- ===== Retrieval Strategy Settings (for both modes) ===== @@ -708,6 +634,11 @@ definitions: ===== Multi-turn Conversation Settings ===== Whether multi-turn conversation is enabled type: boolean + query_understand_model_id: + description: |- + Dedicated chat model ID for the query-understanding (rewrite + intent) step. + When empty, the main conversation ModelID is used as a fallback. + type: string rerank_model_id: description: ReRank model ID for retrieval type: string @@ -1118,16 +1049,27 @@ definitions: type: object github_com_Tencent_WeKnora_internal_types.InviteMemberRequest: properties: + representative_user_id: + description: |- + RepresentativeUserID identifies the user attached to the OTM row for + display/audit. Optional: when unset, the handler picks a stable default + (the user from the legacy UserID field, or the tenant's owner). + type: string role: allOf: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.OrgMemberRole' description: 'Role to assign: admin/editor/viewer' + tenant_id: + description: TenantID is the tenant to enrol as an org member. Preferred field. + type: integer user_id: - description: User ID to invite + description: |- + UserID is retained for backward compatibility. When set without + TenantID, the handler resolves the user's TenantID and uses this + user as the representative. type: string required: - role - - user_id type: object github_com_Tencent_WeKnora_internal_types.JoinByOrganizationIDRequest: properties: @@ -1287,6 +1229,20 @@ definitions: created_at: description: Creation time of the knowledge base type: string + creator_id: + description: |- + CreatorID records the user ID of whoever originally created the KB. + Used by the tenant-level RBAC middleware to let Contributors edit + their own KBs without granting them access to everyone else's. + Nullable for backward compatibility with rows created before the + RBAC migration backfilled the column to the tenant Owner. + type: string + creator_name: + description: |- + CreatorName 是 CreatorID 对应用户的展示名(username / email 等), + 仅在列表场景由 handler 批量回填,不落库;为空表示创建者无法解析(用户已删除、 + CreatorID 为空的老数据等)。前端用它在卡片来源徽章上做 mine vs tenant 的二分。 + type: string deleted_at: allOf: - $ref: '#/definitions/gorm.DeletedAt' @@ -1320,7 +1276,15 @@ definitions: IndexingStrategy controls which indexing pipelines are active for this knowledge base. Pipelines: vector search, keyword search, wiki generation, knowledge graph extraction. is_pinned: - description: Whether this knowledge base is pinned to the top of the list + description: |- + IsPinned and PinnedAt are computed per-caller from user_kb_pins + (see migration 000050). They used to be stored on the row itself, + which made pinning a tenant-wide ordering decision gated behind + the kb-edit RBAC guard. The columns are still present in legacy + schemas for rollback safety but are no longer read or written by + the application — both fields are tagged `gorm:"-"` so GORM + ignores them on every CRUD call and the list handler stamps them + after enriching with the caller's pin set. type: boolean is_processing: description: IsProcessing indicates if there is a processing import task (for @@ -1337,7 +1301,9 @@ definitions: description: Name of the knowledge base type: string pinned_at: - description: Time when the knowledge base was pinned (nil if not pinned) + description: |- + PinnedAt records when the current caller pinned this knowledge + base; nil when they have not. type: string processing_count: description: ProcessingCount indicates the number of knowledge items being @@ -1540,14 +1506,31 @@ definitions: type: object github_com_Tencent_WeKnora_internal_types.LoginResponse: properties: + active_tenant: + allOf: + - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant' + description: |- + ActiveTenant is the tenant whose ID is encoded in the issued JWT; + future requests are scoped to it until the client calls /auth/switch-tenant. + Defaults to the user's home tenant on a fresh login. + memberships: + description: |- + Memberships lists every tenant the user can authenticate into, + along with their role in each. Always populated (length 1 for users + who only belong to their home tenant) so frontends can render a + tenant switcher without a follow-up request. Serialised without + omitempty so the field is always present as a JSON array (possibly + empty) — the "always populated" contract relies on the server side + guaranteeing a non-nil slice. + items: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.Membership' + type: array message: type: string refresh_token: type: string success: type: boolean - tenant: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant' token: type: string user: @@ -1708,6 +1691,15 @@ definitions: - MatchTypeWebSearch - MatchTypeDirectLoad - MatchTypeDataAnalysis + github_com_Tencent_WeKnora_internal_types.Membership: + properties: + role: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole' + tenant_id: + type: integer + tenant_name: + type: string + type: object github_com_Tencent_WeKnora_internal_types.MentionedItem: properties: id: @@ -1988,6 +1980,23 @@ definitions: - ModelTypeKnowledgeQA - ModelTypeVLLM - ModelTypeASR + github_com_Tencent_WeKnora_internal_types.OBSEngineConfig: + properties: + access_key: + type: string + bucket_name: + type: string + endpoint: + type: string + path_prefix: + type: string + region: + type: string + secret_key: + type: string + use_ssl: + type: boolean + type: object github_com_Tencent_WeKnora_internal_types.OIDCAuthURLResponse: properties: authorization_url: @@ -2049,10 +2058,14 @@ definitions: type: string joined_at: type: string + representative_user_id: + type: string role: type: string tenant_id: type: integer + tenant_name: + type: string user_id: type: string username: @@ -2093,6 +2106,14 @@ definitions: type: string owner_id: type: string + owner_tenant_id: + description: |- + OwnerTenantID is the persisted owner tenant of the organization + (Plan 3, migration 000046). Frontend uses this to identify the + "owner row" in the tenant-keyed members list — comparing + member.tenant_id against owner_tenant_id is the post-Plan-3 + equivalent of the old member.user_id == owner_id check. + type: integer pending_join_request_count: description: 待审批加入申请数(仅管理员可见) type: integer @@ -2136,6 +2157,9 @@ definitions: mineru_model: description: MinerU 自建解析参数 type: string + mineru_vlm_server_url: + description: vLLM 服务器地址 (vlm-http-client / hybrid-http-client) + type: string type: object github_com_Tencent_WeKnora_internal_types.ParserEngineRule: properties: @@ -2366,12 +2390,16 @@ definitions: type: string endpoint: type: string + force_path_style: + type: boolean path_prefix: type: string region: type: string secret_key: type: string + use_ssl: + type: boolean type: object github_com_Tencent_WeKnora_internal_types.SearchParams: properties: @@ -2516,6 +2544,16 @@ definitions: is_pinned: description: IsPinned indicates whether the session is pinned in the list. type: boolean + last_request_state: + allOf: + - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.SessionLastRequestState' + description: |- + LastRequestState records the input-bar state used the last time this + session sent a question (agent, model, KB scope, web search, MCPs). + Persisted on every successful POST to /knowledge-chat or /agent-chat so + that reopening the session can restore the original request context to + the chat UI. Stored in the legacy sessions.agent_config JSONB column to + avoid a new migration; the shape used today is `SessionLastRequestState`. pinned_at: description: PinnedAt records when the session was pinned; nil when not pinned. type: string @@ -2533,6 +2571,25 @@ definitions: tenant level) and for IM-created sessions that do not map to a WeKnora user. type: string type: object + github_com_Tencent_WeKnora_internal_types.SessionLastRequestState: + properties: + agent_enabled: + type: boolean + agent_id: + type: string + knowledge_base_ids: + items: + type: string + type: array + knowledge_ids: + items: + type: string + type: array + model_id: + type: string + web_search_enabled: + type: boolean + type: object github_com_Tencent_WeKnora_internal_types.ShareKnowledgeBaseRequest: properties: organization_id: @@ -2546,26 +2603,42 @@ definitions: github_com_Tencent_WeKnora_internal_types.StorageConfig: properties: app_id: + description: App ID (COS specific) type: string bucket_name: + description: Bucket Name type: string + endpoint: + description: Endpoint (S3 specific) - e.g., s3.amazonaws.com, oss-cn-hangzhou.aliyuncs.com + type: string + force_path_style: + description: ForcePathStyle (S3 specific) - whether to use path-style URLs + type: boolean path_prefix: + description: Path Prefix type: string provider: + description: 'Provider: "cos", "minio", "s3"' type: string region: + description: Region type: string secret_id: + description: Secret ID (COS) / Access Key ID (S3, MinIO) type: string secret_key: + description: Secret Key (COS) / Secret Access Key (S3, MinIO) type: string + use_ssl: + description: UseSSL (S3 specific) - whether to use HTTPS + type: boolean type: object github_com_Tencent_WeKnora_internal_types.StorageEngineConfig: properties: cos: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.COSEngineConfig' default_provider: - description: '"local", "minio", "cos", "tos", "s3", "oss", "ks3"' + description: '"local", "minio", "cos", "tos", "s3", "oss", "ks3", "obs"' type: string ks3: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.KS3EngineConfig' @@ -2573,6 +2646,8 @@ definitions: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.LocalEngineConfig' minio: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.MinIOEngineConfig' + obs: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.OBSEngineConfig' oss: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.OSSEngineConfig' s3: @@ -2583,7 +2658,7 @@ definitions: github_com_Tencent_WeKnora_internal_types.StorageProviderConfig: properties: provider: - description: '"local", "minio", "cos", "tos", "s3", "oss", "ks3"' + description: '"local", "minio", "cos", "tos", "s3", "oss", "ks3", "obs"' type: string type: object github_com_Tencent_WeKnora_internal_types.SubmitJoinRequestRequest: @@ -2673,12 +2748,6 @@ definitions: type: object github_com_Tencent_WeKnora_internal_types.Tenant: properties: - agent_config: - allOf: - - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.AgentConfig' - description: |- - Deprecated: AgentConfig is deprecated, use CustomAgent (builtin-smart-reasoning) config instead. - This field is kept for backward compatibility and will be removed in future versions. api_key: description: API key type: string @@ -2695,12 +2764,6 @@ definitions: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.ContextConfig' description: Global Context configuration for this tenant (default for all sessions) - conversation_config: - allOf: - - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.ConversationConfig' - description: |- - Deprecated: ConversationConfig is deprecated, use CustomAgent (builtin-quick-answer) config instead. - This field is kept for backward compatibility and will be removed in future versions. created_at: description: Creation time type: string @@ -2759,6 +2822,18 @@ definitions: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.WebSearchConfig' description: Global WebSearch configuration for this tenant type: object + github_com_Tencent_WeKnora_internal_types.TenantRole: + enum: + - owner + - admin + - contributor + - viewer + type: string + x-enum-varnames: + - TenantRoleOwner + - TenantRoleAdmin + - TenantRoleContributor + - TenantRoleViewer github_com_Tencent_WeKnora_internal_types.ToolCall: properties: args: @@ -2866,6 +2941,13 @@ definitions: is_active: description: Whether the user is active type: boolean + preferences: + allOf: + - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.UserPreferences' + description: |- + Per-user UI/feature preferences (memory toggle, future knobs). + Stored as JSON (jsonb on Postgres, TEXT on SQLite) via the + driver.Valuer / sql.Scanner methods on UserPreferences. tenant: allOf: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant' @@ -2880,6 +2962,32 @@ definitions: description: Username of the user type: string type: object + github_com_Tencent_WeKnora_internal_types.UserPreferences: + properties: + enable_memory: + description: |- + EnableMemory mirrors the "开启记忆功能" switch in General Settings. + nil = preference never set (treat as feature default = false) + *false / *true = user explicitly set the toggle. + type: boolean + last_active_tenant_id: + description: |- + LastActiveTenantID remembers the last tenant the user actively + switched into, so a fresh login (new device, cleared browser, new + refresh token) lands them back in that workspace instead of always + bouncing to their home tenant. Login / RefreshToken validate that + the tenant still exists and the user still has an active membership + (or CanAccessAllTenants) before honouring this preference; an + invalid pointer is best-effort cleared and the user falls back to + home. + + nil = no preference (use user.TenantID, i.e. home) + *0 = "clear preference" sentinel for the partial-update endpoint + (UpdateUserPreferences turns this into nil). Otherwise treat + a stored *0 the same as nil. + *N = preferred tenant id. + type: integer + type: object github_com_Tencent_WeKnora_internal_types.VLMConfig: properties: api_key: @@ -3393,6 +3501,13 @@ definitions: description: Valid is true if Time is not NULL type: boolean type: object + internal_handler.AddFavoriteRequest: + properties: + id: + type: string + type: + type: string + type: object internal_handler.BatchDeleteKnowledgeRequest: properties: ids: @@ -3792,6 +3907,17 @@ definitions: type: object interfaceType: type: string + modelId: + description: |- + ModelID, when set, instructs the handler to substitute any missing + secrets (APIKey, AppSecret via ExtraConfig) from the stored model + record before assembling the test client. This lets the "Test + connection" button work on existing models without making the + frontend reload — and ship — the plaintext API key. Other fields + (BaseURL, ModelName, etc.) on this request still override the + stored values, so a user can validate a new endpoint against the + existing credentials in one click. + type: string modelName: type: string provider: @@ -3937,6 +4063,17 @@ definitions: type: object interfaceType: type: string + modelId: + description: |- + ModelID, when set, instructs the handler to substitute any missing + secrets (APIKey, AppSecret via ExtraConfig) from the stored model + record before assembling the test client. This lets the "Test + connection" button work on existing models without making the + frontend reload — and ship — the plaintext API key. Other fields + (BaseURL, ModelName, etc.) on this request still override the + stored values, so a user can validate a new endpoint against the + existing credentials in one click. + type: string modelName: type: string provider: @@ -3974,10 +4111,12 @@ definitions: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.KS3EngineConfig' minio: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.MinIOEngineConfig' + obs: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.OBSEngineConfig' oss: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.OSSEngineConfig' provider: - description: '"minio", "cos", "tos", "s3", "oss", "ks3"' + description: '"minio", "cos", "tos", "s3", "oss", "ks3", "obs"' type: string s3: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.S3EngineConfig' @@ -4113,6 +4252,16 @@ definitions: required: - name type: object + internal_handler.addMemberRequest: + properties: + email: + type: string + role: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole' + required: + - email + - role + type: object internal_handler.addSimilarQuestionsRequest: properties: similar_questions: @@ -4123,6 +4272,41 @@ definitions: required: - similar_questions type: object + internal_handler.auditLogListResponse: + properties: + data: + items: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.AuditLog' + type: array + next_cursor: + type: integer + success: + type: boolean + type: object + internal_handler.createInvitationRequest: + properties: + email: + type: string + message: + type: string + role: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole' + required: + - email + - role + type: object + internal_handler.createTenantRequest: + properties: + description: + maxLength: 512 + type: string + name: + maxLength: 128 + minLength: 1 + type: string + required: + - name + type: object internal_handler.updateLastFAQImportResultDisplayStatusRequest: properties: display_status: @@ -4133,6 +4317,26 @@ definitions: required: - display_status type: object + internal_handler.updateMemberRoleRequest: + properties: + role: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.TenantRole' + required: + - role + type: object + internal_handler.updateMyPreferencesRequest: + properties: + enable_memory: + type: boolean + last_active_tenant_id: + description: |- + LastActiveTenantID lets the SPA persist "after a fresh login, + drop me back into this workspace" across devices. Send a positive + tenant id to set / replace, or 0 to clear. Membership is validated + at next login, not here. Nil = field omitted from the PATCH and + stays untouched. + type: integer + type: object internal_handler_session.AttachmentUpload: properties: data: @@ -4166,7 +4370,18 @@ definitions: description: Whether to disable auto title generation type: boolean enable_memory: - description: Whether memory feature is enabled for this request + description: |- + EnableMemory is the per-request override for the memory feature. + Pointer + omitempty so the request can distinguish three states: + nil = client did not specify; backend falls back to the calling + user's persisted preference (user.preferences.enable_memory), + defaulting to false if that's also unset. This is the path + used by the normal logged-in chat UI now that the toggle is + stored server-side per user. + *true / *false = explicit override. Embedded mode forces *false so a + user's personal memory setting doesn't leak into a widget + context; older clients that still send a literal bool also + land here (back-compat). type: boolean images: description: Attached images for multimodal chat @@ -5533,6 +5748,22 @@ paths: summary: 修改密码 tags: - 认证 + /auth/config: + get: + consumes: + - application/json + description: 返回当前部署的注册模式等公开认证配置,供前端决定是否展示注册入口 + produces: + - application/json + responses: + "200": + description: 认证配置 + schema: + additionalProperties: true + type: object + summary: 获取认证配置 + tags: + - 认证 /auth/login: post: consumes: @@ -5603,6 +5834,41 @@ paths: summary: 获取当前用户信息 tags: - 认证 + /auth/me/preferences: + put: + consumes: + - application/json + description: |- + 按 PATCH 语义合并用户偏好(仅覆盖请求体里出现的字段,其余字段保持不变), + 数据存放在 users.preferences (JSON),跨设备/浏览器自动同步。 + parameters: + - description: Preferences patch + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handler.updateMyPreferencesRequest' + produces: + - application/json + responses: + "200": + description: 更新后的偏好 + schema: + additionalProperties: true + type: object + "400": + description: 请求参数错误 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + "401": + description: 未授权 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + summary: 更新当前用户的个性化设置 + tags: + - 认证 /auth/oidc/callback: get: consumes: @@ -5733,6 +5999,43 @@ paths: summary: 用户注册 tags: - 认证 + /auth/switch-tenant: + post: + consumes: + - application/json + description: 为当前用户在目标租户重新签发访问令牌;要求该用户在目标租户存在 active 成员关系 + parameters: + - description: 切换请求 + in: body + name: request + required: true + schema: + properties: + refresh_token: + type: string + tenant_id: + type: integer + type: object + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.LoginResponse' + "400": + description: 参数错误 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + "403": + description: 无该租户成员关系 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + summary: 切换激活租户 + tags: + - 认证 /auth/validate: get: consumes: @@ -8686,6 +8989,79 @@ paths: summary: 更新MCP服务 tags: - MCP服务 + /mcp-services/{id}/credentials: + put: + consumes: + - application/json + description: 为指定字段写入新凭据;省略的字段保留原值;空字符串视为 no-op(如需删除请用 DELETE) + parameters: + - description: MCP 服务 ID + in: path + name: id + required: true + type: string + - description: '{api_key?: string, token?: string}' + in: body + name: request + required: true + schema: + additionalProperties: true + type: object + produces: + - application/json + responses: + "200": + description: 写入后的凭据状态 + schema: + additionalProperties: true + type: object + "400": + description: 请求参数错误 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + "404": + description: 服务不存在 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + - ApiKeyAuth: [] + summary: 设置 MCP 服务凭据 + tags: + - MCP服务 + /mcp-services/{id}/credentials/{field}: + delete: + description: 删除指定字段的存储凭据;删除已为空的字段是幂等的 + parameters: + - description: MCP 服务 ID + in: path + name: id + required: true + type: string + - description: 字段名(api_key | token) + in: path + name: field + required: true + type: string + produces: + - application/json + responses: + "204": + description: No Content + "400": + description: 字段名非法 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + "404": + description: 服务不存在 + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + - ApiKeyAuth: [] + summary: 移除 MCP 服务的单个凭据字段 + tags: + - MCP服务 /mcp-services/{id}/resources: get: consumes: @@ -8818,6 +9194,87 @@ paths: summary: 获取MCP服务工具列表 tags: - MCP服务 + /me/invitations: + get: + description: 返回当前登录用户的待接受邀请(默认仅 pending),用于头像入口和 /invitations 收件箱页。 + parameters: + - description: 是否包含已处理 / 已过期等终止态行(默认 false) + in: query + name: include_terminal + type: boolean + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 列出我的待接受邀请 + tags: + - 我的邀请 + /me/invitations/{inv_id}/accept: + post: + description: 当前登录用户接受一条 pending 邀请;服务端会同时写入 tenant_members 行。 + parameters: + - description: 邀请 ID + in: path + name: inv_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 接受邀请 + tags: + - 我的邀请 + /me/invitations/{inv_id}/decline: + post: + description: 当前登录用户拒绝一条 pending 邀请;不创建 tenant_members 行。 + parameters: + - description: 邀请 ID + in: path + name: inv_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 拒绝邀请 + tags: + - 我的邀请 + /me/invitations/pending-count: + get: + description: 轻量级 endpoint,返回当前登录用户的 pending 邀请数,用于头像旁的角标轮询。 + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 获取我的待处理邀请数 + tags: + - 我的邀请 /messages/{session_id}/{id}: delete: consumes: @@ -9130,7 +9587,7 @@ paths: - WeKnoraCloud /organizations: get: - description: 获取当前用户所属的所有组织,并附带各空间内知识库/智能体数量 + description: 获取当前租户所属的所有组织,并附带各空间内知识库/智能体数量 produces: - application/json responses: @@ -9435,7 +9892,7 @@ paths: - 组织管理 /organizations/{id}/members: get: - description: 获取组织的所有成员 + description: 获取组织的所有成员(按租户) parameters: - description: 组织ID in: path @@ -9454,18 +9911,18 @@ paths: summary: 获取组织成员列表 tags: - 组织管理 - /organizations/{id}/members/{user_id}: + /organizations/{id}/members/{tenant_id}: delete: - description: 从组织中移除成员(需要管理员权限) + description: 从组织中移除成员租户(需要管理员权限) parameters: - description: 组织ID in: path name: id required: true type: string - - description: 用户ID + - description: 成员租户ID in: path - name: user_id + name: tenant_id required: true type: string responses: @@ -9486,16 +9943,16 @@ paths: put: consumes: - application/json - description: 更新组织成员的角色(需要管理员权限) + description: 更新组织成员(租户)的角色(需要管理员权限) parameters: - description: 组织ID in: path name: id required: true type: string - - description: 用户ID + - description: 成员租户ID in: path - name: user_id + name: tenant_id required: true type: string - description: 角色信息 @@ -9555,16 +10012,16 @@ paths: summary: 申请权限升级 tags: - 组织管理 - /organizations/{id}/search-users: + /organizations/{id}/search-tenants: get: - description: 搜索用户(排除已有成员)用于邀请加入组织 + description: 搜索租户(排除已加入的租户)用于邀请加入组织;按租户去重,附带代表用户 parameters: - description: 组织ID in: path name: id required: true type: string - - description: 搜索关键词(用户名或邮箱) + - description: 搜索关键词(租户名、用户名或邮箱) in: query name: q required: true @@ -9588,9 +10045,13 @@ paths: $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' security: - Bearer: [] - summary: 搜索可邀请的用户 + summary: 搜索可邀请的租户 tags: - 组织管理 + /organizations/{id}/search-users: + get: + deprecated: true + responses: {} /organizations/{id}/shared-agents: get: description: 获取指定空间下所有共享智能体,包含他人共享的与我共享的,用于列表页空间视角 @@ -10466,14 +10927,17 @@ paths: post: consumes: - application/json - description: 创建新的租户 + description: |- + 创建新的租户。任意已登录用户均可调用以建立自己的新工作区, + 调用方会被自动设为该租户的 Owner。跨租户超管仍可像以前一样 + 通过本接口创建任意租户。 parameters: - description: 租户信息 in: body name: request required: true schema: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_types.Tenant' + $ref: '#/definitions/internal_handler.createTenantRequest' produces: - application/json responses: @@ -10616,6 +11080,291 @@ paths: summary: 重置租户 API Key tags: - 租户管理 + /tenants/{id}/audit-log: + get: + description: 返回该租户最近的审计事件,按 id 倒序。游标分页:将上次响应的 next_cursor 作为下一次请求的 after_id。 + parameters: + - description: 租户ID + in: path + name: id + required: true + type: string + - description: 游标:返回 id 小于此值的记录(默认从最新开始) + in: query + name: after_id + type: integer + - description: 页大小,1-100,默认 50 + in: query + name: limit + type: integer + - description: 按 action 精确过滤(如 rbac.member_added / rbac.access_denied) + in: query + name: action + type: string + - description: 按 outcome 精确过滤(success / denied) + in: query + name: outcome + type: string + - description: 按 actor_user_id 精确过滤 + in: query + name: actor + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/internal_handler.auditLogListResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' + security: + - Bearer: [] + - ApiKeyAuth: [] + summary: 获取租户审计日志 + tags: + - 审计日志 + /tenants/{id}/invitations: + get: + description: 按 tenant 列出待接受 / 历史邀请。query include_terminal=true 时附带 accepted/declined/revoked/expired。 + parameters: + - description: 租户 ID + in: path + name: id + required: true + type: string + - description: 是否包含终止态行(默认 false) + in: query + name: include_terminal + type: boolean + - default: 1 + description: 页码(从 1 起) + in: query + name: page + type: integer + - default: 20 + description: 每页数量 + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 列出租户邀请 + tags: + - 租户邀请 + post: + consumes: + - application/json + description: Owner 通过邮箱邀请已注册用户加入当前租户;被邀请人需要在 /me/invitations 接受后才会成为成员。 + parameters: + - description: 租户 ID + in: path + name: id + required: true + type: string + - description: 邀请请求 + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handler.createInvitationRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 发出租户邀请 + tags: + - 租户邀请 + /tenants/{id}/invitations/{inv_id}: + delete: + description: Owner 取消一条还在 pending 的邀请;已 accepted/declined/revoked/expired 的行不可再撤销。 + parameters: + - description: 租户 ID + in: path + name: id + required: true + type: string + - description: 邀请 ID + in: path + name: inv_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 撤销待接受邀请 + tags: + - 租户邀请 + /tenants/{id}/leave: + post: + description: 调用方主动退出当前租户。等价于以自己的 user_id 调 RemoveMember, + parameters: + - description: 租户 ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 退出当前租户 + tags: + - 租户成员 + /tenants/{id}/members: + get: + description: 分页返回当前租户内 active 成员(含每位成员的角色、邮箱、头像);支持 q 按邮箱/用户名筛选 + parameters: + - description: 租户 ID + in: path + name: id + required: true + type: string + - description: 按邮箱/用户名模糊筛选 + in: query + name: q + type: string + - default: 1 + description: 页码(从 1 起) + in: query + name: page + type: integer + - default: 20 + description: 每页数量(最大 100) + in: query + name: page_size + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 列出租户成员 + tags: + - 租户成员 + post: + consumes: + - application/json + parameters: + - description: 租户 ID + in: path + name: id + required: true + type: string + - description: 邀请请求 + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handler.addMemberRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 直接添加租户成员(直加路径) + tags: + - 租户成员 + /tenants/{id}/members/{user_id}: + delete: + description: Owner 将某位成员从当前租户中移除(软删除 tenant_members 行);不能移除最后一位 Owner + parameters: + - description: 租户 ID + in: path + name: id + required: true + type: string + - description: 用户 ID + in: path + name: user_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 移除租户成员 + tags: + - 租户成员 + put: + consumes: + - application/json + description: Owner 修改某位成员在当前租户内的角色;不能将最后一位 Owner 降级 + parameters: + - description: 租户 ID + in: path + name: id + required: true + type: string + - description: 用户 ID + in: path + name: user_id + required: true + type: string + - description: 目标角色 + in: body + name: request + required: true + schema: + $ref: '#/definitions/internal_handler.updateMemberRoleRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - Bearer: [] + summary: 修改租户成员角色 + tags: + - 租户成员 /tenants/all: get: consumes: @@ -10642,7 +11391,7 @@ paths: get: consumes: - application/json - description: 获取租户级别的KV配置(支持agent-config、web-search-config、conversation-config) + description: 获取租户级别的KV配置(支持web-search-config、prompt-templates、parser-engine-config、storage-engine-config、chat-history-config、retrieval-config) parameters: - description: 配置键名 in: path @@ -10670,7 +11419,7 @@ paths: put: consumes: - application/json - description: 更新租户级别的KV配置(支持agent-config、web-search-config、conversation-config) + description: 更新租户级别的KV配置(支持web-search-config、parser-engine-config、storage-engine-config、chat-history-config、retrieval-config) parameters: - description: 配置键名 in: path @@ -10701,52 +11450,6 @@ paths: summary: 更新租户KV配置 tags: - 租户管理 - /tenants/kv/agent-config: - get: - consumes: - - application/json - description: 获取租户的全局Agent配置(默认应用于所有会话) - produces: - - application/json - responses: - "200": - description: Agent配置 - schema: - additionalProperties: true - type: object - "400": - description: 请求参数错误 - schema: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' - security: - - Bearer: [] - - ApiKeyAuth: [] - summary: 获取租户Agent配置 - tags: - - 租户管理 - /tenants/kv/conversation-config: - get: - consumes: - - application/json - description: 获取租户的全局对话配置(默认应用于普通模式会话) - produces: - - application/json - responses: - "200": - description: 对话配置 - schema: - additionalProperties: true - type: object - "400": - description: 请求参数错误 - schema: - $ref: '#/definitions/github_com_Tencent_WeKnora_internal_errors.AppError' - security: - - Bearer: [] - - ApiKeyAuth: [] - summary: 获取租户对话配置 - tags: - - 租户管理 /tenants/kv/prompt-templates: get: consumes: @@ -10835,6 +11538,64 @@ paths: summary: 搜索租户 tags: - 租户管理 + /user/favorites: + get: + description: Lists this user's starred resources in the current tenant for a + given type + parameters: + - description: Resource type (kb | agent) + in: query + name: type + required: true + type: string + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + summary: List my favorites + tags: + - User + post: + parameters: + - description: Type + id + in: body + name: body + required: true + schema: + $ref: '#/definitions/internal_handler.AddFavoriteRequest' + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + summary: Star a resource + tags: + - User + /user/favorites/{type}/{id}: + delete: + parameters: + - description: Resource type + in: path + name: type + required: true + type: string + - description: Resource id + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + summary: Unstar a resource + tags: + - User /vector-stores: get: description: List all vector stores for the current tenant, including environment-configured diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7e2217e8..a5bbd71c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "knowledage-base", - "version": "0.5.2", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "knowledage-base", - "version": "0.5.2", + "version": "0.6.0", "dependencies": { "@microsoft/fetch-event-source": "^2.0.1", "@types/dompurify": "^3.2.0", diff --git a/frontend/package.json b/frontend/package.json index b36fb558..d942a9a8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "knowledage-base", - "version": "0.5.2", + "version": "0.6.0", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 761f842e..d0489357 100755 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -4438,6 +4438,7 @@ export default { tenantMember: { title: 'Members', sectionDescription: 'Invite teammates to the tenant and manage their roles. Only Owner can add or remove members.', + learnRbacGuide: 'Learn about RBAC', totalCount: '{n} members', listTitle: 'Workspace members', filterMatched: '{n} matched', diff --git a/frontend/src/i18n/locales/ko-KR.ts b/frontend/src/i18n/locales/ko-KR.ts index ea41d623..68b2819b 100755 --- a/frontend/src/i18n/locales/ko-KR.ts +++ b/frontend/src/i18n/locales/ko-KR.ts @@ -4498,6 +4498,7 @@ export default { tenantMember: { title: "멤버 관리", sectionDescription: "테넌트에 동료를 초대하고 역할을 관리합니다. 소유자만 멤버를 추가하거나 제거할 수 있습니다.", + learnRbacGuide: "RBAC 알아보기", totalCount: "총 {n}명", listTitle: "워크스페이스 멤버", filterMatched: "{n}명 일치", diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index 6ca2e7d6..12669654 100755 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -4395,6 +4395,7 @@ export default { tenantMember: { title: 'Участники', sectionDescription: 'Приглашайте коллег в тенант и управляйте их ролями. Добавлять и удалять участников может только Владелец.', + learnRbacGuide: 'Подробнее о RBAC', totalCount: 'Участников: {n}', listTitle: 'Участники пространства', filterMatched: 'найдено: {n}', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index ea872ef5..fb847704 100755 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -4430,6 +4430,7 @@ export default { tenantMember: { title: "成员管理", sectionDescription: "邀请伙伴加入当前空间并分配角色。只有 Owner 可以新增或移除成员。", + learnRbacGuide: "了解 RBAC", totalCount: "共 {n} 位成员", listTitle: "空间成员", filterMatched: "筛选出 {n} 位", diff --git a/frontend/src/views/settings/TenantMembers.vue b/frontend/src/views/settings/TenantMembers.vue index 73fa813c..b8094bb4 100644 --- a/frontend/src/views/settings/TenantMembers.vue +++ b/frontend/src/views/settings/TenantMembers.vue @@ -50,7 +50,18 @@ -

{{ $t('tenantMember.sectionDescription') }}

+

+ {{ $t('tenantMember.sectionDescription') }} + + {{ $t('tenantMember.learnRbacGuide') }} + + +

diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 401297de..03e6c8f1 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -5,7 +5,7 @@ description: | with document parsing, vector search, and LLM integration. type: application version: 0.1.0 -appVersion: "v0.5.2" +appVersion: "v0.6.0" kubeVersion: ">=1.25.0-0" home: https://github.com/Tencent/WeKnora icon: https://raw.githubusercontent.com/Tencent/WeKnora/main/docs/images/logo.png diff --git a/internal/handler/audit_log.go b/internal/handler/audit_log.go index ecbe9469..cf0c4887 100644 --- a/internal/handler/audit_log.go +++ b/internal/handler/audit_log.go @@ -4,7 +4,7 @@ import ( "net/http" "strconv" - apperrors "github.com/Tencent/WeKnora/internal/errors" + "github.com/Tencent/WeKnora/internal/errors" "github.com/Tencent/WeKnora/internal/logger" "github.com/Tencent/WeKnora/internal/types" "github.com/Tencent/WeKnora/internal/types/interfaces" @@ -86,7 +86,7 @@ func (h *AuditLogHandler) ListTenantAuditLog(c *gin.Context) { entries, err := h.auditService.List(ctx, tenantID, q) if err != nil { logger.ErrorWithFields(ctx, err, map[string]interface{}{"tenant_id": tenantID}) - c.Error(apperrors.NewInternalServerError(err.Error())) + c.Error(errors.NewInternalServerError(err.Error())) return }