| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- import * as React from 'react';
- import { useLocation } from 'react-router-dom';
- import { observer } from 'mobx-react';
- import './style.less';
- import {
- Button, Input, Form, Divider, Splitter, Select, InputNumber, InputNumberProps,
- Radio, Switch, Row, Col, Slider, Space, RadioChangeEvent,
- Spin, message
- } from 'antd';
- import { PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons';
- import { apis } from '@/apis';
- import router from '@/router';
- const { TextArea } = Input;
- const FormItem = Form.Item;
- const { Option } = Select;
- const MAX_COUNT = 10;
- const Index = 1;
- const QuestionAnswerInfo: React.FC = () => {
- const [form] = Form.useForm();
- // top_p
- const [topPValue, setTopPValue] = React.useState(0.1);
- const TopPDecimalStep: React.FC = () => {
- const onChange: InputNumberProps['onChange'] = (value) => {
- if (Number.isNaN(value)) {
- return;
- }
- setTopPValue(value as number);
- };
- return (
- <Row>
- <Col span={12}>
- <Slider
- min={0}
- max={1}
- onChange={onChange}
- value={typeof topPValue === 'number' ? topPValue : 0}
- step={0.1}
- />
- </Col>
- <Col span={4}>
- <InputNumber
- min={0}
- max={1}
- style={{ margin: '0 16px', width: '100px' }}
- step={0.01}
- value={topPValue}
- onChange={onChange}
- />
- </Col>
- </Row>
- );
- };
- const [tempValue, setTempValue] = React.useState(0.01);
- // temperature
- const TempDecimalStep: React.FC = () => {
- const onChange: InputNumberProps['onChange'] = (value) => {
- if (Number.isNaN(value)) {
- return;
- }
- setTempValue(value as number);
- };
- return (
- <Row>
- <Col span={12}>
- <Slider
- min={0}
- max={1}
- onChange={onChange}
- value={typeof tempValue === 'number' ? tempValue : 0}
- step={0.01}
- />
- </Col>
- <Col span={4}>
- <InputNumber
- min={0}
- max={1}
- style={{ margin: '0 16px', width: '100px' }}
- step={0.01}
- value={tempValue}
- onChange={onChange}
- />
- </Col>
- </Row>
- );
- };
- type ModelList = {
- label: string,
- value: string,
- }[];
- type KnowledgeList = {
- label: string,
- value: string,
- }[];
- const [step, setStep] = React.useState(1);
- const [pageLoading, setPageLoading] = React.useState(false);
- const [modelList, setModelList] = React.useState<ModelList>([]);
- const [knowledgeList, setKnowledgeList] = React.useState<KnowledgeList>([]);
- const [isVisible, setIsVisible] = React.useState(false);
- const [isVisibleCus, setIsVisibleCus] = React.useState(false);
- const [isVisibleSlice, setIsVisibleSlice] = React.useState(false);
- const [isVisibleRerank, setIsVisibleRerank] = React.useState(false);
- const [name, setName] = React.useState('');
- const style: React.CSSProperties = {
- display: 'flex',
- flexDirection: 'column',
- gap: 8,
- width: 300,
- };
- const location = useLocation();
- const init = async (id: string) => {
- await Promise.all([
- api.fetchKnowlegde(),
- api.fetchModelList(),
- ])
- if (id) {
- await api.fetchDetail(id);
- }
- }
- React.useEffect(() => {
- const id = location?.state?.id;
- init(id)
- }, []);
- // 定义一个状态来存储输入框数组
- const [inputs, setInputs] = React.useState([{ id: 1, value: '' }]);
- // 添加新输入框的函数
- const addInput = () => {
- const newId = inputs.length + 1; // 生成新的唯一ID
- setInputs([...inputs, { id: newId, value: '' }]);
- };
- const delInput = () => {
- const newId = inputs.length - 1; // 生成新的唯一ID
- setInputs(inputs.slice(0, inputs.length - 1));
- };
- // 处理输入变更的函数
- const handleChange = (id: number, value: string) => {
- setInputs(inputs.map(input => (input.id === id ? { ...input, value } : input)));
- };
- // const onChange: InputNumberProps['onChange'] = (value) => {
- // console.log('changed', value);
- // };
- // const onChangeShow = (checked: boolean) => {
- // console.log(`switch to ${checked}`);
- // };
- // const onChangeModel = (checked: boolean) => {
- // setIsVisibleRerank(!isVisibleRerank);
- // };
- // const onChangeCount = (value: string) => {
- // if (value === 'fixed') {
- // setIsVisibleSlice(!isVisibleSlice);
- // } else {
- // setIsVisibleSlice(false);
- // }
- // };
- // 召回方式
- const onChangeRecallMethod = (e: RadioChangeEvent) => {
- };
- // 获取应用详情
- const api = {
- fetchDetail: async (app_id: string) => {
- setPageLoading(true);
- try {
- const res = await apis.fetchApplicationDetail(app_id)
- const sd = res.data.questionlist.map((item: any, index: number) => {
- return {
- "id": index + 1,
- "value": item.question,
- }
- });
- const info = res.data.detail;
- if (info.paramDesc === 'custom') {
- setIsVisibleCus(!isVisibleCus); //自定义回答风格
- }
- // if (jsonObj.rerank_status === 1) {
- // setIsVisibleRerank(!isVisibleRerank) //模型
- // }
- // //召回切片数量
- // if (jsonObj.slice_config_type === 'fixed') {
- // setIsVisibleSlice(!isVisibleSlice);
- // }else {
- // setIsVisibleSlice(false);
- // }
- setTopPValue(info.topP as number);
- setTempValue(info.temperature as number);
- setName(info.name);
- form.setFieldsValue({
- id: info.id,
- name: info.name, //应用名称
- desc: info.desc, //应用描述
- prompt: info.prompt, //应用提示语
- top_p: info.top_p, //topP
- temperature: info.temperature, //温度
- knowledge_ids: info.knowledge_ids,
- slice_count: info.slice_count,
- model: info.model,
- icon_color: info.icon_color,
- icon_type: info.icon_type,
- questionList: sd, //问题列表
- // max_token: info.max_token, //应用最大token
- // updateDate: info.updateDate, // 更新时间
- // param_desc: info.paramDesc, //回答风格
- // questionList: sd, //问题列表
- // knowledge_ids: jsonObj.knowledge_ids, //知识库id
- // model: jsonObj.model, //模型名称
- // slice_config_type: jsonObj.slice_config_type, //切片类型
- // recall_method: jsonObj.recall_method, //召回方式
- // slice_count: jsonObj.slice_count, //切片数量
- // rerank_model_name: jsonObj.rerank_model_name, //模型名称
- // recall_slice_splicing_method: jsonObj.recall_slice_splicing_method,
- // rerank_status: jsonObj.rerank_status, //开启rerank
- // show_recall_result: jsonObj.show_recall_result, //是否展示召回结果
- // recall_index_type_list: jsonObj.recall_index_type_list, //知识库id
- // rerank_index_type_list: jsonObj.rerank_index_type_list, //知识库id
- })
- console.log(sd, 'sd');
- setInputs(sd);
- } catch (error) {
- console.error(error);
- } finally {
- setPageLoading(false);
- }
- },
- //获取知识库列表
- fetchKnowlegde: async () => {
- try {
- const res = await apis.fetchKnowledgeList();
- const list = res.data.map((item: any) => {
- return {
- label: item.name,
- value: item.knowledgeId,
- }
- });
- setKnowledgeList(list);
- } catch (error: any) {
- console.error(error);
- }
- },
- // 获取模型列表
- fetchModelList: async () => {
- try {
- const res = await apis.fetchModelList();
- const list = res.data.data.map((item: any) => {
- return {
- label: item.modelName,
- value: item.modelCode,
- }
- });
- setModelList(list);
- } catch (error: any) {
- console.error(error);
- }
- },
- }
- const handleRedioClick = (value: string) => {
- setIsVisibleCus(false);
- }
- return (
- <div className='questionAnswerInfo'>
- <Spin spinning={pageLoading}>
- <Form
- style={{ paddingTop: '20px' }}
- form={form}
- layout='vertical'
- initialValues={{
- max_token: 1024
- }}
- >
- <div style={{ display: step === 1 ? 'block' : 'none' }} className='questionAnswerInfo-content'>
- <FormItem
- label='问答应用名称'
- name='name'
- rules={[{ required: true, message: '问答应用名称不能为空' }]}
- >
- <Input placeholder="应用名称" style={{ width: 646, padding: 8 }} />
- </FormItem>
- <FormItem
- label='问答应用描述'
- name='desc'
- rules={[{ required: true, message: '问答应用描述不能为空' }]}
- >
- <TextArea
- showCount
- maxLength={500}
- placeholder="请输入描述"
- style={{ height: 120, resize: 'none', width: 646 }}
- />
- </FormItem>
- <div>
- <h4>添加预设问题</h4>
- <div>
- {
- inputs.map(input => (
- <div key={input.id} style={{ paddingTop: '10px' }}>
- <label>问题 {input.id}</label>
- <Input
- style={{ width: 300, paddingTop: 8, marginLeft: 20 }}
- type="text"
- value={input.value}
- onChange={e => handleChange(input.id, e.target.value)}
- />
- <PlusCircleOutlined style={{ marginLeft: 20 }} onClick={addInput} />
- <MinusCircleOutlined style={{ marginLeft: 20 }} onClick={delInput} />
- </div>
- ))}
- </div>
- </div>
- <br />
- <Button type='primary' onClick={() => {
- form.validateFields(['name', 'desc']).then(async (values) => {
- setStep(2);
- setInputs(inputs);
- console.log(inputs, 'inputs');
- }).catch((error) => {
- console.error(error);
- });
- }} >
- 下一步
- </Button>
- </div>
- <div style={{ display: step === 2 ? 'block' : 'none' }} className='questionAnswerInfo-content'>
- <div style={{ paddingTop: '20px', display: 'flex', justifyContent: 'flex-end' }}>
- <div>
- <Button
- style={{ background: '#f5f5f5' }}
- onClick={() => {
- setStep(1);
- }}
- >
- 上一步
- </Button>
- <Button
- type='primary'
- onClick={() => {
- form.validateFields().then(async (values) => {
- const data = values;
- const question: string[] = [];
- if (inputs) {
- inputs.map((item, index) => {
- question.push(item.value);
- });
- }
- console.log(question, 'question');
- const info = {
- id: values.id,
- name: values.name, //应用名称
- desc: values.desc, //应用描述
- prompt: values.prompt, //应用提示语
- top_p: values.top_p, //topP
- temperature: values.temperature, //温度
- knowledge_ids: values.knowledge_ids,
- slice_count: values.slice_count,
- model: values.model,
- icon_color: values.icon_color,
- icon_type: values.icon_type,
- questionList: question,
- // knowledge_info: {
- // model: values.model, // 默认模型名称
- // knowledge_ids: values.knowledge_ids, // 知识库id列表
- // slice_config_type: values.slice_config_type, // 切片类型,默认为 fixed
- // recall_method: values.recall_method, // 召回方式,默认为 embedding
- // recall_index_type_list: values.recall_index_type_list, // 索引配置类型列表,默认为空数组
- // slice_count: values.slice_count, // 切片数量,默认为空字符串
- // rerank_status: values.rerank_status ? 1 : 0, // 是否开启rerank,默认关闭
- // rerank_model_name: values.rerank_model_name, // 模型名称,默认为空字符串
- // show_recall_result: values.show_recall_result, // 是否展示召回结果,默认为空字符串
- // recall_slice_splicing_method: values.recall_slice_splicing_method // 召回切片拼接方式,默认为空字符串
- // }
- };
- const id = location?.state?.id;
- let res = null;
- if (id) {
- // 编辑应用
- res = await apis.modifyApplicationApi(id, info);
- } else {
- // 创建应用
- res = await await apis.createApplicationApi(info);
- }
- if (res.data.code !== 200) {
- message.error(res.data.message);
- } else {
- router.navigate({ pathname: '/questionAnswer' });
- }
- }).catch((error) => {
- console.error(error);
- });
- }}
- >发布应用</Button>
- </div>
- </div>
- <Splitter style={{ height: '100%', boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)' }}>
- <Splitter.Panel defaultSize="40%">
- <div style={{ width: '100%', height: '100%' }}>
- <h2>Prompt编写</h2>
- <div style={{ paddingTop: '20px' }}>
- <TextArea
- autoSize
- readOnly
- placeholder="编写Prompt过程中可以引入2项变量:{{知识}} 代表知识库中检索到的知识内容, {{用户}}代表用户输入的内容。您可以在编写Prompt过程中将变量拼接在合适的位置。插入:{{知识}} 插入:{{用户}}"
- style={{ width: '100%', height: '300px' }}
- />
- </div>
- <Divider plain></Divider>
- <div >
- <FormItem name='prompt' >
- <TextArea
- placeholder="提示词"
- rows={20}
- />
- </FormItem>
- </div>
- </div>
- </Splitter.Panel>
- <Splitter.Panel defaultSize="60%">
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', background: '#f5f5f5', width: '100%', height: '100%' }}>
- <div style={{ width: '50%', height: '100%' }}>
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '50px', fontSize: '16px' }}>
- 欢迎使用 {name}
- </div>
- <div style={{
- display: 'flex', justifyContent: 'center', alignItems: 'center',
- paddingTop: '20px'
- }}>
- <FormItem
- label='引用知识库'
- name='knowledge_ids'
- rules={[{ required: true, message: '知识库不能为空' }]}>
- <Select
- mode='multiple'
- maxCount={MAX_COUNT}
- // suffixIcon={suffix}
- style={{ width: '300px', height: '48px' }}
- placeholder='请选择知识库'
- >
- {
- knowledgeList.map((item, index) => {
- return <Option value={item.value} key={index}>
- {item.label}
- </Option>
- })
- }
- </Select>
- </FormItem>
- </div>
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='调用模型'
- name="model"
- rules={[{ required: true, message: '模型不能为空' }]}>
- <Select
- placeholder='请选择模型'
- allowClear={true}
- style={{ width: '300px', height: '48px' }}
- >
- {
- modelList.map((item, index) => {
- return <Option value={item.value} key={index}>
- {item.label}
- </Option>
- })
- }
- </Select>
- </FormItem>
- </div>
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='max token'
- name='max_token'
- rules={[{ required: true, message: 'max token不能为空' }]}>
- <InputNumber
- className='questionAnswerInfo-content-title'
- />
- </FormItem>
- </div>
- {/* {
- !isVisible &&
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <a onClick={() => {
- setIsVisible(!isVisible);
- }} className='questionAnswerInfo-content-title'>
- 更多设置
- </a>
- </div>
- } */}
- {/* {isVisible &&
- <div>
- {isVisibleCus &&
- <Space style={{ width: '100%' }} direction="vertical">
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='Top-p'
- name='topP'
- style={{ width: '300px' }}
- >
- <TopPDecimalStep />
- </FormItem>
- </div>
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='Temperature'
- name='temperature'
- style={{ width: '300px' }}
- >
- <TempDecimalStep />
- </FormItem>
- </div>
- </Space >
- }
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='回答风格'
- name='param_desc'
- rules={[{ required: true, message: '回答风格不能为空' }]}>
- <Radio.Group buttonStyle="solid"
- className='questionAnswerInfo-content-title'>
- <Radio.Button onClick={() => {
- handleRedioClick('')
- }} value='strict'>严谨</Radio.Button>
- <Radio.Button onClick={() => {
- handleRedioClick('')
- }} value='moderate'>适中</Radio.Button>
- <Radio.Button onClick={() => {
- handleRedioClick('')
- }} value='flexib'>发散</Radio.Button>
- <Radio.Button value='custom'
- onClick={() => {
- setIsVisibleCus(!isVisibleCus);
- }}
- >自定义
- </Radio.Button>
- </Radio.Group>
- </FormItem>
- </div>
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='展示引用知识'
- name='show_recall_result'
- className='questionAnswerInfo-content-title'>
- <Switch onChange={onChangeShow} />
- </FormItem>
- </div>
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='召回方式'
- name='recall_method'
- rules={[{ required: true, message: '召回方式不能为空' }]}>
- <Radio.Group
- style={style}
- onChange={onChangeRecallMethod}
- options={[
- { value: 'embedding', label: '向量化检索' },
- { value: 'keyword', label: '关键词检索' },
- { value: 'mixed', label: '混合检索' },
- ]}
- />
- </FormItem>
- </div>
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <p className='questionAnswerInfo-content-title'>重排方式</p>
- </div>
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='Rerank模型'
- name='rerank_status'
- valuePropName='checked'
- className='questionAnswerInfo-content-title'
- >
- <Switch onChange={onChangeModel} />
- </FormItem>
- </div>
- {isVisibleRerank &&
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='模型选择'
- name='rerank_model_name'
- >
- <Select
- style={{ width: '300px', height: '48px' }}
- placeholder='请选择模型'
- defaultValue={'默认rerank模型'}
- >
- <Option value='rerank'>默认rerank模型</Option>
- </Select>
- </FormItem>
- </div>
- }
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='召回切片数量'
- name='slice_config_type'
- rules={[{ required: true, message: '召回方式不能为空' }]}>
- <Select
- style={{ width: '300px', height: '48px' }}
- placeholder='请选择'
- onChange={onChangeCount}>
- <Option value="fixed">手动设置</Option>
- <Option value="customized">自动设置</Option>
- </Select>
- </FormItem>
- </div>
- {isVisibleSlice &&
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='召回切片数'
- name='slice_count'
- rules={[{ required: true, message: '切片数不能为空' }]}>
- <InputNumber max={1024} changeOnWheel className='questionAnswerInfo-content-title' />
- </FormItem>
- </div>
- }
- <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
- <FormItem
- label='召回切片拼接方式'
- name='recall_slice_splicing_method'
- >
- <TextArea
- rows={4}
- className='questionAnswerInfo-content-title'
- placeholder="请输入内容"
- />
- </FormItem>
- </div>
- </div>
- } */}
- </div>
- </div>
- </Splitter.Panel>
- </Splitter>
- </div>
- </Form>
- </Spin>
- </div>
- );
- };
- export default observer(QuestionAnswerInfo);
|