From cc4b7d50e356b0beb5d13bf731aa050d5d26e567 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 6 Feb 2026 22:09:47 -0800 Subject: [PATCH] feat(admin-client): add manual experience creation in ExperiencePage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 管理员现在可以在"系统经验管理"页面手动创建经验,而不仅限于审核系统自动生成的经验。 实现细节: - experience.api.ts: 新增 CreateExperienceDto 类型和 createExperience() API 方法 - 调用 POST /memory/experience 创建经验 - 创建后自动调用 POST /memory/experience/:id/approve 激活 - 管理员手动创建的经验无需额外审核流程 - sourceConversationId 标记为 'admin-manual' 以区分来源 - 默认置信度 80%(高于系统自动生成的 45%) - useExperience.ts: 新增 useCreateExperience mutation hook - 创建成功后自动刷新经验列表和统计数据 - ExperiencePage.tsx: 新增"新建经验"按钮和创建表单弹窗 - 表单字段:经验类型、适用场景、经验内容、相关移民类别(可选)、置信度 - 移民类别下拉:QMAS/GEP/IANG/TTPS/CIES/TechTAS - 表单验证:类型、场景、内容为必填 这与方案A(评估门控失败自动沉淀经验)互补: - 自动路径:Gate failure → PENDING experience → 管理员审核 → 激活 - 手动路径:管理员直接创建 → 自动激活(无需审核) Co-Authored-By: Claude Opus 4.6 --- .../features/experience/application/index.ts | 1 + .../experience/application/useExperience.ts | 19 ++- .../infrastructure/experience.api.ts | 25 ++++ .../experience/infrastructure/index.ts | 2 +- .../presentation/pages/ExperiencePage.tsx | 118 ++++++++++++++++-- 5 files changed, 155 insertions(+), 10 deletions(-) diff --git a/packages/admin-client/src/features/experience/application/index.ts b/packages/admin-client/src/features/experience/application/index.ts index 6e2d170..57d7cca 100644 --- a/packages/admin-client/src/features/experience/application/index.ts +++ b/packages/admin-client/src/features/experience/application/index.ts @@ -3,6 +3,7 @@ export { useExperienceStatistics, useApproveExperience, useRejectExperience, + useCreateExperience, useRunEvolution, EXPERIENCE_QUERY_KEY, EXPERIENCE_STATS_KEY, diff --git a/packages/admin-client/src/features/experience/application/useExperience.ts b/packages/admin-client/src/features/experience/application/useExperience.ts index a9c4ecc..d3b9183 100644 --- a/packages/admin-client/src/features/experience/application/useExperience.ts +++ b/packages/admin-client/src/features/experience/application/useExperience.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { message } from 'antd'; -import { experienceApi } from '../infrastructure/experience.api'; +import { experienceApi, type CreateExperienceDto } from '../infrastructure/experience.api'; export const EXPERIENCE_QUERY_KEY = 'pending-experiences'; export const EXPERIENCE_STATS_KEY = 'experience-stats'; @@ -48,6 +48,23 @@ export function useRejectExperience() { }); } +export function useCreateExperience() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (dto: CreateExperienceDto) => + experienceApi.createExperience(dto), + onSuccess: () => { + message.success('经验已创建并自动激活'); + queryClient.invalidateQueries({ queryKey: [EXPERIENCE_QUERY_KEY] }); + queryClient.invalidateQueries({ queryKey: [EXPERIENCE_STATS_KEY] }); + }, + onError: () => { + message.error('创建经验失败'); + }, + }); +} + export function useRunEvolution() { const queryClient = useQueryClient(); diff --git a/packages/admin-client/src/features/experience/infrastructure/experience.api.ts b/packages/admin-client/src/features/experience/infrastructure/experience.api.ts index ad44cce..5cf147a 100644 --- a/packages/admin-client/src/features/experience/infrastructure/experience.api.ts +++ b/packages/admin-client/src/features/experience/infrastructure/experience.api.ts @@ -27,6 +27,14 @@ export interface ExperienceStatistics { byType: Record; } +export interface CreateExperienceDto { + experienceType: string; + content: string; + scenario: string; + relatedCategory?: string; + confidence?: number; +} + export const experienceApi = { getPendingExperiences: async (type?: string): Promise => { const params = new URLSearchParams(); @@ -48,6 +56,23 @@ export const experienceApi = { await api.post(`/memory/experience/${id}/reject`, { adminId }); }, + createExperience: async (dto: CreateExperienceDto): Promise => { + // 管理员手动创建:先创建(PENDING),再自动批准(APPROVED + isActive) + const createResponse = await api.post('/memory/experience', { + ...dto, + sourceConversationId: 'admin-manual', + confidence: dto.confidence ?? 80, + }); + const created = createResponse.data.data as Experience; + + // 自动批准 — 管理员手动创建的经验无需再审核 + await api.post(`/memory/experience/${created.id}/approve`, { + adminId: 'admin-manual', + }); + + return created; + }, + runEvolution: async (hoursBack: number = 24, limit: number = 50): Promise<{ conversationsAnalyzed: number; experiencesExtracted: number; diff --git a/packages/admin-client/src/features/experience/infrastructure/index.ts b/packages/admin-client/src/features/experience/infrastructure/index.ts index 0423522..c3196b6 100644 --- a/packages/admin-client/src/features/experience/infrastructure/index.ts +++ b/packages/admin-client/src/features/experience/infrastructure/index.ts @@ -1,2 +1,2 @@ export { experienceApi } from './experience.api'; -export type { Experience, ExperienceListResponse, ExperienceStatistics } from './experience.api'; +export type { Experience, ExperienceListResponse, ExperienceStatistics, CreateExperienceDto } from './experience.api'; diff --git a/packages/admin-client/src/features/experience/presentation/pages/ExperiencePage.tsx b/packages/admin-client/src/features/experience/presentation/pages/ExperiencePage.tsx index 0d43e48..1f2fd0f 100644 --- a/packages/admin-client/src/features/experience/presentation/pages/ExperiencePage.tsx +++ b/packages/admin-client/src/features/experience/presentation/pages/ExperiencePage.tsx @@ -12,12 +12,16 @@ import { Statistic, Row, Col, + Form, + Input, + InputNumber, } from 'antd'; import { CheckOutlined, CloseOutlined, EyeOutlined, PlayCircleOutlined, + PlusOutlined, } from '@ant-design/icons'; import { useAuthStore } from '../../../auth/application'; import { @@ -25,11 +29,13 @@ import { useExperienceStatistics, useApproveExperience, useRejectExperience, + useCreateExperience, useRunEvolution, } from '../../application'; import type { Experience } from '../../infrastructure'; const { Title, Text, Paragraph } = Typography; +const { TextArea } = Input; const EXPERIENCE_TYPES = [ { value: 'COMMON_QUESTION', label: '常见问题' }, @@ -47,6 +53,8 @@ export function ExperiencePage() { const [typeFilter, setTypeFilter] = useState(); const [selectedExperience, setSelectedExperience] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [createForm] = Form.useForm(); const admin = useAuthStore((state) => state.admin); const { data: pendingData, isLoading: pendingLoading } = usePendingExperiences( @@ -56,6 +64,7 @@ export function ExperiencePage() { const { data: stats } = useExperienceStatistics(); const approveMutation = useApproveExperience(); const rejectMutation = useRejectExperience(); + const createMutation = useCreateExperience(); const runEvolutionMutation = useRunEvolution(); const handleView = (exp: Experience) => { @@ -75,6 +84,20 @@ export function ExperiencePage() { } }; + const handleCreate = async () => { + try { + const values = await createForm.validateFields(); + createMutation.mutate(values, { + onSuccess: () => { + setIsCreateModalOpen(false); + createForm.resetFields(); + }, + }); + } catch { + // validation error — form will show inline errors + } + }; + const getTypeLabel = (type: string) => { return EXPERIENCE_TYPES.find((t) => t.value === type)?.label || type; }; @@ -181,14 +204,22 @@ export function ExperiencePage() {
系统经验管理 - + + + +
{/* 统计卡片 */} @@ -346,6 +377,77 @@ export function ExperiencePage() {
)} + + {/* 新建经验弹窗 */} + { + setIsCreateModalOpen(false); + createForm.resetFields(); + }} + onOk={handleCreate} + okText="创建" + cancelText="取消" + confirmLoading={createMutation.isPending} + width={600} + > +
+ + + + + +