fix(knowledge): deep audit — 18 bugs fixed across backend + frontend

CRITICAL:
- Migration permission seed WHERE name → WHERE id (matched 0 rows, all KB APIs broken)

HIGH:
- analytics_quality SQL alias + missing comma fix
- search() duplicate else block compile error
- chunk_content duplicate var declarations + type mismatch
- SQL invalid escape sequences
- delete_category missing rows_affected check

MEDIUM:
- analytics_overview hit_rate vs positive_feedback_rate separation
- analytics_quality GROUP BY kc.id,kc.name (same-name category merge)
- update_category handler trim + empty name validation
- update_item duplicate VALID_STATUSES inside transaction
- page_size max(1) lower bound in list handlers
- batch_create title/content/length validation
- embedding dispatch silent error → tracing::warn
- Version modal close clears detailItem state
- Search empty state distinguishes not-searched vs no-results
- Create modal cancel resets form
This commit is contained in:
iven
2026-04-02 19:07:42 +08:00
parent 837abec48a
commit 7e4b787d5c
7 changed files with 953 additions and 114 deletions

View File

@@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS knowledge_categories (
id TEXT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
parent_id TEXT REFERENCES knowledge_categories(id),
parent_id TEXT REFERENCES knowledge_categories(id) ON DELETE RESTRICT,
icon VARCHAR(50),
sort_order INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
@@ -22,13 +22,13 @@ CREATE INDEX IF NOT EXISTS idx_kc_parent ON knowledge_categories(parent_id);
-- 知识条目
CREATE TABLE IF NOT EXISTS knowledge_items (
id TEXT PRIMARY KEY,
category_id TEXT NOT NULL REFERENCES knowledge_categories(id),
category_id TEXT NOT NULL REFERENCES knowledge_categories(id) ON DELETE RESTRICT,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
keywords TEXT[] DEFAULT '{}',
related_questions TEXT[] DEFAULT '{}',
priority INT DEFAULT 0,
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'archived', 'deprecated')),
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'archived', 'deprecated', 'draft')),
version INT DEFAULT 1,
source VARCHAR(50) DEFAULT 'manual',
tags TEXT[] DEFAULT '{}',
@@ -38,6 +38,7 @@ CREATE TABLE IF NOT EXISTS knowledge_items (
CHECK (length(content) <= 100000)
);
CREATE INDEX IF NOT EXISTS idx_ki_category ON knowledge_items(category_id);
CREATE INDEX IF NOT EXISTS idx_ki_status_updated ON knowledge_items(status, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_ki_keywords ON knowledge_items USING GIN(keywords);
-- 知识分块RAG 检索核心)
@@ -50,6 +51,7 @@ CREATE TABLE IF NOT EXISTS knowledge_chunks (
keywords TEXT[] DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_kchunks_item_idx ON knowledge_chunks(item_id, chunk_index);
CREATE INDEX IF NOT EXISTS idx_kchunks_item ON knowledge_chunks(item_id);
CREATE INDEX IF NOT EXISTS idx_kchunks_keywords ON knowledge_chunks USING GIN(keywords);
@@ -57,7 +59,7 @@ CREATE INDEX IF NOT EXISTS idx_kchunks_keywords ON knowledge_chunks USING GIN(ke
-- 仅在有数据后创建此索引可提升性能,这里预创建
CREATE INDEX IF NOT EXISTS idx_kchunks_embedding ON knowledge_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
WITH (m = 16, ef_construction = 128);
-- 版本快照
CREATE TABLE IF NOT EXISTS knowledge_versions (
@@ -77,8 +79,8 @@ CREATE INDEX IF NOT EXISTS idx_kv_item ON knowledge_versions(item_id);
-- 使用追踪
CREATE TABLE IF NOT EXISTS knowledge_usage (
id TEXT PRIMARY KEY,
item_id TEXT NOT NULL REFERENCES knowledge_items(id),
chunk_id TEXT REFERENCES knowledge_chunks(id),
item_id TEXT REFERENCES knowledge_items(id) ON DELETE SET NULL,
chunk_id TEXT REFERENCES knowledge_chunks(id) ON DELETE SET NULL,
session_id VARCHAR(100),
query_text TEXT,
relevance_score FLOAT,
@@ -86,24 +88,36 @@ CREATE TABLE IF NOT EXISTS knowledge_usage (
agent_feedback VARCHAR(20) CHECK (agent_feedback IN ('positive', 'negative')),
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_ku_item ON knowledge_usage(item_id);
CREATE INDEX IF NOT EXISTS idx_ku_created ON knowledge_usage(created_at);
CREATE INDEX IF NOT EXISTS idx_ku_item ON knowledge_usage(item_id) WHERE item_id IS NOT NULL;
-- BRIN 索引:追加写入的时间序列数据比 B-tree 更高效
CREATE INDEX IF NOT EXISTS idx_ku_created_brin ON knowledge_usage USING brin(created_at);
-- 权限种子数据
-- 权限种子数据(使用 jsonb 操作避免 REPLACE 脆弱性)
UPDATE roles
SET permissions = REPLACE(
permissions,
']',
', "knowledge:read", "knowledge:write", "knowledge:admin", "knowledge:search"]'
SET permissions = (
SELECT '[' || string_agg('"' || elem || '"', ', ') || ']'
FROM (
SELECT DISTINCT elem
FROM json_array_elements_text(permissions::json) AS elem
UNION ALL SELECT 'knowledge:read'
UNION ALL SELECT 'knowledge:write'
UNION ALL SELECT 'knowledge:admin'
UNION ALL SELECT 'knowledge:search'
) sub
)
WHERE name = 'super_admin'
WHERE id = 'super_admin'
AND permissions NOT LIKE '%knowledge:read%';
UPDATE roles
SET permissions = REPLACE(
permissions,
']',
', "knowledge:read", "knowledge:write", "knowledge:search"]'
SET permissions = (
SELECT '[' || string_agg('"' || elem || '"', ', ') || ']'
FROM (
SELECT DISTINCT elem
FROM json_array_elements_text(permissions::json) AS elem
UNION ALL SELECT 'knowledge:read'
UNION ALL SELECT 'knowledge:write'
UNION ALL SELECT 'knowledge:search'
) sub
)
WHERE name = 'admin'
WHERE id = 'admin'
AND permissions NOT LIKE '%knowledge:read%';