RecallTestModal.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import { apiInterceptors, recallMethodOptions, recallTest, recallTestRecommendQuestion } from '@/client/api';
  2. import MarkDownContext from '@/new-components/common/MarkdownContext';
  3. import { ISpace, RecallTestProps } from '@/types/knowledge';
  4. import { SettingOutlined } from '@ant-design/icons';
  5. import { useRequest } from 'ahooks';
  6. import { Button, Card, Empty, Form, Input, InputNumber, Modal, Popover, Select, Spin, Tag } from 'antd';
  7. import React, { useEffect } from 'react';
  8. type RecallTestModalProps = {
  9. open: boolean;
  10. setOpen: React.Dispatch<React.SetStateAction<boolean>>;
  11. space: ISpace;
  12. };
  13. // const tagColors = ['magenta', 'orange', 'geekblue', 'purple', 'cyan', 'green'];
  14. const RecallTestModal: React.FC<RecallTestModalProps> = ({ open, setOpen, space }) => {
  15. const [form] = Form.useForm();
  16. const [extraForm] = Form.useForm();
  17. // 获取推荐问题
  18. const { run: questionsRun } = useRequest(
  19. // const { data: questions = [], run: questionsRun } = useRequest(
  20. async () => {
  21. const [, res] = await apiInterceptors(recallTestRecommendQuestion(space.name + ''));
  22. return res ?? [];
  23. },
  24. {
  25. manual: true,
  26. },
  27. );
  28. // 召回方法选项
  29. const { data: options = [], run: optionsRun } = useRequest(
  30. async () => {
  31. const [, res] = await apiInterceptors(recallMethodOptions(space.name + ''));
  32. return res ?? [];
  33. },
  34. {
  35. manual: true,
  36. onSuccess: data => {
  37. extraForm.setFieldValue('recall_retrievers', data);
  38. },
  39. },
  40. );
  41. useEffect(() => {
  42. if (open) {
  43. // questionsRun();
  44. optionsRun();
  45. }
  46. }, [open, optionsRun, questionsRun]);
  47. // 召回测试
  48. const {
  49. run: recallTestRun,
  50. data: resultList = [],
  51. loading,
  52. } = useRequest(
  53. async (props: RecallTestProps) => {
  54. const [, res] = await apiInterceptors(recallTest({ ...props }, space.name + ''));
  55. return res ?? [];
  56. },
  57. {
  58. manual: true,
  59. },
  60. );
  61. const onTest = async () => {
  62. form.validateFields().then(async values => {
  63. const extraVal = extraForm.getFieldsValue();
  64. await recallTestRun({ recall_top_k: 1, recall_retrievers: options, ...values, ...extraVal });
  65. });
  66. };
  67. return (
  68. <Modal
  69. title='召回测试'
  70. width={'60%'}
  71. open={open}
  72. footer={false}
  73. onCancel={() => setOpen(false)}
  74. centered
  75. destroyOnClose={true}
  76. >
  77. <Card
  78. title='召回配置'
  79. size='small'
  80. className='my-4'
  81. extra={
  82. <Popover
  83. placement='bottomRight'
  84. trigger='hover'
  85. title='向量检索设置'
  86. content={
  87. <Form
  88. form={extraForm}
  89. initialValues={{
  90. recall_top_k: 1,
  91. }}
  92. >
  93. <Form.Item label='Topk' tooltip='基于相似度得分的前 k 个向量' name='recall_top_k'>
  94. <InputNumber placeholder='请输入' className='w-full' />
  95. </Form.Item>
  96. <Form.Item label='召回方法' name='recall_retrievers'>
  97. <Select
  98. mode='multiple'
  99. options={options.map(item => {
  100. return { label: item, value: item };
  101. })}
  102. className='w-full'
  103. allowClear
  104. disabled
  105. />
  106. </Form.Item>
  107. <Form.Item label='score阈值' name='recall_score_threshold'>
  108. <InputNumber placeholder='请输入' className='w-full' step={0.1} />
  109. </Form.Item>
  110. </Form>
  111. }
  112. >
  113. <SettingOutlined className='text-lg' />
  114. </Popover>
  115. }
  116. >
  117. <Form form={form} layout='vertical' onFinish={onTest}>
  118. <Form.Item
  119. label='测试问题'
  120. required={true}
  121. name='question'
  122. rules={[{ required: true, message: '请输入测试问题' }]}
  123. className='m-0 p-0'
  124. >
  125. <div className='flex w-full items-center gap-8'>
  126. <Input placeholder='请输入测试问题' autoComplete='off' allowClear className='w-1/2' />
  127. <Button type='primary' htmlType='submit'>
  128. 测试
  129. </Button>
  130. </div>
  131. </Form.Item>
  132. {/* {questions?.length > 0 && (
  133. <Col span={16}>
  134. <Form.Item label="推荐问题" tooltip="点击选择,自动填入">
  135. <div className="flex flex-wrap gap-2">
  136. {questions.map((item, index) => (
  137. <Tag
  138. color={tagColors[index]}
  139. key={item}
  140. className="cursor-pointer"
  141. onClick={() => {
  142. form.setFieldValue('question', item);
  143. }}
  144. >
  145. {item}
  146. </Tag>
  147. ))}
  148. </div>
  149. </Form.Item>
  150. </Col>
  151. )} */}
  152. </Form>
  153. </Card>
  154. <Card title='召回结果' size='small'>
  155. <Spin spinning={loading}>
  156. {resultList.length > 0 ? (
  157. <div
  158. className='flex flex-col overflow-y-auto'
  159. style={{
  160. height: '45vh',
  161. }}
  162. >
  163. {resultList.map(item => (
  164. <Card
  165. title={
  166. <div className='flex items-center'>
  167. <Tag color='blue'># {item.chunk_id}</Tag>
  168. {item.metadata.source}
  169. </div>
  170. }
  171. extra={
  172. <div className='flex items-center gap-2'>
  173. <span className='font-semibold'>score:</span>
  174. <span className='text-blue-500'>{item.score}</span>
  175. </div>
  176. }
  177. key={item.chunk_id}
  178. size='small'
  179. className='mb-4 border-gray-500 shadow-md'
  180. >
  181. <MarkDownContext>{item.content}</MarkDownContext>
  182. </Card>
  183. ))}
  184. </div>
  185. ) : (
  186. <Empty />
  187. )}
  188. </Spin>
  189. </Card>
  190. </Modal>
  191. );
  192. };
  193. export default RecallTestModal;