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} + > +
+ + + + + +