feat(admin): Implement Prompt knowledge base integration
Features: - PromptService enhancement: enhanceWithKnowledge(), loadFullKnowledge(), ragSearch() - FULL mode: Load entire knowledge base content - RAG mode: Vector search based on user query - Knowledge config API: PUT /:code/knowledge-config - Test render with knowledge injection support - Frontend: Knowledge config UI in Prompt editor Bug fixes: - Fix knowledge config not returned in getPromptDetail - Fix publish button 400 error (empty request body) - Fix cache not invalidated after publish - Add detailed logging for debugging Documentation: - Add development record 2026-01-28 - Update ADMIN module status to Phase 4.6 - Update system status document to v4.5 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,12 +12,17 @@ import {
|
||||
Timeline,
|
||||
Alert,
|
||||
Spin,
|
||||
Switch,
|
||||
Select,
|
||||
InputNumber,
|
||||
Divider,
|
||||
} from 'antd'
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
SaveOutlined,
|
||||
RocketOutlined,
|
||||
LockOutlined,
|
||||
BookOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { useAuth } from '../../framework/auth'
|
||||
import PromptEditor from './components/PromptEditor'
|
||||
@@ -27,8 +32,12 @@ import {
|
||||
publishPrompt,
|
||||
rollbackPrompt,
|
||||
testRender,
|
||||
saveKnowledgeConfig,
|
||||
fetchSystemKbList,
|
||||
type PromptDetail,
|
||||
type PromptVersion,
|
||||
type KnowledgeConfig,
|
||||
type SystemKb,
|
||||
} from './api/promptApi'
|
||||
|
||||
const { TextArea } = Input
|
||||
@@ -68,10 +77,32 @@ const PromptEditorPage = () => {
|
||||
visible: false,
|
||||
version: null,
|
||||
})
|
||||
|
||||
// 知识库配置状态
|
||||
const [systemKbs, setSystemKbs] = useState<SystemKb[]>([])
|
||||
const [knowledgeConfig, setKnowledgeConfig] = useState<KnowledgeConfig>({
|
||||
enabled: false,
|
||||
kb_codes: [],
|
||||
injection_mode: 'FULL',
|
||||
target_variable: 'context',
|
||||
top_k: 5,
|
||||
min_score: 0.5,
|
||||
})
|
||||
const [savingKbConfig, setSavingKbConfig] = useState(false)
|
||||
|
||||
// 权限检查
|
||||
const canPublish = user?.role === 'SUPER_ADMIN'
|
||||
|
||||
// 加载系统知识库列表
|
||||
const loadSystemKbs = async () => {
|
||||
try {
|
||||
const kbs = await fetchSystemKbList()
|
||||
setSystemKbs(kbs)
|
||||
} catch (error) {
|
||||
console.warn('加载系统知识库列表失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载 Prompt 详情
|
||||
const loadPromptDetail = async () => {
|
||||
if (!code) return
|
||||
@@ -86,6 +117,11 @@ const PromptEditorPage = () => {
|
||||
if (latestVersion) {
|
||||
setContent(latestVersion.content)
|
||||
}
|
||||
|
||||
// 初始化知识库配置
|
||||
if (data.knowledge_config) {
|
||||
setKnowledgeConfig(data.knowledge_config)
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '加载失败')
|
||||
navigate('/admin/prompts')
|
||||
@@ -96,6 +132,7 @@ const PromptEditorPage = () => {
|
||||
|
||||
useEffect(() => {
|
||||
loadPromptDetail()
|
||||
loadSystemKbs()
|
||||
}, [code])
|
||||
|
||||
// 内容变化
|
||||
@@ -158,12 +195,21 @@ const PromptEditorPage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 测试渲染
|
||||
// 测试渲染(支持知识库注入)
|
||||
const handleTestRender = async () => {
|
||||
try {
|
||||
const result = await testRender(content, testVariables)
|
||||
// 如果启用了知识库增强,传入 code 以触发知识库注入
|
||||
const result = await testRender(
|
||||
content,
|
||||
testVariables,
|
||||
knowledgeConfig.enabled ? code : undefined // 传入 code 触发知识库注入
|
||||
)
|
||||
setTestResult(result.rendered)
|
||||
message.success('渲染成功')
|
||||
if (result.knowledgeInjected) {
|
||||
message.success('渲染成功,知识库内容已注入!')
|
||||
} else {
|
||||
message.success('渲染成功')
|
||||
}
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '渲染失败')
|
||||
}
|
||||
@@ -174,6 +220,21 @@ const PromptEditorPage = () => {
|
||||
setViewVersionModal({ visible: true, version })
|
||||
}
|
||||
|
||||
// 保存知识库配置
|
||||
const handleSaveKnowledgeConfig = async () => {
|
||||
if (!code) return
|
||||
|
||||
setSavingKbConfig(true)
|
||||
try {
|
||||
await saveKnowledgeConfig(code, knowledgeConfig)
|
||||
message.success('知识库配置已保存')
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '保存失败')
|
||||
} finally {
|
||||
setSavingKbConfig(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 回滚到指定版本
|
||||
const handleRollback = (version: PromptVersion) => {
|
||||
if (!code) return
|
||||
@@ -387,6 +448,143 @@ const PromptEditorPage = () => {
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 知识库增强配置 */}
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<BookOutlined />
|
||||
<span>知识库增强</span>
|
||||
{knowledgeConfig.enabled && (
|
||||
<Tag color="green">已启用</Tag>
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={handleSaveKnowledgeConfig}
|
||||
loading={savingKbConfig}
|
||||
style={{ background: PRIMARY_COLOR, borderColor: PRIMARY_COLOR }}
|
||||
>
|
||||
保存配置
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{/* 启用开关 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-gray-700">启用知识库增强</span>
|
||||
<Switch
|
||||
checked={knowledgeConfig.enabled}
|
||||
onChange={(checked) => setKnowledgeConfig({ ...knowledgeConfig, enabled: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{knowledgeConfig.enabled && (
|
||||
<>
|
||||
<Divider className="my-3" />
|
||||
|
||||
{/* 知识库选择 */}
|
||||
<div>
|
||||
<label className="text-sm text-gray-600 block mb-1">关联知识库</label>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder="选择知识库"
|
||||
value={knowledgeConfig.kb_codes}
|
||||
onChange={(values) => setKnowledgeConfig({ ...knowledgeConfig, kb_codes: values })}
|
||||
style={{ width: '100%' }}
|
||||
options={systemKbs.map(kb => ({
|
||||
value: kb.code,
|
||||
label: `${kb.name} (${kb.documentCount}篇)`,
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 注入模式 */}
|
||||
<div>
|
||||
<label className="text-sm text-gray-600 block mb-1">注入模式</label>
|
||||
<Select
|
||||
value={knowledgeConfig.injection_mode}
|
||||
onChange={(value) => setKnowledgeConfig({ ...knowledgeConfig, injection_mode: value })}
|
||||
style={{ width: '100%' }}
|
||||
options={[
|
||||
{ value: 'FULL', label: 'FULL - 全量注入(适合小型核心知识库)' },
|
||||
{ value: 'RAG', label: 'RAG - 向量检索(适合大型知识库)' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 目标变量 */}
|
||||
<div>
|
||||
<label className="text-sm text-gray-600 block mb-1">注入变量</label>
|
||||
<Select
|
||||
value={knowledgeConfig.target_variable}
|
||||
onChange={(value) => setKnowledgeConfig({ ...knowledgeConfig, target_variable: value })}
|
||||
style={{ width: '100%' }}
|
||||
options={[
|
||||
{ value: 'context', label: 'context(推荐)' },
|
||||
...(prompt.variables || []).map(v => ({ value: v, label: v })),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* RAG 模式配置 */}
|
||||
{knowledgeConfig.injection_mode === 'RAG' && (
|
||||
<>
|
||||
<div className="text-xs text-gray-400 mt-2 mb-1 border-t pt-2">RAG 配置</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label className="text-xs text-gray-500">Top K</label>
|
||||
<InputNumber
|
||||
value={knowledgeConfig.top_k}
|
||||
onChange={(value) => setKnowledgeConfig({ ...knowledgeConfig, top_k: value || 5 })}
|
||||
min={1}
|
||||
max={20}
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-500">最低分数</label>
|
||||
<InputNumber
|
||||
value={knowledgeConfig.min_score}
|
||||
onChange={(value) => setKnowledgeConfig({ ...knowledgeConfig, min_score: value || 0.5 })}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.1}
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* FULL 模式配置 */}
|
||||
{knowledgeConfig.injection_mode === 'FULL' && (
|
||||
<>
|
||||
<div className="text-xs text-gray-400 mt-2 mb-1 border-t pt-2">FULL 配置</div>
|
||||
<div>
|
||||
<label className="text-xs text-gray-500">最大 Token 限制</label>
|
||||
<InputNumber
|
||||
value={knowledgeConfig.max_tokens}
|
||||
onChange={(value) => setKnowledgeConfig({ ...knowledgeConfig, max_tokens: value || undefined })}
|
||||
min={1000}
|
||||
max={100000}
|
||||
step={1000}
|
||||
size="small"
|
||||
placeholder="默认 50000"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 版本历史 */}
|
||||
<Card title="📜 版本历史">
|
||||
<Timeline>
|
||||
|
||||
@@ -60,6 +60,18 @@ export interface PromptVersion {
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// 知识库增强配置
|
||||
export interface KnowledgeConfig {
|
||||
enabled: boolean
|
||||
kb_codes: string[]
|
||||
injection_mode: 'FULL' | 'RAG'
|
||||
target_variable: string
|
||||
query_field?: string
|
||||
top_k?: number
|
||||
min_score?: number
|
||||
max_tokens?: number
|
||||
}
|
||||
|
||||
export interface PromptDetail {
|
||||
id: number
|
||||
code: string
|
||||
@@ -67,6 +79,7 @@ export interface PromptDetail {
|
||||
module: string
|
||||
description?: string
|
||||
variables?: string[]
|
||||
knowledge_config?: KnowledgeConfig | null
|
||||
versions: PromptVersion[]
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
@@ -143,12 +156,19 @@ export async function saveDraft(
|
||||
* 发布 Prompt
|
||||
*/
|
||||
export async function publishPrompt(code: string): Promise<any> {
|
||||
console.log('[promptApi] publishPrompt:', code)
|
||||
const headers = getAuthHeaders()
|
||||
console.log('[promptApi] headers:', headers)
|
||||
|
||||
const response = await fetch(`${API_BASE}/${code}/publish`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
headers,
|
||||
body: JSON.stringify({}), // 发送空对象作为请求体
|
||||
})
|
||||
|
||||
console.log('[promptApi] response status:', response.status)
|
||||
const data = await response.json()
|
||||
console.log('[promptApi] response data:', data)
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to publish prompt')
|
||||
@@ -213,12 +233,21 @@ export async function getDebugStatus(): Promise<any> {
|
||||
|
||||
/**
|
||||
* 测试渲染
|
||||
* @param content - Prompt 内容
|
||||
* @param variables - 变量
|
||||
* @param code - 可选:传入 code 以测试知识库注入
|
||||
* @param userQuery - 可选:RAG 模式的查询词
|
||||
*/
|
||||
export async function testRender(content: string, variables: Record<string, any>): Promise<any> {
|
||||
export async function testRender(
|
||||
content: string,
|
||||
variables: Record<string, any>,
|
||||
code?: string,
|
||||
userQuery?: string
|
||||
): Promise<{ rendered: string; extractedVariables: string[]; validation: any; knowledgeInjected?: boolean }> {
|
||||
const response = await fetch(`${API_BASE}/test-render`, {
|
||||
method: 'POST',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ content, variables }),
|
||||
body: JSON.stringify({ content, variables, code, userQuery }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
@@ -230,4 +259,53 @@ export async function testRender(content: string, variables: Record<string, any>
|
||||
return data.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存知识库配置
|
||||
*/
|
||||
export async function saveKnowledgeConfig(
|
||||
code: string,
|
||||
knowledgeConfig: KnowledgeConfig
|
||||
): Promise<any> {
|
||||
const response = await fetch(`${API_BASE}/${code}/knowledge-config`, {
|
||||
method: 'PUT',
|
||||
headers: getAuthHeaders(),
|
||||
body: JSON.stringify({ knowledge_config: knowledgeConfig }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to save knowledge config')
|
||||
}
|
||||
|
||||
return data.data
|
||||
}
|
||||
|
||||
// 系统知识库类型
|
||||
export interface SystemKb {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
description?: string
|
||||
category?: string
|
||||
documentCount: number
|
||||
totalTokens: number
|
||||
status: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统知识库列表(用于选择)
|
||||
*/
|
||||
export async function fetchSystemKbList(): Promise<SystemKb[]> {
|
||||
const response = await fetch('/api/v1/admin/system-kb', {
|
||||
headers: getAuthHeaders(),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || 'Failed to fetch system knowledge bases')
|
||||
}
|
||||
|
||||
return data.data
|
||||
}
|
||||
Reference in New Issue
Block a user