feat: Add base project structure with API and web components
This commit is contained in:
446
web/src/views/ApplicationConfig/Agent.tsx
Normal file
446
web/src/views/ApplicationConfig/Agent.tsx
Normal file
@@ -0,0 +1,446 @@
|
||||
import { type FC, type ReactNode, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
||||
import clsx from 'clsx'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Row, Col, Space, Form, Input, Switch, Button, App, Spin } from 'antd'
|
||||
import Chat from './components/Chat'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import Card from './components/Card'
|
||||
import ModelConfigModal from './components/ModelConfigModal'
|
||||
import type {
|
||||
ModelConfigModalRef,
|
||||
ChatData,
|
||||
Config,
|
||||
ModelConfig,
|
||||
AgentRef,
|
||||
KnowledgeBase,
|
||||
KnowledgeConfig,
|
||||
Variable,
|
||||
MemoryConfig,
|
||||
} from './types'
|
||||
import type { Model } from '@/views/ModelManagement/types'
|
||||
import { getModelList } from '@/api/models';
|
||||
import { saveAgentConfig } from '@/api/application'
|
||||
import Knowledge from './components/Knowledge'
|
||||
import VariableList from './components/VariableList'
|
||||
import { getApplicationConfig } from '@/api/application'
|
||||
import { getKnowledgeBaseList } from '@/views/KnowledgeBase/service'
|
||||
import { memoryConfigListUrl } from '@/api/memory'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
|
||||
|
||||
|
||||
const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className}) => {
|
||||
return (
|
||||
<div className={clsx(className, "rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-[16px] ")}>
|
||||
{desc}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const LabelWrapper: FC<{title: string, className?: string; children?: ReactNode}> = ({title, className, children}) => {
|
||||
return (
|
||||
<div className={clsx(className, "rb:text-[14px] rb:font-medium rb:leading-[20px]")}>
|
||||
{title}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const SwitchWrapper: FC<{ title: string, desc: string, name: string }> = ({ title, desc, name }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="rb:flex rb:items-center rb:justify-between">
|
||||
<LabelWrapper title={t(`application.${title}`)}>
|
||||
<DescWrapper desc={t(`application.${desc}`)} className="rb:mt-[8px]" />
|
||||
</LabelWrapper>
|
||||
<Form.Item
|
||||
name={name}
|
||||
valuePropName="checked"
|
||||
className="rb:mb-[0px]!"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const SelectWrapper: FC<{ title: string, desc: string, name: string, url: string }> = ({ title, desc, name, url }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<LabelWrapper title={t(`application.${title}`)} className="rb:mb-[8px]">
|
||||
</LabelWrapper>
|
||||
<Form.Item
|
||||
name={name}
|
||||
className="rb:mb-[0px]!"
|
||||
>
|
||||
<CustomSelect
|
||||
url={url}
|
||||
hasAll={false}
|
||||
valueKey='config_id'
|
||||
labelKey="config_name"
|
||||
/>
|
||||
</Form.Item>
|
||||
<DescWrapper desc={t(`application.${desc}`)} className="rb:mt-[8px]" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams();
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState<Config | null>(null);
|
||||
const modelConfigModalRef = useRef<ModelConfigModalRef>(null)
|
||||
const [modelList, setModelList] = useState<Model[]>([])
|
||||
const [defaultModel, setDefaultModel] = useState<Model | null>(null)
|
||||
const [chatList, setChatList] = useState<ChatData[]>([])
|
||||
const [formData, setFormData] = useState<{
|
||||
default_model_config_id?: string,
|
||||
model_parameters?: Config['model_parameters'],
|
||||
} | null>(null)
|
||||
const values = Form.useWatch<{
|
||||
memoryEnabled: boolean;
|
||||
memory_content?: string | number;
|
||||
webSearch: boolean;
|
||||
} & Config>([], form)
|
||||
|
||||
const [knowledgeConfig, setKnowledgeConfig] = useState<KnowledgeConfig>({ knowledge_bases: [] })
|
||||
const [variableList, setVariableList] = useState<Variable[]>([])
|
||||
const [isSave, setIsSave] = useState(false)
|
||||
const initialized = useRef(false)
|
||||
|
||||
// 初始化完成标记
|
||||
useEffect(() => {
|
||||
if (data && values && formData) {
|
||||
initialized.current = true
|
||||
}
|
||||
}, [data, values, formData])
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [knowledgeConfig])
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [variableList])
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [formData])
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [values])
|
||||
|
||||
useEffect(() => {
|
||||
getModels()
|
||||
getData()
|
||||
}, [])
|
||||
|
||||
const getData = () => {
|
||||
setLoading(true)
|
||||
getApplicationConfig(id as string).then(res => {
|
||||
const response = res as Config
|
||||
setData(response)
|
||||
const { memory, tools } = response
|
||||
form.setFieldsValue({
|
||||
...response,
|
||||
memoryEnabled: memory?.enabled || false,
|
||||
memory_content: memory?.memory_content ? Number(memory?.memory_content) : undefined,
|
||||
webSearch: tools?.web_search?.enabled || false,
|
||||
})
|
||||
setFormData({
|
||||
default_model_config_id: response.default_model_config_id,
|
||||
model_parameters: response.model_parameters || {},
|
||||
})
|
||||
if (response?.knowledge_retrieval?.knowledge_bases?.length) {
|
||||
getDefaultKnowledgeList(response)
|
||||
}
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const getDefaultKnowledgeList = (data: Config) => {
|
||||
if (!data || !data.knowledge_retrieval || !data.knowledge_retrieval?.knowledge_bases?.length) {
|
||||
return
|
||||
}
|
||||
const initialList = [...(data?.knowledge_retrieval?.knowledge_bases || [])]
|
||||
getKnowledgeBaseList(undefined, {
|
||||
kb_ids: initialList.map(vo => vo.kb_id).join(','),
|
||||
page: 1,
|
||||
pagesize: 100,
|
||||
})
|
||||
.then(res => {
|
||||
const list = res.items || []
|
||||
const knowledge_bases: KnowledgeBase[] = list.map(item => {
|
||||
const filterItem = initialList.find(vo => vo.kb_id === item.id)
|
||||
return {
|
||||
...item,
|
||||
...filterItem
|
||||
}
|
||||
})
|
||||
setData((prev) => {
|
||||
prev = prev as Config
|
||||
const knowledge_retrieval: KnowledgeConfig = {
|
||||
...(prev?.knowledge_retrieval || {}),
|
||||
knowledge_bases: [...knowledge_bases]
|
||||
}
|
||||
return {
|
||||
...(prev || {}),
|
||||
knowledge_retrieval
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const refresh = (vo: ModelConfig, type: 'model' | 'chat') => {
|
||||
if (type === 'model') {
|
||||
const { default_model_config_id, ...rest } = vo
|
||||
form.setFieldsValue({
|
||||
default_model_config_id,
|
||||
model_parameters: {...rest}
|
||||
})
|
||||
setFormData((prevState) => {
|
||||
const prev = prevState as Config
|
||||
return {
|
||||
...(prev || {}),
|
||||
default_model_config_id,
|
||||
model_parameters: {...rest}
|
||||
};
|
||||
})
|
||||
if (default_model_config_id === formData?.default_model_config_id) {
|
||||
setChatList([{
|
||||
label: vo.label || '',
|
||||
model_config_id: default_model_config_id || '',
|
||||
model_parameters: {...rest},
|
||||
list: []
|
||||
}])
|
||||
}
|
||||
} else if (type === 'chat') {
|
||||
if (chatList.length >= 4) {
|
||||
message.warning(t('application.maxChatCount'))
|
||||
return
|
||||
}
|
||||
const { label, default_model_config_id, ...reset } = vo
|
||||
|
||||
setChatList((prev: ChatData[]) => {
|
||||
const newChatItem: ChatData = {
|
||||
label,
|
||||
model_config_id: default_model_config_id || '',
|
||||
model_parameters: {...reset},
|
||||
list: []
|
||||
};
|
||||
return [
|
||||
...(prev || []).map(item => ({
|
||||
...item,
|
||||
list: []
|
||||
})),
|
||||
newChatItem
|
||||
];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleModelConfig = () => {
|
||||
modelConfigModalRef.current?.handleOpen('model')
|
||||
}
|
||||
const handleClearDebugging = () => {
|
||||
setChatList([])
|
||||
}
|
||||
|
||||
// 保存Agent配置
|
||||
const handleSave = (flag = true) => {
|
||||
if (!isSave || !data) return Promise.resolve()
|
||||
const { memoryEnabled, memory_content, webSearch, ...rest } = values
|
||||
const { knowledge_bases = [], ...knowledgeRest } = knowledgeConfig || {}
|
||||
|
||||
// 从原数据中获取memory的其他必要属性
|
||||
const originalMemory = data.memory || ({} as MemoryConfig)
|
||||
|
||||
const params: Config = {
|
||||
...data,
|
||||
...rest,
|
||||
...(formData || {}),
|
||||
memory: {
|
||||
...originalMemory,
|
||||
enabled: memoryEnabled,
|
||||
memory_content: memory_content ? String(memory_content) : '',
|
||||
max_history: originalMemory.max_history || '',
|
||||
},
|
||||
variables: variableList || [],
|
||||
knowledge_retrieval: knowledge_bases.length > 0 ? {
|
||||
...data.knowledge_retrieval,
|
||||
...knowledgeRest,
|
||||
knowledge_bases: knowledge_bases.map(item => ({
|
||||
kb_id: item.id,
|
||||
...(item.config || {})
|
||||
}))
|
||||
} as KnowledgeConfig : null,
|
||||
tools: {
|
||||
web_search: {
|
||||
enabled: webSearch,
|
||||
config: {
|
||||
web_search: webSearch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
saveAgentConfig(data.app_id, params)
|
||||
.then(() => {
|
||||
if (flag) {
|
||||
message.success(t('common.saveSuccess'))
|
||||
}
|
||||
setIsSave(false)
|
||||
resolve(true)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
const getModels = () => {
|
||||
const requests = [getModelList({ type: 'llm', pagesize: 100, page: 1 }), getModelList({ type: 'chat', pagesize: 100, page: 1 })]
|
||||
Promise.all(requests)
|
||||
.then(responses => {
|
||||
const [chatRes, modelRes] = responses as { items: Model[] }[]
|
||||
const chatList = chatRes.items || []
|
||||
const modelList = modelRes.items || []
|
||||
setModelList([...chatList, ...modelList])
|
||||
})
|
||||
}
|
||||
const handleAddModel = () => {
|
||||
modelConfigModalRef.current?.handleOpen('chat')
|
||||
}
|
||||
useEffect(() => {
|
||||
if (formData?.default_model_config_id && modelList.length > 0) {
|
||||
const filterValue = modelList.find(item => item.id === formData.default_model_config_id)
|
||||
setDefaultModel(filterValue as Model | null)
|
||||
setChatList([{
|
||||
label: filterValue?.name || '',
|
||||
model_config_id: filterValue?.id || '',
|
||||
model_parameters: {...(filterValue?.config || {})} as unknown as ModelConfig,
|
||||
list: []
|
||||
}])
|
||||
}
|
||||
}, [modelList, formData?.default_model_config_id])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleSave
|
||||
}))
|
||||
|
||||
return (
|
||||
<>
|
||||
{loading && <Spin fullscreen></Spin>}
|
||||
<Row className="rb:h-[calc(100vh-64px)]">
|
||||
<Col span={12} className="rb:h-full rb:overflow-x-auto rb:border-r rb:border-[#DFE4ED] rb:p-[20px_16px_24px_16px]">
|
||||
<div className="rb:flex rb:items-center rb:justify-end rb:mb-[20px]">
|
||||
<Space size={10}>
|
||||
<Button onClick={handleModelConfig} className="rb:group">
|
||||
{defaultModel?.name ? <div className="rb:w-[16px] rb:h-[16px] rb:bg-[url('@/assets/images/application/model.svg')] rb:group-hover:bg-[url('@/assets/images/application/model_hover.svg')]"></div> : null}
|
||||
{defaultModel?.name || t('application.chooseModel')}
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => handleSave()}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<Form form={form}>
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
{/* 提示词 */}
|
||||
<Card title={t('application.promptConfiguration')}>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-[11px]">
|
||||
<div className="rb:font-medium rb:leading-[20px]">
|
||||
{t('application.configuration')}
|
||||
<span className="rb:font-regular rb:text-[12px] rb:text-[#5B6167]"> ({t('application.configurationDesc')})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Form.Item name="system_prompt" className="rb:mb-[0]!">
|
||||
<Input.TextArea
|
||||
placeholder={t('application.promptPlaceholder')}
|
||||
styles={{
|
||||
textarea: {
|
||||
minHeight: '200px',
|
||||
borderRadius: '8px'
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
{/* 知识库 */}
|
||||
<Knowledge
|
||||
data={data?.knowledge_retrieval || { knowledge_bases: [] }}
|
||||
onUpdate={setKnowledgeConfig}
|
||||
/>
|
||||
|
||||
{/* 记忆配置 */}
|
||||
<Card title={t('application.memoryConfiguration')}>
|
||||
<Space size={24} direction='vertical' style={{ width: '100%' }}>
|
||||
<SwitchWrapper title="dialogueHistoricalMemory" desc="dialogueHistoricalMemoryDesc" name="memoryEnabled" />
|
||||
<SelectWrapper
|
||||
title="selectMemoryContent"
|
||||
desc="selectMemoryContentDesc"
|
||||
name="memory_content"
|
||||
url={memoryConfigListUrl}
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 变量配置 */}
|
||||
<VariableList
|
||||
data={data?.variables}
|
||||
onUpdate={setVariableList}
|
||||
/>
|
||||
{/* 工具配置 */}
|
||||
<Card title={t('application.toolConfiguration')}>
|
||||
<Space size={24} direction='vertical' style={{ width: '100%' }}>
|
||||
<SwitchWrapper title="webSearch" desc="webSearchDesc" name="webSearch" />
|
||||
{/* <SwitchWrapper title="codeExecutor" desc="codeExecutorDesc" name="codeExecutor" />
|
||||
<SwitchWrapper title="imageGeneration" desc="imageGenerationDesc" name="imageGeneration" /> */}
|
||||
</Space>
|
||||
</Card>
|
||||
</Space>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col span={12} className="rb:h-full rb:overflow-x-hidden rb:p-[20px_16px_24px_16px]">
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-[20px]">
|
||||
{t('application.debuggingAndPreview')}
|
||||
|
||||
<Space size={10}>
|
||||
<Button type="primary" ghost onClick={handleAddModel}>
|
||||
+{t('application.addModel')}
|
||||
</Button>
|
||||
<div className="rb:w-[32px] rb:h-[32px] rb:cursor-pointer rb:bg-[url('@/assets/images/application/clean.svg')]" onClick={handleClearDebugging}></div>
|
||||
</Space>
|
||||
</div>
|
||||
<RbCard height="calc(100vh - 160px)" bodyClassName="rb:p-[0]! rb:h-full rb:overflow-hidden">
|
||||
<Chat
|
||||
data={data as Config}
|
||||
chatList={chatList}
|
||||
updateChatList={setChatList}
|
||||
handleSave={handleSave}
|
||||
/>
|
||||
</RbCard>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<ModelConfigModal
|
||||
modelList={modelList}
|
||||
data={formData as Config}
|
||||
chatList={chatList}
|
||||
ref={modelConfigModalRef}
|
||||
refresh={refresh}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Agent;
|
||||
154
web/src/views/ApplicationConfig/Api.tsx
Normal file
154
web/src/views/ApplicationConfig/Api.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import { type FC, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Space, App
|
||||
// Slider, Input,
|
||||
// Form,
|
||||
// Checkbox
|
||||
} from 'antd';
|
||||
import copy from 'copy-to-clipboard'
|
||||
|
||||
import Card from './components/Card';
|
||||
// import qpsRestrictions from '@/assets/images/application/qpsRestrictions.svg'
|
||||
// import dailyAdjustmentDosage from '@/assets/images/application/dailyAdjustmentDosage.svg'
|
||||
// import tokenCap from '@/assets/images/application/tokenCap.svg'
|
||||
|
||||
// const limitList = [
|
||||
// { key: 'qpsRestrictions', value: '10', icon: qpsRestrictions, unit: ' times/second' },
|
||||
// { key: 'dailyAdjustmentDosage', value: '1000', icon: dailyAdjustmentDosage, unit: ' times/day' },
|
||||
// { key: 'tokenCap', value: '10', icon: tokenCap, unit: 'M Tokens/day' },
|
||||
// ]
|
||||
// const sdkList = ['pythonSDK', 'nodejsSDK', 'goSDK', 'curlExample']
|
||||
|
||||
const Api: FC<{apiKeyList?: string[]}> = ({apiKeyList = []}) => {
|
||||
const { t } = useTranslation();
|
||||
const [activeMethods, setActiveMethod] = useState(['GET']);
|
||||
const { message } = App.useApp()
|
||||
// const [form] = Form.useForm();
|
||||
const copyContent = window.location.origin + '/v1/chat'
|
||||
|
||||
const handleCopy = (content: string) => {
|
||||
copy(content)
|
||||
message.success(t('common.copySuccess'))
|
||||
}
|
||||
return (
|
||||
<div className="rb:w-[1000px] rb:mt-[20px] rb:pb-[20px] rb:mx-auto">
|
||||
{/* <Form form={form} layout="vertical"> */}
|
||||
<Space size={20} direction="vertical" style={{width: '100%'}}>
|
||||
<Card title={t('application.endpointConfiguration')}>
|
||||
<div className="rb:p-[20px_20px_24px_20px] rb:bg-[#F0F3F8] rb:border rb:border-[#DFE4ED] rb:rounded-[8px]">
|
||||
<Space size={8}>
|
||||
{['GET', 'POST', 'PUT', 'DELETE'].map((method) => (
|
||||
<Button key={method} type={activeMethods.includes(method) ? 'primary' : 'default'} onClick={() => setActiveMethod(prev => activeMethods.includes(method) ? prev.filter(m => m !== method) : [...prev, method])}>
|
||||
{method}
|
||||
</Button>
|
||||
))}
|
||||
</Space>
|
||||
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:text-[#5B6167] rb:mt-[20px] rb:p-[20px_16px] rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-[8px] rb:leading-[20px]">
|
||||
{copyContent}
|
||||
|
||||
<Button className="rb:px-[8px]! rb:h-[28px]! rb:group" onClick={() => handleCopy(copyContent)}>
|
||||
<div
|
||||
className="rb:w-[16px] rb:h-[16px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/copy.svg')] rb:group-hover:bg-[url('@/assets/images/copy_active.svg')]"
|
||||
></div>
|
||||
{t('common.copy')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('application.authenticationMethod')}
|
||||
// extra={
|
||||
// <Button style={{padding: '0 8px', height: '24px'}} onClick={handleAdd}>+ {t('application.addApiKey')}</Button>
|
||||
// }
|
||||
>
|
||||
<div className="rb:p-[10px_20px] rb:bg-[#F0F3F8] rb:border rb:border-[#DFE4ED] rb:rounded-[8px] rb:font-medium rb:text-center">
|
||||
{t('application.apiKeyTitle')}
|
||||
<p className="rb:mt-[6px] rb:text-[#5B6167] rb:text-[12px] rb:font-regular">{t('application.apiKeyDesc')}</p>
|
||||
</div>
|
||||
{apiKeyList.map((item, index) => (
|
||||
<div key={index} className="rb:flex rb:items-center rb:justify-between rb:text-[#5B6167] rb:mt-[20px] rb:p-[12px_16px] rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-[8px] rb:leading-[20px]">
|
||||
{item}
|
||||
|
||||
<Space>
|
||||
<Button className="rb:px-[8px]! rb:h-[28px]! rb:group" onClick={() => handleCopy(item)}>
|
||||
<div
|
||||
className="rb:w-[16px] rb:h-[16px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/copy.svg')] rb:group-hover:bg-[url('@/assets/images/copy_active.svg')]"
|
||||
></div>
|
||||
{t('common.copy')}
|
||||
</Button>
|
||||
{/* <div
|
||||
className="rb:w-[24px] rb:h-[24px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
|
||||
onClick={() => handleDelete(index)}
|
||||
></div> */}
|
||||
</Space>
|
||||
</div>
|
||||
))}
|
||||
</Card>
|
||||
{/* <Card title={t('application.requestResponseExample')}>
|
||||
<div className="rb:mb-[12px] rb:flex rb:items-center rb:justify-between rb:text-[#5B6167] rb:font-regular">
|
||||
{t('application.requestExample')}
|
||||
<Button>{t('application.downloadPostmanCollection')}</Button>
|
||||
</div>
|
||||
<div className="rb:p-[16px_20px] rb:bg-[#F0F3F8] rb:rounded-[8px] rb:text-[#5B6167] rb:leading-[18px]">
|
||||
curl -X POST https://api.example.com/v1/agent/execute \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d
|
||||
</div>
|
||||
|
||||
<div className="rb:mb-[12px] rb:mt-[24px] rb:flex rb:items-center rb:justify-between rb:text-[#5B6167] rb:font-regular">
|
||||
{t('application.responseExample')}
|
||||
</div>
|
||||
<div className="rb:p-[16px_20px] rb:bg-[#F0F3F8] rb:rounded-[8px] rb:text-[#5B6167] rb:leading-[18px]">
|
||||
curl -X POST https://api.example.com/v1/agent/execute \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d
|
||||
</div>
|
||||
</Card>
|
||||
<Card title={t('application.rateLimitingStrategy')}>
|
||||
<div className="rb:grid rb:grid-cols-3 rb:gap-[18px]">
|
||||
{limitList.map(item => (
|
||||
<div key={item.key} className="rb:border rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded-[8px] rb:p-[16px_20px]">
|
||||
<div className="rb:flex rb:justify-between">
|
||||
<div className="rb:leading-[20px]">
|
||||
{t(`application.${item.key}`)}
|
||||
<div className="rb:text-[14px] rb:font-medium rb:text-[#155EEF] rb:mt-[8px]">{item.value}{item.unit}</div>
|
||||
</div>
|
||||
<img src={item.icon} className="rb:w-[24px] rb:h-[24px]" />
|
||||
</div>
|
||||
<Slider style={{ margin: '24px 0 0 0' }} value={item.value} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card title={t('application.sdkDownload')}>
|
||||
<div className="rb:grid rb:grid-cols-4 rb:gap-[16px]">
|
||||
{sdkList.map(item => (
|
||||
<div key={item} className="rb:border rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded-[8px] rb:p-[24px_20px] rb:text-center">
|
||||
{t(`application.${item}`)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card title={t('application.advancedSettings')}>
|
||||
<Form.Item
|
||||
name="WebhookReturnsTimeout"
|
||||
label={<>{t('application.WebhookReturnsTimeout')}<span className="rb:text-[#5B6167] rb:text-[12px] rb:font-regular"> ({t('application.WebhookReturnsTimeoutDesc')})</span></>}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="whitelistIP"
|
||||
label={<>{t('application.whitelistIP')}<span className="rb:text-[#5B6167] rb:text-[12px] rb:font-regular"> ({t('application.whitelistIPDesc')})</span></>}
|
||||
>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="whitelistIP"
|
||||
className="rb:mb-[0]!"
|
||||
>
|
||||
<Checkbox>{t('application.publicAPIDocumentation')}</Checkbox>
|
||||
</Form.Item>
|
||||
</Card> */}
|
||||
</Space>
|
||||
{/* </Form> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Api;
|
||||
205
web/src/views/ApplicationConfig/Cluster.tsx
Normal file
205
web/src/views/ApplicationConfig/Cluster.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
import { type FC, useEffect, useState, useRef, type Key } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Card from './components/Card'
|
||||
import { Form, Space, Row, Col, Button, Flex, App } from 'antd'
|
||||
import type { DefaultOptionType } from 'antd/es/select'
|
||||
import Tag, { type TagProps } from './components/Tag'
|
||||
import CustomSelect from '@/components/CustomSelect';
|
||||
import { getApplicationListUrl, getMultiAgentConfig, saveMultiAgentConfig } from '@/api/application';
|
||||
import type {
|
||||
Config,
|
||||
SubAgentModalRef,
|
||||
ChatData,
|
||||
SubAgentItem
|
||||
} from './types'
|
||||
import Chat from './components/Chat'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import SubAgentModal from './components/SubAgentModal'
|
||||
import Empty from '@/components/Empty'
|
||||
|
||||
|
||||
const tagColors = ['processing', 'warning', 'default']
|
||||
const MAX_LENGTH = 5;
|
||||
const Cluster: FC<{application: SubAgentItem}> = ({application}) => {
|
||||
const { t } = useTranslation()
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm()
|
||||
const { id } = useParams()
|
||||
const subAgentModalRef = useRef<SubAgentModalRef>(null)
|
||||
const [data, setData] = useState<Config | null>(null)
|
||||
const values = Form.useWatch([], form)
|
||||
const [subAgents, setSubAgents] = useState<SubAgentItem[]>([])
|
||||
const [chatList, setChatList] = useState<ChatData[]>([
|
||||
{
|
||||
list: []
|
||||
},
|
||||
])
|
||||
|
||||
const handleSave = (flag = true) => {
|
||||
const params = {
|
||||
...values,
|
||||
sub_agents: (subAgents || []).map(item => ({
|
||||
...item,
|
||||
priority: 1,
|
||||
}))
|
||||
}
|
||||
console.log('params', params)
|
||||
form.validateFields().then(() => {
|
||||
saveMultiAgentConfig(id as string, params).then(() => {
|
||||
if (flag) {
|
||||
message.success(t('common.saveSuccess'))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
getData()
|
||||
}, [id])
|
||||
useEffect(() => {
|
||||
if (application) {
|
||||
form.setFieldsValue({
|
||||
name: application.name,
|
||||
})
|
||||
}
|
||||
}, [application])
|
||||
|
||||
const getData = () => {
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
getMultiAgentConfig(id as string).then(res => {
|
||||
const response = res as Config
|
||||
setData(response)
|
||||
form.setFieldsValue({
|
||||
...response,
|
||||
})
|
||||
setSubAgents(response.sub_agents || [])
|
||||
})
|
||||
}
|
||||
const handleSubAgentModal = (agent?: SubAgentItem) => {
|
||||
subAgentModalRef.current?.handleOpen(agent)
|
||||
}
|
||||
const refreshSubAgents = (agent: SubAgentItem) => {
|
||||
// setSubAgents(subAgents)
|
||||
const index = subAgents.findIndex(item => item.agent_id === agent.agent_id)
|
||||
const newSubAgents = [...subAgents]
|
||||
if (index === -1) {
|
||||
if (subAgents.length >= MAX_LENGTH) {
|
||||
message.warning(t('application.subAgentMaxLength', {maxLength: MAX_LENGTH}))
|
||||
return
|
||||
}
|
||||
setSubAgents([...newSubAgents, agent])
|
||||
} else {
|
||||
newSubAgents[index] = agent
|
||||
setSubAgents(newSubAgents)
|
||||
}
|
||||
}
|
||||
const handleDeleteSubAgent = (agent: SubAgentItem) => {
|
||||
setSubAgents(prev => prev.filter(item => item.agent_id !== agent.agent_id))
|
||||
}
|
||||
const handleChange = (value: Key, option?: DefaultOptionType | DefaultOptionType[] | undefined) => {
|
||||
if (option && !Array.isArray(option)) {
|
||||
form.setFieldsValue({ master_agent_name: option.children })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Row className="rb:h-[calc(100vh-64px)]">
|
||||
<Col span={12} className="rb:h-full rb:overflow-x-auto rb:border-r rb:border-[#DFE4ED] rb:p-[20px_16px_24px_16px]">
|
||||
<div className="rb:flex rb:items-center rb:justify-end rb:mb-[20px]">
|
||||
<Button type="primary" onClick={() => handleSave()}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
<Form form={form} layout="vertical">
|
||||
<Space size={20} direction="vertical" style={{width: '100%'}}>
|
||||
<Card title={t('application.supervisorAgent')}>
|
||||
<Row gutter={18}>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="master_agent_id"
|
||||
label={
|
||||
<div className="rb:font-medium">
|
||||
{t('application.agentName')}
|
||||
</div>
|
||||
}
|
||||
className="rb:mb-[20px]!"
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
<CustomSelect
|
||||
url={getApplicationListUrl}
|
||||
params={{ pagesize: 100, status: 'active', type: 'agent' }}
|
||||
valueKey="id"
|
||||
labelKey="name"
|
||||
hasAll={false}
|
||||
optionFilterProp="search"
|
||||
showSearch={true}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="master_agent_name" hidden />
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title={t('application.subAgentsManagement')}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<div className="rb:font-regular rb:text-[#5B6167] rb:leading-[20px]">{t('application.added')}: {subAgents.length}/{MAX_LENGTH}</div>
|
||||
<Button size="small" disabled={subAgents.length >= MAX_LENGTH} onClick={() => handleSubAgentModal()}>{t('application.addSubAgent')}</Button>
|
||||
</Flex>
|
||||
|
||||
{subAgents.length === 0
|
||||
? <Empty size={88} />
|
||||
: subAgents.map((agent, index) => (
|
||||
<Flex key={index} align="center" justify="space-between"
|
||||
className="rb:mt-[16px]! rb:w-full! rb:border rb:border-[#DFE4ED] rb:rounded-[8px] rb:p-[20px_31px_20px_20px]!"
|
||||
>
|
||||
<Flex className="rb:w-[calc(100%-80px)]!">
|
||||
<div className="rb:w-[48px] rb:h-[48px] rb:rounded-[8px] rb:mr-[13px] rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||
{agent.name?.[0]}
|
||||
</div>
|
||||
<div className="rb:flex rb:flex-col rb:justify-center rb:max-w-[calc(100%-60px)]">
|
||||
{agent.name}
|
||||
{agent.role && <div className="rb:font-regular rb:leading-[20px] rb:text-[#5B6167] rb:mt-[6px]">{agent.role || '-'}</div>}
|
||||
{agent.capabilities && <Flex wrap gap={8} className="rb:mt-[16px]">{agent.capabilities.map((tag, tagIndex) => <Tag key={tagIndex} color={tagColors[tagIndex % tagColors.length] as TagProps['color']}>{tag}</Tag>)}</Flex>}
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<Space>
|
||||
<div
|
||||
className="rb:w-[32px] rb:h-[32px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
||||
onClick={() => handleSubAgentModal(agent)}
|
||||
></div>
|
||||
<div
|
||||
className="rb:w-[32px] rb:h-[32px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||
onClick={() => handleDeleteSubAgent(agent)}
|
||||
></div>
|
||||
</Space>
|
||||
</Flex>
|
||||
))}
|
||||
</Card>
|
||||
</Space>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col span={12} className="rb:h-full rb:overflow-x-hidden rb:p-[20px_16px_24px_16px]">
|
||||
<RbCard height="100%" bodyClassName="rb:p-[0]! rb:h-full rb:overflow-hidden">
|
||||
<Chat
|
||||
data={data as Config}
|
||||
chatList={chatList}
|
||||
updateChatList={setChatList}
|
||||
handleSave={handleSave}
|
||||
source="cluster"
|
||||
/>
|
||||
</RbCard>
|
||||
</Col>
|
||||
|
||||
<SubAgentModal
|
||||
ref={subAgentModalRef}
|
||||
refresh={refreshSubAgents}
|
||||
/>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export default Cluster
|
||||
159
web/src/views/ApplicationConfig/ReleasePage.tsx
Normal file
159
web/src/views/ApplicationConfig/ReleasePage.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { type FC, useState, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx';
|
||||
import { Button, Space, Input, Form, App } from 'antd';
|
||||
import Tag, { type TagProps } from './components/Tag'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
import { getReleaseList, rollbackRelease } from '@/api/application'
|
||||
import ReleaseModal from './components/ReleaseModal'
|
||||
import ReleaseShareModal from './components/ReleaseShareModal'
|
||||
import type { Release, ReleaseModalRef, ReleaseShareModalRef } from './types'
|
||||
import type { Application } from '@/views/ApplicationManagement/types'
|
||||
import Empty from '@/components/Empty'
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
import Markdown from '@/components/Markdown'
|
||||
const tagColors: Record<Release['tagKey'], TagProps['color']> = {
|
||||
current: 'processing',
|
||||
rolledBack: 'warning',
|
||||
history: 'default',
|
||||
}
|
||||
|
||||
const ReleasePage: FC<{data: Application; refresh: () => void}> = ({data, refresh}) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp()
|
||||
const releaseModalRef = useRef<ReleaseModalRef>(null)
|
||||
const releaseShareModalRef = useRef<ReleaseShareModalRef>(null)
|
||||
const [selectedVersion, setSelectedVersion] = useState<Release | null>(null);
|
||||
const [releaseList, setReleaseList] = useState<Release[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
getData()
|
||||
}, [data.id])
|
||||
|
||||
const getData = () => {
|
||||
refresh()
|
||||
getReleaseList(data.id).then(res => {
|
||||
const response = res as Release[] || []
|
||||
setReleaseList(response)
|
||||
setSelectedVersion(response?.[0])
|
||||
})
|
||||
}
|
||||
const handleRollback = () => {
|
||||
if (!selectedVersion) return
|
||||
rollbackRelease(data.id, selectedVersion.version).then(() => {
|
||||
getData()
|
||||
message.success(t('common.operateSuccess'))
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="rb:flex rb:h-[calc(100vh-64px)]">
|
||||
<div className="rb:h-full rb:overflow-y-auto rb:w-[432px] rb:flex-[0_0_auto] rb:border-r-[1px] rb:border-[#DFE4ED] rb:p-[16px]">
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
<div className="rb:leading-[22px] rb:px-[4px]">
|
||||
{t('application.versionList')}
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-[4px] rb:leading-[16px]">{t('application.versionListDesc')}</div>
|
||||
</div>
|
||||
{releaseList.length === 0
|
||||
? <Empty />
|
||||
: selectedVersion && releaseList.map((version, index) => {
|
||||
const tagKey = version.id === data.current_release_id && index === 0
|
||||
? 'current'
|
||||
: version.id === data.current_release_id
|
||||
? 'rolledBack' : 'history'
|
||||
return (
|
||||
<RbCard
|
||||
key={version.version}
|
||||
title={<>
|
||||
{version.version_name || `v${version.version}`}
|
||||
{tagKey && <Tag color={tagColors[tagKey]} className="rb:ml-[8px]">
|
||||
{tagKey}
|
||||
</Tag>}
|
||||
</>}
|
||||
className={clsx("rb:hover:border-[#155EEF]! rb:cursor-pointer", {
|
||||
'rb:bg-[rgba(21,94,239,0.06)]! rb:border-[#155EEF]!': version.id === selectedVersion.id,
|
||||
'rb:border-[#DFE4ED] rb:bg-[#FBFDFF]': version.id !== selectedVersion.id
|
||||
})}
|
||||
headerType="borderless"
|
||||
onClick={() => setSelectedVersion(version)}
|
||||
>
|
||||
<div className="rb:leading-[20px] rb:line-clamp-2 rb:overflow-hidden rb:text-ellipsis rb:whitespace-nowrap">
|
||||
<Markdown content={version.release_notes} />
|
||||
</div>
|
||||
<div className="rb:mt-[16px] rb:text-[12px] rb:text-[#5B6167] rb:leading-[16px]">
|
||||
{t('application.publishedOn')} {formatDateTime(version.published_at, 'YYYY-MM-DD HH:mm:ss')}
|
||||
</div>
|
||||
<div className="rb:text-[12px] rb:text-[#5B6167] rb:mt-[4px] rb:leading-[16px]">
|
||||
{t('application.publisher')}: {version.publisher_name}
|
||||
</div>
|
||||
</RbCard>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Space>
|
||||
</div>
|
||||
<div className="rb:h-full rb:overflow-y-auto rb:flex-[1_1_auto] rb:p-[16px]">
|
||||
<Form layout="vertical">
|
||||
<div className={clsx("rb:leading-[22px] rb:px-[4px] rb:flex rb:items-center rb:text-[16px] rb:font-medium rb:mb-[21px]", {
|
||||
'rb:justify-between': selectedVersion,
|
||||
'rb:justify-end': !selectedVersion
|
||||
})}>
|
||||
{selectedVersion && t('application.DetailsOfVersion', { version: selectedVersion.version_name || `v${selectedVersion.version}` || '-' })}
|
||||
|
||||
<Space size={10}>
|
||||
{selectedVersion && <>
|
||||
{/* <Button>{t('application.exportDSLFile')}</Button> */}
|
||||
{data.current_release_id !== selectedVersion.id && <Button onClick={handleRollback}>{t('application.willRollToThisVersion')}</Button>}
|
||||
<Button type="primary" ghost onClick={() => releaseShareModalRef.current?.handleOpen()}>{t('application.share')}</Button>
|
||||
</>}
|
||||
<Button type="primary" onClick={() => releaseModalRef.current?.handleOpen()}>{t('application.release')}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
{selectedVersion &&
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
<RbCard title={t('application.VersionInformation')} headerType="borderless">
|
||||
<div className="rb:grid rb:grid-cols-3 rb:gap-[16px]">
|
||||
<Form.Item label={t('application.releaseTime')} className="rb:mb-[0]!">
|
||||
<Input value={formatDateTime(selectedVersion.published_at, 'YYYY-MM-DD HH:mm:ss')} disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('application.lastUpdateTime')} className="rb:mb-[0]!">
|
||||
<Input value={formatDateTime(selectedVersion.updated_at, 'YYYY-MM-DD HH:mm:ss')} disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label={t('application.editor')} className="rb:mb-[0]!">
|
||||
<Input value={selectedVersion.publisher_name} disabled />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</RbCard>
|
||||
|
||||
{/* 日志 */}
|
||||
<RbCard title={t('application.changeLog')} headerType="borderless">
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
{selectedVersion && (
|
||||
<RbCard
|
||||
headerType="borderBL"
|
||||
title={<div className="rb:text-[14px]">{formatDateTime(selectedVersion.published_at, 'YYYY-MM-DD HH:mm:ss')}</div>}
|
||||
extra={<span className="rb:text-[12px] rb:text-[#5B6167] rb:leading-[16px]">{selectedVersion.publisher_name}</span>}
|
||||
>
|
||||
<div className="rb:leading-[20px] rb:font-medium rb:font-regular rb:text-[12px] rb:text-[#5B6167] rb:leading-[16px]">
|
||||
<Markdown content={selectedVersion.release_notes} />
|
||||
</div>
|
||||
</RbCard>
|
||||
)}
|
||||
</Space>
|
||||
</RbCard>
|
||||
</Space>
|
||||
}
|
||||
</Form>
|
||||
</div>
|
||||
<ReleaseModal
|
||||
data={data}
|
||||
ref={releaseModalRef}
|
||||
refreshTable={getData}
|
||||
/>
|
||||
<ReleaseShareModal
|
||||
ref={releaseShareModalRef}
|
||||
version={selectedVersion}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default ReleasePage;
|
||||
81
web/src/views/ApplicationConfig/components/AiPromptModal.tsx
Normal file
81
web/src/views/ApplicationConfig/components/AiPromptModal.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Row, Col, Space, Button } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { AiPromptModalRef } from '../types'
|
||||
// import { request } from '@/utils/request'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import Markdown from '@/components/Markdown';
|
||||
|
||||
interface AiPromptModalProps {
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
||||
// refresh
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [content, setContent] = useState('');
|
||||
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
// const handleSave = () => {
|
||||
// }
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('application.AIPromptAssistant')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
footer={null}
|
||||
width={1000}
|
||||
>
|
||||
<Row className="rb:rounded-[12px] rb:border rb:border-[#DFE4ED]">
|
||||
<Col span={12} className="rb:border-r rb:border-[#DFE4ED]">
|
||||
<div className="rb:p-[12px_17px] rb:border-b rb:border-[#DFE4ED]">{t('application.generatedPrompt')}</div>
|
||||
<div className="rb:h-[200px] rb:p-[16px]">
|
||||
<div className="rb:bg-[#F0F3F8] rb:h-full rb:w-full">
|
||||
<Markdown
|
||||
content={content}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="rb:p-[12px_17px] rb:border-b rb:border-[#DFE4ED]">{t('application.conversationOptimizationPrompt')}</div>
|
||||
<div className="rb:h-[200px] rb:p-[16px]">
|
||||
<div className="rb:bg-[#F0F3F8] rb:h-full rb:w-full">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12} className="rb:border-r rb:border-[#DFE4ED]">
|
||||
<Space>
|
||||
<Button>{t('common.copy')}</Button>
|
||||
<Button type="primary">{t('common.apply')}</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
</Col>
|
||||
</Row>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default AiPromptModal;
|
||||
@@ -0,0 +1,96 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ApiExtensionModalData, ApiExtensionModalRef } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface ApiExtensionModalProps {
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
const ApiExtensionModal = forwardRef<ApiExtensionModalRef, ApiExtensionModalProps>(({
|
||||
refresh
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<ApiExtensionModalData>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const values = Form.useWatch([], form);
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
form.resetFields();
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
setLoading(true)
|
||||
console.log('values', values)
|
||||
refresh()
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('application.addApiExtension')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<FormItem
|
||||
name="name"
|
||||
label={t('application.name')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
name="apiEndpoint"
|
||||
label={t('application.apiEndpoint')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
name="apiKey"
|
||||
label={t('application.apiKey')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default ApiExtensionModal;
|
||||
27
web/src/views/ApplicationConfig/components/Card.tsx
Normal file
27
web/src/views/ApplicationConfig/components/Card.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { type FC, type ReactNode } from 'react'
|
||||
import RbCard from '@/components/RbCard/Card'
|
||||
|
||||
interface CardProps {
|
||||
title?: string | ReactNode;
|
||||
children: ReactNode;
|
||||
extra?: ReactNode;
|
||||
}
|
||||
|
||||
const Card: FC<CardProps> = ({
|
||||
title,
|
||||
children,
|
||||
extra,
|
||||
}) => {
|
||||
return (
|
||||
<RbCard
|
||||
title={title}
|
||||
extra={extra}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#155EEF]! rb:before:h-[19px]"
|
||||
>
|
||||
{children}
|
||||
</RbCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default Card
|
||||
337
web/src/views/ApplicationConfig/components/Chat.tsx
Normal file
337
web/src/views/ApplicationConfig/components/Chat.tsx
Normal file
@@ -0,0 +1,337 @@
|
||||
import { type FC, useRef, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx'
|
||||
import { Input, Form } from 'antd'
|
||||
import ChatIcon from '@/assets/images/application/chat.svg'
|
||||
import ChatSendIcon from '@/assets/images/application/chatSend.svg'
|
||||
import DebuggingEmpty from '@/assets/images/application/debuggingEmpty.svg'
|
||||
import type { ChatItem, ChatData, Config } from '../types'
|
||||
import { runCompare, draftRun } from '@/api/application'
|
||||
import Empty from '@/components/Empty'
|
||||
import Markdown from '@/components/Markdown'
|
||||
|
||||
interface ChatProps {
|
||||
chatList: ChatData[];
|
||||
data: Config;
|
||||
updateChatList: (list: ChatData[]) => void;
|
||||
handleSave: (flag?: boolean) => Promise<any>;
|
||||
source?: 'cluster' | 'agent';
|
||||
}
|
||||
const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, source = 'agent' }) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm<{ message: string }>()
|
||||
const scrollContainerRefs = useRef<(HTMLDivElement | null)[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isCluster, setIsCluster] = useState(source === 'cluster')
|
||||
const [conversationId, setConversationId] = useState<string | null>(null)
|
||||
const [compareLoading, setCompareLoading] = useState(false)
|
||||
|
||||
// 当聊天列表更新时,自动滚动到底部
|
||||
useEffect(() => {
|
||||
// 延迟一下,确保DOM已经更新
|
||||
setTimeout(() => {
|
||||
scrollContainerRefs.current.forEach(container => {
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
}, [chatList]);
|
||||
useEffect(() => {
|
||||
setIsCluster(source === 'cluster')
|
||||
}, [source])
|
||||
|
||||
const handleSend = () => {
|
||||
if (loading) return
|
||||
setLoading(true)
|
||||
setCompareLoading(true)
|
||||
handleSave(false)
|
||||
.then(() => {
|
||||
const message = form.getFieldValue('message')
|
||||
if (!message || message.trim() === '') return
|
||||
const newUserMessage: ChatItem = {
|
||||
role: 'question',
|
||||
content: message,
|
||||
time: Date.now(),
|
||||
};
|
||||
updateChatList((prev: ChatData[]) => {
|
||||
return prev.map(item => ({
|
||||
...item,
|
||||
list: [
|
||||
...(item.list || []),
|
||||
newUserMessage
|
||||
]
|
||||
}))
|
||||
})
|
||||
form.setFieldsValue({ message: undefined })
|
||||
// 添加空的助手消息用于流式更新
|
||||
const assistantMessages: Record<string, ChatItem> = {};
|
||||
if (isCluster) {
|
||||
const assistantMessage: ChatItem = {
|
||||
role: 'answer',
|
||||
content: '',
|
||||
time: Date.now(),
|
||||
};
|
||||
assistantMessages['cluster'] = assistantMessage;
|
||||
updateChatList((prev: ChatData[]) => prev.map(item => ({
|
||||
...item,
|
||||
list: [...(item.list || []), assistantMessage]
|
||||
})))
|
||||
} else {
|
||||
chatList.forEach(item => {
|
||||
const assistantMessage: ChatItem = {
|
||||
role: 'answer',
|
||||
content: '',
|
||||
time: Date.now(),
|
||||
};
|
||||
assistantMessages[item.model_config_id] = assistantMessage;
|
||||
});
|
||||
updateChatList((prev: ChatData[]) => prev.map(item => ({
|
||||
...item,
|
||||
list: [...(item.list || []), assistantMessages[item.model_config_id]]
|
||||
})))
|
||||
}
|
||||
|
||||
const handleStreamMessage = (data: string) => {
|
||||
setCompareLoading(false)
|
||||
try {
|
||||
const lines = data.split('\n');
|
||||
let currentEvent = '';
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
if (line.startsWith('event:')) {
|
||||
currentEvent = line.substring(6).trim();
|
||||
} else if (line.startsWith('data:') && (!isCluster && currentEvent === 'model_message')) {
|
||||
const jsonData = line.substring(5).trim();
|
||||
const parsed = JSON.parse(jsonData);
|
||||
|
||||
if (parsed.content && parsed.model_config_id) {
|
||||
const targetIndex = chatList.findIndex(item => item.model_config_id === parsed.model_config_id);
|
||||
if (targetIndex !== -1) {
|
||||
updateChatList((prev: ChatData[]) => prev.map((item, index) => {
|
||||
if (index === targetIndex) {
|
||||
return {
|
||||
...item,
|
||||
list: item.list?.map((msg, msgIndex) => {
|
||||
if (msgIndex === item.list!.length - 1 && msg.role === 'answer') {
|
||||
return { ...msg, content: msg.content + parsed.content };
|
||||
}
|
||||
return msg;
|
||||
}) || []
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if (parsed.conversation_id) {
|
||||
setConversationId(parsed.conversation_id);
|
||||
}
|
||||
} else if (line.startsWith('data:') && (isCluster && currentEvent === 'message')) {
|
||||
const jsonData = line.substring(5).trim();
|
||||
const parsed = JSON.parse(jsonData);
|
||||
if (parsed.content) {
|
||||
updateChatList((prev: ChatData[]) => prev.map((item, index) => {
|
||||
if (index === 0) {
|
||||
return {
|
||||
...item,
|
||||
list: item.list?.map((msg, msgIndex) => {
|
||||
if (msgIndex === item.list!.length - 1 && msg.role === 'answer') {
|
||||
return { ...msg, content: (msg.content || '') + parsed.content };
|
||||
}
|
||||
return msg;
|
||||
}) || []
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}))
|
||||
}
|
||||
if (parsed.conversation_id) {
|
||||
setConversationId(parsed.conversation_id);
|
||||
}
|
||||
} else if (line.startsWith('data:') && (!isCluster && currentEvent === 'model_end')) {
|
||||
const jsonData = line.substring(5).trim();
|
||||
const parsed = JSON.parse(jsonData);
|
||||
|
||||
if (parsed.message_length === 0 && parsed.model_config_id) {
|
||||
const targetIndex = chatList.findIndex(item => item.model_config_id === parsed.model_config_id);
|
||||
if (targetIndex !== -1) {
|
||||
updateChatList((prev: ChatData[]) => prev.map((item, index) => {
|
||||
if (index === targetIndex) {
|
||||
return {
|
||||
...item,
|
||||
list: item.list?.map((msg, msgIndex) => {
|
||||
if (msgIndex === item.list!.length - 1 && msg.role === 'answer') {
|
||||
return { ...msg, content: null };
|
||||
}
|
||||
return msg;
|
||||
}) || []
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if (parsed.conversation_id) {
|
||||
setConversationId(parsed.conversation_id);
|
||||
}
|
||||
} else if (line.startsWith('data:') && (isCluster && currentEvent === 'model_end')) {
|
||||
const jsonData = line.substring(5).trim();
|
||||
const parsed = JSON.parse(jsonData);
|
||||
if (parsed.message_length === 0) {
|
||||
updateChatList((prev: ChatData[]) => prev.map((item, index) => {
|
||||
if (index === 0) {
|
||||
return {
|
||||
...item,
|
||||
list: item.list?.map((msg, msgIndex) => {
|
||||
if (msgIndex === item.list!.length - 1 && msg.role === 'answer') {
|
||||
return { ...msg, content: null };
|
||||
}
|
||||
return msg;
|
||||
}) || []
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}))
|
||||
}
|
||||
|
||||
if (parsed.conversation_id) {
|
||||
setConversationId(parsed.conversation_id);
|
||||
}
|
||||
} else if (currentEvent === 'compare_end') {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Parse stream data error:', e);
|
||||
}
|
||||
};
|
||||
|
||||
setTimeout(() => {
|
||||
if (isCluster) {
|
||||
draftRun(data.app_id, { message, conversation_id: conversationId, stream: true }, handleStreamMessage)
|
||||
.finally(() => setLoading(false))
|
||||
} else {
|
||||
runCompare(data.app_id, {
|
||||
message,
|
||||
models: chatList.map(item => ({
|
||||
model_config_id: item.model_config_id,
|
||||
label: item.label,
|
||||
model_parameters: item.model_parameters,
|
||||
conversation_id: item.conversation_id
|
||||
})),
|
||||
variables: {},
|
||||
"parallel": true,
|
||||
"stream": true,
|
||||
"timeout": 60,
|
||||
}, handleStreamMessage)
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
}, 0)
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false)
|
||||
setCompareLoading(false)
|
||||
})
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
updateChatList(chatList.filter((_, voIndex) => voIndex !== index))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rb:relative rb:h-[calc(100vh-110px)]">
|
||||
{chatList.length === 0
|
||||
? <Empty
|
||||
url={DebuggingEmpty}
|
||||
title={t('application.debuggingEmpty')}
|
||||
subTitle={t('application.debuggingEmptyDesc')}
|
||||
className="rb:h-full"
|
||||
/>
|
||||
: <>
|
||||
<div className={clsx(`rb:grid rb:grid-cols-${chatList.length} rb:overflow-hidden rb:w-full`, {
|
||||
'rb:h-[calc(100vh-236px)]': !isCluster,
|
||||
'rb:h-[calc(100%-76px)]': isCluster,
|
||||
})}>
|
||||
{chatList.map((chat, index) => (
|
||||
<div key={index} className={clsx('rb:h-full rb:flex rb:flex-col', {
|
||||
"rb:border-r rb:border-[#DFE4ED]": index !== chatList.length - 1 && chatList.length > 1,
|
||||
})}>
|
||||
{chat.label &&
|
||||
<div className={clsx(
|
||||
"rb:grid rb:bg-[#F0F3F8] rb:text-center rb:flex-[0_0_auto]",
|
||||
{
|
||||
'rb:rounded-tr-[12px]': index === chatList.length - 1,
|
||||
'rb:rounded-tl-[12px]': index === 0,
|
||||
}
|
||||
)}>
|
||||
<div className='rb:relative rb:p-[10px_12px] rb:overflow-hidden'>
|
||||
<div className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:w-[calc(100%-24px)]">{chat.label}</div>
|
||||
<div
|
||||
className="rb:w-[16px] rb:h-[16px] rb:cursor-pointer rb:absolute rb:top-[12px] rb:right-[12px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/close.svg')] rb:hover:bg-[url('@/assets/images/close_hover.svg')]"
|
||||
onClick={() => handleDelete(index)}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!chat.list || chat.list.length === 0
|
||||
? <Empty url={ChatIcon} title={t('application.chatEmpty')} className="rb:h-full" />
|
||||
: (
|
||||
<div ref={el => scrollContainerRefs.current[index] = el} className={clsx(`rb:relative rb:overflow-y-auto rb:overflow-x-hidden`, {
|
||||
'rb:h-[calc(100vh-186px)]': isCluster,
|
||||
'rb:h-[calc(100vh-286px)]': !isCluster,
|
||||
})}>
|
||||
{chat.list?.map((vo, voIndex) => {
|
||||
if (compareLoading && voIndex === chat.list?.length - 1) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<div key={voIndex} className={clsx("rb:relative rb:mt-[24px]", {
|
||||
'rb:right-[16px] rb:text-right': vo.role === 'question',
|
||||
'rb:left-[16px] rb:text-left': vo.role !== 'question',
|
||||
})}>
|
||||
<div className="rb:text-[#5B6167] rb:text-[12px] rb:leading-[16px] rb:font-regular">{vo.role === 'question' ? 'You' : chat.label}</div>
|
||||
<div className={clsx('rb:border rb:text-left rb:rounded-[8px] rb:mt-[6px] rb:leading-[18px] rb:p-[10px_12px_2px_12px] rb:inline-block', {
|
||||
'rb:border-[rgba(255,93,52,0.30)] rb:bg-[rgba(255,93,52,0.08)] rb:text-[#FF5D34]': vo.role !== 'question' && vo.content === null,
|
||||
'rb:bg-[rgba(21,94,239,0.08)] rb:border-[rgba(21,94,239,0.30)]': vo.role === 'question' && vo.content,
|
||||
'rb:bg-[#ffffff] rb:border-[rgba(235,235,235,1)]': vo.role !== 'question' && (vo.content || vo.content === ''),
|
||||
'rb:max-w-[400px]': chatList.length === 1,
|
||||
'rb:max-w-[260px]': chatList.length === 2,
|
||||
'rb:max-w-[150px]': chatList.length === 3,
|
||||
'rb:max-w-[108px]': chatList.length === 4,
|
||||
})}>
|
||||
<Markdown content={vo.content === null ? t('application.ReplyException') : vo.content} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="rb:flex rb:items-center rb:gap-[10px] rb:p-[16px]">
|
||||
<Form form={form} style={{width: 'calc(100% - 54px)'}}>
|
||||
<Form.Item name="message" className="rb:mb-[0]!">
|
||||
<Input
|
||||
className="rb:h-[44px] rb:shadow-[0px_2px_8px_0px_rgba(33,35,50,0.1)]"
|
||||
placeholder={t('application.chatPlaceholder')}
|
||||
onPressEnter={handleSend}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<img src={ChatSendIcon} className={clsx("rb:w-[44px] rb:h-[44px] rb:cursor-pointer", {
|
||||
'rb:opacity-50': loading,
|
||||
})} onClick={handleSend} />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Chat;
|
||||
131
web/src/views/ApplicationConfig/components/ConfigHeader.tsx
Normal file
131
web/src/views/ApplicationConfig/components/ConfigHeader.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { type FC, useRef } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Layout, Tabs, Dropdown } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styles from '../index.module.css'
|
||||
import logoutIcon from '@/assets/images/logout.svg'
|
||||
import editIcon from '@/assets/images/edit_hover.svg'
|
||||
import copyIcon from '@/assets/images/copy_hover.svg'
|
||||
import exportIcon from '@/assets/images/export_hover.svg'
|
||||
import deleteIcon from '@/assets/images/delete_hover.svg'
|
||||
import type { Application, ApplicationModalRef } from '@/views/ApplicationManagement/types';
|
||||
import ApplicationModal from '@/views/ApplicationManagement/components/ApplicationModal'
|
||||
import type { CopyModalRef } from '../types'
|
||||
import { deleteApplication } from '@/api/application'
|
||||
import CopyModal from './CopyModal'
|
||||
|
||||
const { Header } = Layout;
|
||||
|
||||
const tabKeys = ['arrangement', 'api', 'release']
|
||||
const menuIcons: Record<string, string> = {
|
||||
edit: editIcon,
|
||||
copy: copyIcon,
|
||||
export: exportIcon,
|
||||
delete: deleteIcon
|
||||
}
|
||||
interface ConfigHeaderProps {
|
||||
application?: Application;
|
||||
activeTab: string;
|
||||
handleChangeTab: (key: string) => void;
|
||||
refresh: () => void;
|
||||
}
|
||||
const ConfigHeader: FC<ConfigHeaderProps> = ({ application, activeTab, handleChangeTab, refresh }) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const applicationModalRef = useRef<ApplicationModalRef>(null);
|
||||
const copyModalRef = useRef<CopyModalRef>(null);
|
||||
|
||||
const formatTabItems = () => {
|
||||
return tabKeys.map(key => ({
|
||||
key,
|
||||
label: t(`application.${key}`),
|
||||
}))
|
||||
}
|
||||
const formatMenuItems = () => {
|
||||
const items = ['edit', 'copy', 'delete'].map(key => ({
|
||||
key,
|
||||
icon: <img src={menuIcons[key]} className="rb:w-[16px] rb:h-[16px] rb:mr-[8px]" />,
|
||||
label: t(`common.${key}`),
|
||||
}))
|
||||
return {
|
||||
items,
|
||||
onClick: handleClick
|
||||
}
|
||||
}
|
||||
const handleClick: MenuProps['onClick'] = ({ key }) => {
|
||||
console.log('key', key)
|
||||
switch (key) {
|
||||
case 'edit':
|
||||
applicationModalRef.current?.handleOpen(application as Application)
|
||||
break;
|
||||
case 'copy':
|
||||
copyModalRef.current?.handleOpen()
|
||||
break;
|
||||
case 'export':
|
||||
break;
|
||||
case 'delete':
|
||||
handleDelete()
|
||||
break;
|
||||
}
|
||||
}
|
||||
const handleDelete = () => {
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
deleteApplication(id as string)
|
||||
.then(() => {
|
||||
goToApplication()
|
||||
})
|
||||
.catch(() => {
|
||||
console.error('Failed to delete application');
|
||||
});
|
||||
}
|
||||
const goToApplication = () => {
|
||||
navigate('/application', { replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header className="rb:w-full rb:h-[64px] rb:grid rb:grid-cols-3 rb:p-[16px_16px_16px_24px]! rb:border-b rb:border-[#EAECEE] rb:leading-[32px]">
|
||||
<div className="rb:h-[32px] rb:flex rb:items-center rb:font-medium">
|
||||
<div className="rb:w-[32px] rb:h-[32px] rb:rounded-[8px] rb:mr-[13px] rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[24px] rb:text-[#ffffff]">
|
||||
{application?.name[0]}
|
||||
</div>
|
||||
|
||||
<div className="rb:max-w-[100%-80px] rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{application?.name}</div>
|
||||
<Dropdown
|
||||
menu={formatMenuItems()}
|
||||
trigger={['click']}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<div
|
||||
className="rb:w-[20px] rb:h-[20px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/edit.svg')] rb:hover:bg-[url('@/assets/images/edit_hover.svg')]"
|
||||
></div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<div className="rb:flex rb:justify-center">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
items={formatTabItems()}
|
||||
onChange={handleChangeTab}
|
||||
className={styles.tabs}
|
||||
/>
|
||||
</div>
|
||||
<div className="rb:h-[32px] rb:flex rb:items-center rb:justify-end rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:cursor-pointer" onClick={goToApplication}>
|
||||
<img src={logoutIcon} className="rb:mr-[8px]" />
|
||||
{t('application.returnToApplicationList')}
|
||||
</div>
|
||||
</Header>
|
||||
<ApplicationModal
|
||||
ref={applicationModalRef}
|
||||
refresh={refresh}
|
||||
/>
|
||||
<CopyModal ref={copyModalRef} data={application as Application} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigHeader;
|
||||
87
web/src/views/ApplicationConfig/components/CopyModal.tsx
Normal file
87
web/src/views/ApplicationConfig/components/CopyModal.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type { CopyModalRef } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { copyApplication } from '@/api/application'
|
||||
import type { Application } from '@/views/ApplicationManagement/types'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface CopyModalProps {
|
||||
data: Application
|
||||
}
|
||||
|
||||
const CopyModal = forwardRef<CopyModalRef, CopyModalProps>(({
|
||||
data
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
setVisible(false);
|
||||
setLoading(true)
|
||||
const values = form.getFieldsValue()
|
||||
copyApplication(data.id, values.new_name)
|
||||
.then((res) => {
|
||||
const resData = res as Application
|
||||
navigate(`/application/config/${resData.id}`)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<RbModal
|
||||
title={t('application.copyApplication')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.copy')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
{/* 应用名 */}
|
||||
<FormItem
|
||||
name="new_name"
|
||||
label={t('application.applicationName')}
|
||||
rules={[
|
||||
{ required: true, message: t('common.pleaseEnter') },
|
||||
]}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</RbModal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default CopyModal;
|
||||
160
web/src/views/ApplicationConfig/components/Knowledge.tsx
Normal file
160
web/src/views/ApplicationConfig/components/Knowledge.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import { type FC, useRef, useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Space, Button, List } from 'antd'
|
||||
import knowledgeEmpty from '@/assets/images/application/knowledgeEmpty.svg'
|
||||
import Card from './Card'
|
||||
import type {
|
||||
KnowledgeConfigForm,
|
||||
KnowledgeConfig,
|
||||
RerankerConfig,
|
||||
KnowledgeBase,
|
||||
KnowledgeModalRef,
|
||||
KnowledgeConfigModalRef,
|
||||
KnowledgeGlobalConfigModalRef,
|
||||
} from '../types'
|
||||
import Empty from '@/components/Empty'
|
||||
import KnowledgeListModal from './KnowledgeListModal'
|
||||
import KnowledgeConfigModal from './KnowledgeConfigModal'
|
||||
import KnowledgeGlobalConfigModal from './KnowledgeGlobalConfigModal'
|
||||
import Tag from '@/components/Tag'
|
||||
|
||||
const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig) => void}> = ({data, onUpdate}) => {
|
||||
const { t } = useTranslation()
|
||||
const knowledgeModalRef = useRef<KnowledgeModalRef>(null)
|
||||
const knowledgeConfigModalRef = useRef<KnowledgeConfigModalRef>(null)
|
||||
const knowledgeGlobalConfigModalRef = useRef<KnowledgeGlobalConfigModalRef>(null)
|
||||
const [knowledgeList, setKnowledgeList] = useState<KnowledgeBase[]>([])
|
||||
const [editConfig, setEditConfig] = useState<KnowledgeConfig>({} as KnowledgeConfig)
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setEditConfig({ ...(data || {}) })
|
||||
const knowledge_bases = [...(data.knowledge_bases || [])]
|
||||
setKnowledgeList(knowledge_bases)
|
||||
onUpdate(prev => ({
|
||||
...prev,
|
||||
knowledge_bases: knowledge_bases,
|
||||
}))
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const handleKnowledgeConfig = () => {
|
||||
knowledgeGlobalConfigModalRef.current?.handleOpen()
|
||||
}
|
||||
const handleAddKnowledge = () => {
|
||||
knowledgeModalRef.current?.handleOpen()
|
||||
}
|
||||
const handleDeleteKnowledge = (id: string) => {
|
||||
const list = knowledgeList.filter(item => item.id !== id)
|
||||
setKnowledgeList([...list])
|
||||
onUpdate(prev => ({
|
||||
...prev,
|
||||
knowledge_bases: [...list],
|
||||
}))
|
||||
}
|
||||
const handleEditKnowledge = (item: KnowledgeBase) => {
|
||||
knowledgeConfigModalRef.current?.handleOpen(item)
|
||||
}
|
||||
const refresh = (values: KnowledgeBase[] | KnowledgeConfigForm | RerankerConfig, type: 'knowledge' | 'knowledgeConfig' | 'rerankerConfig') => {
|
||||
if (type === 'knowledge') {
|
||||
let list = [...knowledgeList]
|
||||
if (list.length > 0) {
|
||||
(Array.isArray(values) ? values : [values]).forEach(vo => {
|
||||
const index = list.findIndex(item => item.id === (vo as KnowledgeBase).id)
|
||||
if (index === -1) {
|
||||
list.push(vo as KnowledgeBase)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
list = [...values as KnowledgeBase[]]
|
||||
}
|
||||
setKnowledgeList([...list])
|
||||
onUpdate(prev => ({
|
||||
...prev,
|
||||
knowledge_bases: [...list],
|
||||
}))
|
||||
} else if (type === 'knowledgeConfig') {
|
||||
const index = knowledgeList.findIndex(item => item.id === (values as KnowledgeBase).kb_id)
|
||||
const list = [...knowledgeList]
|
||||
list[index] = {
|
||||
...list[index],
|
||||
config: {...values as KnowledgeConfigForm}
|
||||
}
|
||||
setKnowledgeList([...list])
|
||||
onUpdate(prev => ({
|
||||
...prev,
|
||||
knowledge_bases: [...list],
|
||||
}))
|
||||
} else if (type === 'rerankerConfig') {
|
||||
setEditConfig(prev => ({ ...prev, ...(values as RerankerConfig) }))
|
||||
onUpdate(prev => ({
|
||||
...prev,
|
||||
...values,
|
||||
reranker_id: values.rerank_model ? values.reranker_id : undefined,
|
||||
reranker_top_k: values.rerank_model ? values.reranker_top_k : undefined,
|
||||
}))
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
title={t('application.knowledgeBaseAssociation')}
|
||||
extra={
|
||||
<Button style={{padding: '0 8px', height: '24px'}} onClick={() => handleKnowledgeConfig()}>{t('application.globalConfig')}</Button>
|
||||
}
|
||||
>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-[12px]">
|
||||
<div className="rb:font-medium rb:leading-[20px]">{t('application.associatedKnowledgeBase')}</div>
|
||||
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddKnowledge}>+{t('application.addKnowledgeBase')}</Button>
|
||||
</div>
|
||||
|
||||
{knowledgeList.length === 0
|
||||
? <Empty url={knowledgeEmpty} size={88} subTitle={t('application.knowledgeEmpty')} />
|
||||
:
|
||||
<List
|
||||
grid={{ gutter: 12, column: 1 }}
|
||||
dataSource={knowledgeList}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<div key={item.id} className="rb:flex rb:items-center rb:justify-between rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-[8px]">
|
||||
<div className="rb:font-medium rb:leading-[16px]">
|
||||
{item.name}
|
||||
<Tag color={item.status === 1 ? 'success' : item.status === 0 ? 'default' : 'error'} className="rb:ml-[8px]">
|
||||
{item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')}
|
||||
</Tag>
|
||||
<div className="rb:mt-[4px] rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-[20px]">{t('application.contains', {include_count: item.doc_num})}</div>
|
||||
</div>
|
||||
<Space size={12}>
|
||||
<div
|
||||
className="rb:w-[24px] rb:h-[24px] rb:cursor-pointer rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
||||
onClick={() => handleEditKnowledge(item)}
|
||||
></div>
|
||||
<div
|
||||
className="rb:w-[24px] rb:h-[24px] rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||
onClick={() => handleDeleteKnowledge(item.id)}
|
||||
></div>
|
||||
</Space>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
{/* 全局设置 */}
|
||||
<KnowledgeGlobalConfigModal
|
||||
data={editConfig}
|
||||
ref={knowledgeGlobalConfigModalRef}
|
||||
refresh={refresh}
|
||||
/>
|
||||
{/* 知识库列表 */}
|
||||
<KnowledgeListModal
|
||||
ref={knowledgeModalRef}
|
||||
selectedList={knowledgeList}
|
||||
refresh={refresh}
|
||||
/>
|
||||
<KnowledgeConfigModal
|
||||
ref={knowledgeConfigModalRef}
|
||||
refresh={refresh}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
export default Knowledge
|
||||
@@ -0,0 +1,180 @@
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Select, InputNumber } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { KnowledgeConfigModalRef, KnowledgeBase, KnowledgeConfigForm } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import RbSlider from '@/components/RbSlider'
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface KnowledgeConfigModalProps {
|
||||
refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void;
|
||||
}
|
||||
const retrieveTypes = ['participle', 'semantic', 'hybrid']
|
||||
|
||||
const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfigModalProps>(({
|
||||
refresh,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<KnowledgeConfigForm>();
|
||||
const [data, setData] = useState<KnowledgeBase | null>(null);
|
||||
|
||||
const values = Form.useWatch<KnowledgeConfigForm>([], form);
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setData(null)
|
||||
};
|
||||
|
||||
const handleOpen = (data: KnowledgeBase) => {
|
||||
form.setFieldsValue({
|
||||
retrieve_type: retrieveTypes[0],
|
||||
kb_id: data.id,
|
||||
...(data || {}),
|
||||
...(data?.config || {}),
|
||||
})
|
||||
setData({...data})
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
refresh(values, 'knowledgeConfig')
|
||||
handleClose()
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (values?.retrieve_type) {
|
||||
const initialValues = Object.keys(values).map(key => {
|
||||
return {
|
||||
[key as keyof KnowledgeConfigForm]: (key === 'kb_id' || key === 'retrieve_type') ? values[key] : undefined
|
||||
}
|
||||
})
|
||||
form.resetFields(initialValues)
|
||||
}
|
||||
}, [values?.retrieve_type])
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('application.knowledgeConfig')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
{data && (
|
||||
<div className="rb:mb-[24px] rb:flex rb:items-center rb:justify-between rb:border rb:rounded-[8px] rb:p-[17px_16px] rb:cursor-pointer rb:bg-[#F0F3F8] rb:border-[#DFE4ED] rb:text-[#212332]">
|
||||
<div className="rb:text-[16px] rb:leading-[22px]">
|
||||
{data.name}
|
||||
<div className="rb:text-[12px] rb:leading-[16px] rb:text-[#5B6167] rb:mt-[8px]">{t('application.contains', {include_count: data.doc_num})}</div>
|
||||
</div>
|
||||
<div className="rb:text-[12px] rb:leading-[16px] rb:text-[#5B6167]">{formatDateTime(data.updated_at, 'YYYY-MM-DD HH:mm:ss')}</div>
|
||||
</div>
|
||||
)}
|
||||
<FormItem name="kb_id" hidden />
|
||||
{/* 检索模式 */}
|
||||
<FormItem
|
||||
name="retrieve_type"
|
||||
label={t('application.retrieve_type')}
|
||||
extra={t('application.retrieve_type_desc')}
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
|
||||
<Select
|
||||
options={retrieveTypes.map(key => ({
|
||||
label: t(`application.${key}`),
|
||||
value: key,
|
||||
}))}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* Top K */}
|
||||
<FormItem
|
||||
name="top_k"
|
||||
label={t('application.top_k')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
extra={t('application.top_k_desc')}
|
||||
>
|
||||
<InputNumber style={{ width: '100%' }} />
|
||||
</FormItem>
|
||||
{/* 语义相似度阈值 similarity_threshold */}
|
||||
{values?.retrieve_type === 'semantic' && (
|
||||
<FormItem
|
||||
name="similarity_threshold"
|
||||
label={t('application.similarity_threshold')}
|
||||
extra={t('application.similarity_threshold_desc')}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{/* 分词匹配度阈值 vector_similarity_weight */}
|
||||
{values?.retrieve_type === 'participle' && (
|
||||
<FormItem
|
||||
name="vector_similarity_weight"
|
||||
label={t('application.vector_similarity_weight')}
|
||||
extra={t('application.vector_similarity_weight_desc')}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{/* 混合检索权重 */}
|
||||
{values?.retrieve_type === 'hybrid' && (
|
||||
<>
|
||||
<FormItem
|
||||
name="similarity_threshold"
|
||||
label={t('application.similarity_threshold')}
|
||||
extra={t('application.similarity_threshold_desc1')}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="vector_similarity_weight"
|
||||
label={t('application.vector_similarity_weight')}
|
||||
extra={t('application.vector_similarity_weight_desc1')}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
/>
|
||||
</FormItem>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default KnowledgeConfigModal;
|
||||
@@ -0,0 +1,121 @@
|
||||
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||
import { Form, InputNumber, Switch } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { RerankerConfig, KnowledgeGlobalConfigModalRef } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
import { getModelListUrl } from '@/api/models'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface KnowledgeGlobalConfigModalProps {
|
||||
data: RerankerConfig;
|
||||
refresh: (values: RerankerConfig, type: 'rerankerConfig') => void;
|
||||
}
|
||||
|
||||
const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, KnowledgeGlobalConfigModalProps>(({
|
||||
refresh,
|
||||
data,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<RerankerConfig>();
|
||||
const values = Form.useWatch<RerankerConfig>([], form);
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
form.setFieldsValue({ ...data, rerank_model: !!data?.reranker_id })
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
refresh(values, 'rerankerConfig')
|
||||
handleClose()
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (values?.rerank_model) {
|
||||
form.setFieldsValue({ ...data })
|
||||
} else {
|
||||
form.setFieldsValue({ reranker_id: undefined, reranker_top_k: undefined })
|
||||
}
|
||||
}, [values?.rerank_model])
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('application.globalConfig')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<div className="rb:text-[#5B6167] rb:mb-[24px]">{t('application.globalConfigDesc')}</div>
|
||||
|
||||
{/* 结果重排 */}
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:my-[24px]">
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-[20px]">
|
||||
{t('application.rerankModel')}
|
||||
<div className="rb:mt-[4px] rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-[16px]">{t('application.rerankModelDesc')}</div>
|
||||
</div>
|
||||
<FormItem
|
||||
name="rerank_model"
|
||||
valuePropName="checked"
|
||||
className="rb:mb-[0px]!"
|
||||
>
|
||||
<Switch />
|
||||
</FormItem>
|
||||
</div>
|
||||
|
||||
{values?.rerank_model && <>
|
||||
<FormItem
|
||||
name="reranker_id"
|
||||
label={t('application.rearrangementModel')}
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
extra={t('application.rearrangementModelDesc')}
|
||||
>
|
||||
<CustomSelect
|
||||
url={getModelListUrl}
|
||||
params={{ type: 'rerank', pagesize: 100 }}
|
||||
valueKey="id"
|
||||
labelKey="name"
|
||||
hasAll={false}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* Top K */}
|
||||
<FormItem
|
||||
name="reranker_top_k"
|
||||
label={t('application.reranker_top_k')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
extra={t('application.reranker_top_k_desc')}
|
||||
>
|
||||
<InputNumber style={{ width: '100%' }} min={1} max={20} />
|
||||
</FormItem>
|
||||
</>}
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default KnowledgeGlobalConfigModal;
|
||||
@@ -0,0 +1,147 @@
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { Space, List } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx'
|
||||
import type { KnowledgeModalRef, KnowledgeBase } from '../types'
|
||||
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { getKnowledgeBaseList } from '@/views/KnowledgeBase/service'
|
||||
import SearchInput from '@/components/SearchInput'
|
||||
import Empty from '@/components/Empty'
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
interface KnowledgeModalProps {
|
||||
refresh: (rows: KnowledgeBase[], type: 'knowledge') => void;
|
||||
selectedList: KnowledgeBase[];
|
||||
}
|
||||
|
||||
const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
||||
refresh,
|
||||
selectedList
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [list, setList] = useState<KnowledgeBaseListItem[]>([])
|
||||
const [filterList, setFilterList] = useState<KnowledgeBaseListItem[]>([])
|
||||
const [query, setQuery] = useState<{keywords?: string}>({})
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([])
|
||||
const [selectedRows, setSelectedRows] = useState<KnowledgeBase[]>([])
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
setQuery({})
|
||||
setSelectedIds([])
|
||||
setSelectedRows([])
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
setVisible(true);
|
||||
setQuery({})
|
||||
setSelectedIds([])
|
||||
setSelectedRows([])
|
||||
getList()
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getList()
|
||||
}, [query.keywords])
|
||||
const getList = () => {
|
||||
getKnowledgeBaseList(undefined, {
|
||||
...query,
|
||||
pagesize: 100,
|
||||
orderby:'created_at',
|
||||
desc:true,
|
||||
})
|
||||
.then(res => {
|
||||
const response = res as { items: KnowledgeBaseListItem[] }
|
||||
setList(response.items || [])
|
||||
})
|
||||
}
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
refresh(selectedRows.map(item => ({
|
||||
...item,
|
||||
config: {
|
||||
similarity_threshold: 0.7,
|
||||
strategy: "hybrid",
|
||||
top_k: 3,
|
||||
weight: 1,
|
||||
}
|
||||
})), 'knowledge')
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
const handleSearch = (value?: string) => {
|
||||
setQuery({keywords: value})
|
||||
setSelectedIds([])
|
||||
setSelectedRows([])
|
||||
}
|
||||
const handleSelect = (item: KnowledgeBase) => {
|
||||
const index = selectedIds.indexOf(item.id)
|
||||
if (index === -1) {
|
||||
setSelectedIds([...selectedIds, item.id])
|
||||
setSelectedRows([...selectedRows, item])
|
||||
} else {
|
||||
setSelectedIds(selectedIds.filter(id => id !== item.id))
|
||||
setSelectedRows(selectedRows.filter(row => row.id !== item.id))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (list.length && selectedList.length) {
|
||||
const unSelectedList = list.filter(item => selectedList.findIndex(vo => vo.id === item.id) < 0)
|
||||
setFilterList([...unSelectedList])
|
||||
} else if (list.length) {
|
||||
setFilterList([...list])
|
||||
}
|
||||
}, [list, selectedList])
|
||||
|
||||
return (
|
||||
<>
|
||||
<RbModal
|
||||
title={t('application.chooseKnowledge')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
width={1000}
|
||||
>
|
||||
<Space size={24} direction="vertical" className="rb:w-full">
|
||||
<SearchInput
|
||||
placeholder={t('knowledgeBase.searchPlaceholder')}
|
||||
onSearch={handleSearch}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
{filterList.length === 0
|
||||
? <Empty />
|
||||
: <List
|
||||
grid={{ gutter: 16, column: 2 }}
|
||||
dataSource={filterList}
|
||||
renderItem={(item: KnowledgeBase) => (
|
||||
<List.Item>
|
||||
<div key={item.id} className={clsx("rb:flex rb:items-center rb:justify-between rb:border rb:rounded-[8px] rb:p-[17px_16px] rb:cursor-pointer rb:hover:bg-[#F0F3F8]", {
|
||||
"rb:bg-[rgba(21,94,239,0.06)] rb:border-[#155EEF] rb:text-[#155EEF]": selectedIds.includes(item.id),
|
||||
"rb:border-[#DFE4ED] rb:text-[#212332]": !selectedIds.includes(item.id),
|
||||
})} onClick={() => handleSelect(item)}>
|
||||
<div className="rb:text-[16px] rb:leading-[22px]">
|
||||
{item.name}
|
||||
<div className="rb:text-[12px] rb:leading-[16px] rb:text-[#5B6167] rb:mt-[8px]">{t('application.contains', {include_count: item.doc_num})}</div>
|
||||
</div>
|
||||
<div className="rb:text-[12px] rb:leading-[16px] rb:text-[#5B6167]">{formatDateTime(item.created_at, 'YYYY-MM-DD HH:mm:ss')}</div>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
</Space>
|
||||
</RbModal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default KnowledgeListModal;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { useEffect, useState, type FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Cascader } from 'antd'
|
||||
import type { CascaderProps } from 'antd';
|
||||
import { getModelProviderList } from '@/api/models'
|
||||
|
||||
interface Option {
|
||||
value?: string | number | null;
|
||||
label: React.ReactNode;
|
||||
children?: Option[];
|
||||
isLeaf?: boolean;
|
||||
}
|
||||
const CustomSelect: FC<CascaderProps> = () => {
|
||||
const { t } = useTranslation();
|
||||
const [options, setOptions] = useState<Option[]>([]);
|
||||
useEffect(() => {
|
||||
getProviderList()
|
||||
}, []);
|
||||
|
||||
const getProviderList = () => {
|
||||
getModelProviderList().then(res => {
|
||||
const response = res as string[]
|
||||
setOptions(response.map((key: string) => ({
|
||||
value: key,
|
||||
label: t(`model.${key}`),
|
||||
children: [],
|
||||
isLeaf: false,
|
||||
})))
|
||||
})
|
||||
}
|
||||
const loadData = (selectedOptions: Option[]) => {
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||
console.log(targetOption)
|
||||
}
|
||||
return (
|
||||
<Cascader
|
||||
options={options}
|
||||
loadData={loadData}
|
||||
changeOnSelect
|
||||
/>
|
||||
);
|
||||
}
|
||||
export default CustomSelect;
|
||||
147
web/src/views/ApplicationConfig/components/ModelConfigModal.tsx
Normal file
147
web/src/views/ApplicationConfig/components/ModelConfigModal.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||
import { Form, Select } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ModelConfig, ModelConfigModalRef, Config, ChatData } from '../types'
|
||||
import type { Model } from '@/views/ModelManagement/types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import RbSlider from '@/components/RbSlider'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface ModelConfigModalProps {
|
||||
modelList: Model[];
|
||||
refresh: (values: ModelConfig, type: 'model') => void;
|
||||
data: Config;
|
||||
chatList: ChatData[]
|
||||
}
|
||||
|
||||
const configFields = [
|
||||
{ key: 'temperature', max: 2, min: 0, step: 0.1, defaultValue: 0.7 },
|
||||
{ key: 'max_tokens', max: 32000, min: 256, step: 1, defaultValue: 2000 },
|
||||
{ key: 'top_p', max: 1, min: 0, step: 0.1, defaultValue: 1.0 },
|
||||
{ key: 'frequency_penalty', max: 2.0, min: -2.0, step: 0.1, defaultValue: 0.0 },
|
||||
{ key: 'presence_penalty', max: 2.0, min: -2.0, step: 0.1, defaultValue: 0.0 },
|
||||
{ key: 'n', max: 10, min: 1, step: 1, defaultValue: 1 },
|
||||
]
|
||||
|
||||
const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(({
|
||||
refresh,
|
||||
data,
|
||||
modelList
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<ModelConfig>();
|
||||
const [source, setSource] = useState<'chat' | 'model'>('model')
|
||||
|
||||
const values = Form.useWatch([], form);
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleOpen = (source: 'chat' | 'model', model) => {
|
||||
setSource(source)
|
||||
if (source === 'model') {
|
||||
form.setFieldsValue({
|
||||
...(data?.model_parameters || {}),
|
||||
default_model_config_id: data.default_model_config_id || ''
|
||||
})
|
||||
} else if (source === 'chat') {
|
||||
if (model) {
|
||||
form.setFieldsValue({
|
||||
...(model?.model_parameters || {}),
|
||||
default_model_config_id: model.default_model_config_id || ''
|
||||
})
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
...(data?.model_parameters || {}),
|
||||
default_model_config_id: undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
refresh(values, source)
|
||||
handleClose()
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('err', err)
|
||||
});
|
||||
}
|
||||
const handleChange = (value: string, option: Model) => {
|
||||
if (source === 'chat') {
|
||||
form.setFieldValue('label', option.name)
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({...(data?.model_parameters || {})})
|
||||
}, [values?.default_model_config_id])
|
||||
return (
|
||||
<RbModal
|
||||
title={t('application.modelConfig')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
cancelText={t('application.resetDefault')}
|
||||
okText={t('application.apply')}
|
||||
onOk={handleSave}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
className="rb:ml-[7px]!"
|
||||
>
|
||||
<FormItem
|
||||
name="default_model_config_id"
|
||||
label={t('application.currentModel')}
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
<Select
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
fieldNames={{
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
}}
|
||||
options={modelList}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FormItem>
|
||||
{source === 'chat' && <FormItem name="label" hidden />}
|
||||
|
||||
<div className="rb:text-[14px] rb:font-medium rb:text-[#5B6167] rb:mb-[16px]">{t('application.parameterConfig')}</div>
|
||||
|
||||
{configFields.map(item => (
|
||||
<FormItem
|
||||
key={item.key}
|
||||
name={item.key}
|
||||
label={t(`application.${item.key}`)}
|
||||
extra={t(`application.${item.key}_desc`)}
|
||||
>
|
||||
<RbSlider
|
||||
max={item.max}
|
||||
step={item.step}
|
||||
min={item.min}
|
||||
/>
|
||||
</FormItem>
|
||||
))}
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default ModelConfigModal;
|
||||
100
web/src/views/ApplicationConfig/components/ReleaseModal.tsx
Normal file
100
web/src/views/ApplicationConfig/components/ReleaseModal.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ReleaseModalRef } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { publishRelease } from '@/api/application'
|
||||
import type { Application } from '@/views/ApplicationManagement/types'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface ReleaseModalProps {
|
||||
refreshTable: () => void;
|
||||
data: Application
|
||||
}
|
||||
|
||||
const ReleaseModal = forwardRef<ReleaseModalRef, ReleaseModalProps>(({
|
||||
refreshTable,
|
||||
data
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
setVisible(true);
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form.validateFields().then(() => {
|
||||
setLoading(true)
|
||||
const values = form.getFieldsValue()
|
||||
publishRelease(data.id, values)
|
||||
.then(() => {
|
||||
handleClose()
|
||||
refreshTable()
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<RbModal
|
||||
title={t('application.releaseNewVersion')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('application.release')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
{/* 版本名 */}
|
||||
<FormItem
|
||||
name="version_name"
|
||||
label={t('application.versionName')}
|
||||
rules={[
|
||||
{ required: true, message: t('common.pleaseEnter') },
|
||||
]}
|
||||
extra={t('application.versionNameTip')}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
{/* 版本描述 */}
|
||||
<FormItem
|
||||
name="release_notes"
|
||||
label={t('application.versionDescription')}
|
||||
rules={[
|
||||
{ required: true, message: t('common.pleaseEnter') },
|
||||
]}
|
||||
extra={t('application.versionDescriptionTip')}
|
||||
>
|
||||
<Input.TextArea placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</RbModal>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default ReleaseModal;
|
||||
@@ -0,0 +1,84 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Button, App } from 'antd';
|
||||
import { ExclamationCircleFilled } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import copy from 'copy-to-clipboard'
|
||||
|
||||
import type { Release, ReleaseShareModalRef } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { shareRelease } from '@/api/application'
|
||||
import RbAlert from '@/components/RbAlert'
|
||||
|
||||
interface ReleaseShareModalProps {
|
||||
version: Release | null
|
||||
}
|
||||
|
||||
const ReleaseShareModal = forwardRef<ReleaseShareModalRef, ReleaseShareModalProps>(({
|
||||
version
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const { message } = App.useApp()
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [shareLink, setShareLink] = useState<string | null>(null)
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
if (!version) {
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
shareRelease(version?.app_id, version.id || '')
|
||||
.then(res => {
|
||||
const response = res as { share_token: string }
|
||||
if (response?.share_token) {
|
||||
setShareLink(`${window.location.origin}/#/conversation/${response?.share_token}`)
|
||||
setVisible(true);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
};
|
||||
const handleCopy = () => {
|
||||
if (!shareLink) return
|
||||
copy(shareLink)
|
||||
message.success(t('common.copySuccess'))
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={<>{t('application.shareVersion')} {version?.version}</>}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
footer={false}
|
||||
>
|
||||
<>
|
||||
<div className="rb:leading-[20px] rb:mb-[8px]">{t('application.shareLink')}</div>
|
||||
<div className="rb:mb-[12px] rb:flex rb:items-center rb:gap-[10px] rb:justify-between">
|
||||
<div className="rb:overflow-hidden rb:whitespace-nowrap rb:text-ellipsis rb:cursor-pointer rb:h-[32px] rb:p-[6px_10px] rb:bg-[#FFFFFF] rb:border rb:border-[#EBEBEB] rb:rounded-[6px] rb:leading-[20px]">{shareLink}</div>
|
||||
|
||||
<Button type="primary" loading={loading} disabled={!shareLink} onClick={handleCopy}>
|
||||
{t('common.copy')}
|
||||
</Button>
|
||||
</div>
|
||||
<RbAlert color="orange" icon={<ExclamationCircleFilled />}>
|
||||
{t('application.shareLinkTip')}
|
||||
</RbAlert>
|
||||
</>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default ReleaseShareModal;
|
||||
116
web/src/views/ApplicationConfig/components/SubAgentModal.tsx
Normal file
116
web/src/views/ApplicationConfig/components/SubAgentModal.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { forwardRef, useImperativeHandle, useState, type Key } from 'react';
|
||||
import { Form, Select, Input } from 'antd';
|
||||
import type { DefaultOptionType } from 'antd/es/select'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { SubAgentModalRef, SubAgentItem } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import CustomSelect from '@/components/CustomSelect';
|
||||
import { getApplicationListUrl } from '@/api/application';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface SubAgentModalProps {
|
||||
refresh: (agent: SubAgentItem) => void;
|
||||
}
|
||||
|
||||
const SubAgentModal = forwardRef<SubAgentModalRef, SubAgentModalProps>(({
|
||||
refresh,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [editVo, setEditVo] = useState<SubAgentItem>()
|
||||
const values = Form.useWatch([], form)
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
const handleOpen = (agent?: SubAgentItem) => {
|
||||
setVisible(true);
|
||||
form.setFieldsValue(agent)
|
||||
setEditVo(agent)
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form.validateFields().then(() => {
|
||||
setLoading(false)
|
||||
refresh(values)
|
||||
handleClose()
|
||||
})
|
||||
}
|
||||
const handleChange = (value: Key, option?: DefaultOptionType | DefaultOptionType[] | undefined) => {
|
||||
console.log(value, option)
|
||||
if (option && !Array.isArray(option)) {
|
||||
form.setFieldsValue({ name: option.children })
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t(`application.${editVo?.agent_id ? 'updateSubAgent' : 'addSubAgent'}`)}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
{/* Agent名称 */}
|
||||
<FormItem
|
||||
name="agent_id"
|
||||
label={t('application.agentName')}
|
||||
rules={[
|
||||
{ required: true, message: t('common.pleaseEnter') },
|
||||
]}
|
||||
>
|
||||
<CustomSelect
|
||||
url={getApplicationListUrl}
|
||||
params={{ pagesize: 100, status: 'active', type: 'agent' }}
|
||||
valueKey="id"
|
||||
labelKey="name"
|
||||
hasAll={false}
|
||||
optionFilterProp="search"
|
||||
showSearch={true}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem name="name" hidden />
|
||||
{/* 描述 */}
|
||||
<FormItem
|
||||
name="role"
|
||||
label={t('application.description')}
|
||||
>
|
||||
<Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||
</FormItem>
|
||||
{/* 关键词 */}
|
||||
<FormItem
|
||||
name="capabilities"
|
||||
label={t('application.capabilities')}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('common.pleaseEnter')}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default SubAgentModal;
|
||||
23
web/src/views/ApplicationConfig/components/Tag.tsx
Normal file
23
web/src/views/ApplicationConfig/components/Tag.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { type FC, type ReactNode } from 'react'
|
||||
|
||||
export interface TagProps {
|
||||
color?: 'processing' | 'warning' | 'default' | 'success';
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const colors = {
|
||||
processing: 'rb:text-[#155EEF] rb:border-[rgba(21,94,239,0.25)] rb:bg-[rgba(21,94,239,0.06)]',
|
||||
warning: 'rb:text-[#FF5D34] rb:border-[rgba(255,93,52,0.08)] rb:bg-[rgba(255,93,52,0.08)]',
|
||||
default: 'rb:text-[#5B6167] rb:border-[rgba(91,97,103,0.30)] rb:bg-[rgba(91,97,103,0.08)]',
|
||||
success: 'rb:text-[#369F21] rb:border-[rgba(54,159,33,0.30)] rb:bg-[rgba(54,159,33,0.08)]',
|
||||
}
|
||||
|
||||
const Tag: FC<TagProps> = ({ color = 'processing', children, className }) => {
|
||||
return (
|
||||
<span className={`rb:inline-block rb:px-[8px] rb:py-[2px] rb:rounded-[11px] rb:text-[12px] rb:font-regular rb:leading-[16px] rb:border-[1px] ${colors[color]} ${className || ''}`}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
export default Tag
|
||||
232
web/src/views/ApplicationConfig/components/VariableEditModal.tsx
Normal file
232
web/src/views/ApplicationConfig/components/VariableEditModal.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
||||
import { Form, Input, Select, InputNumber, Checkbox, Tag, Divider, Button } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ApiExtensionModalRef, Variable, VariableEditModalRef } from '../types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import SortableList from '@/components/SortableList'
|
||||
import ApiExtensionModal from './ApiExtensionModal'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface VariableEditModalProps {
|
||||
refreshTable: (values: Variable) => void;
|
||||
}
|
||||
|
||||
const types = [
|
||||
'text',
|
||||
'paragraph',
|
||||
// 'dropdownOptions',
|
||||
'number',
|
||||
// 'checkbox',
|
||||
// 'apiVariable'
|
||||
]
|
||||
const variableType = {
|
||||
text: 'string',
|
||||
paragraph: 'string',
|
||||
dropdownOptions: 'string',
|
||||
number: 'number',
|
||||
checkbox: 'boolean',
|
||||
apiVariable: 'string',
|
||||
}
|
||||
|
||||
const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProps>(({
|
||||
refreshTable
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<Variable>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [editVo, setEditVo] = useState<Variable | null>(null)
|
||||
const apiExtensionModalRef = useRef<ApiExtensionModalRef>(null)
|
||||
|
||||
const values = Form.useWatch([], form);
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
setEditVo(null)
|
||||
};
|
||||
|
||||
const handleOpen = (variable?: Variable) => {
|
||||
setVisible(true);
|
||||
if (variable) {
|
||||
setEditVo(variable || null)
|
||||
form.setFieldsValue(variable)
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form.validateFields().then((values) => {
|
||||
refreshTable({
|
||||
...(editVo || {}),
|
||||
...values,
|
||||
})
|
||||
handleClose()
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
// 变量类型改变时,更新初始化其他字段值
|
||||
const handleChangeType = (value: Variable['type']) => {
|
||||
if (value) {
|
||||
form.setFieldsValue({
|
||||
type: value,
|
||||
name: undefined,
|
||||
display_name: undefined,
|
||||
description: undefined,
|
||||
max_length: undefined,
|
||||
options: undefined,
|
||||
api_extension: undefined,
|
||||
// default_value: undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
// 添加 API 扩展
|
||||
const addApiExtension = () => {
|
||||
apiExtensionModalRef.current?.handleOpen()
|
||||
}
|
||||
const refreshApiExtensionList = () => {}
|
||||
|
||||
return (
|
||||
<>
|
||||
<RbModal
|
||||
title={t('application.editVariable')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
||||
>
|
||||
{/* 变量类型 */}
|
||||
<FormItem
|
||||
name="type"
|
||||
label={t('application.variableType')}
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
<Select
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
options={types.map(key => ({
|
||||
value: key,
|
||||
label: t(`application.${key}`),
|
||||
}))}
|
||||
onChange={handleChangeType}
|
||||
labelRender={(props) => <div className="rb:flex rb:justify-between rb:items-center">{props.label} <Tag color="blue">{variableType[props.value as keyof typeof variableType]}</Tag></div>}
|
||||
optionRender={(props) => <div className="rb:flex rb:justify-between rb:items-center">{props.label} <Tag color="blue">{variableType[props.value as keyof typeof variableType]}</Tag></div>}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* 变量名称 */}
|
||||
<FormItem
|
||||
name="name"
|
||||
label={t('application.variableName')}
|
||||
rules={[
|
||||
{ required: true, message: t('common.pleaseEnter') },
|
||||
{ pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: t('application.invalidVariableName') },
|
||||
]}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
{/* 显示名称 */}
|
||||
<FormItem
|
||||
name="display_name"
|
||||
label={t('application.displayName')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
{/* 描述 */}
|
||||
<FormItem
|
||||
name="description"
|
||||
label={t('application.description')}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
</FormItem>
|
||||
{/* 最大长度 */}
|
||||
{['text', 'paragraph'].includes(values?.type) && (
|
||||
<FormItem
|
||||
name="max_length"
|
||||
label={t('application.maxLength')}
|
||||
>
|
||||
<InputNumber placeholder={t('common.enter')} style={{ width: '100%' }} />
|
||||
</FormItem>
|
||||
)}
|
||||
{/* 默认值 */}
|
||||
{/* {['text', 'paragraph', 'number', 'checkbox'].includes(values?.type) && (
|
||||
<FormItem
|
||||
name="default_value"
|
||||
label={t('application.defaultValue')}
|
||||
>
|
||||
{['text'].includes(values.type) && <Input placeholder={t('common.enter')} />}
|
||||
{['paragraph'].includes(values.type) && <Input.TextArea placeholder={t('common.enter')} />}
|
||||
{['number'].includes(values.type) && <InputNumber placeholder={t('common.enter')} style={{ width: '100%' }} />}
|
||||
{['checkbox'].includes(values.type) && <Select options={[{ value: true, label: t('application.defaultChecked') }, { value: false, label: t('application.notDefaultChecked') }]} />}
|
||||
</FormItem>
|
||||
)} */}
|
||||
{/* 选项 */}
|
||||
{['dropdownOptions'].includes(values?.type) && (
|
||||
<FormItem
|
||||
name="options"
|
||||
label={t('application.options')}
|
||||
>
|
||||
<SortableList />
|
||||
</FormItem>
|
||||
)}
|
||||
{/* API 扩展 */}
|
||||
{['apiVariable'].includes(values?.type) && (
|
||||
<FormItem
|
||||
name="api_extension"
|
||||
label={t('application.apiExtension')}
|
||||
>
|
||||
<Select
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
popupRender={(menu) => (
|
||||
<>
|
||||
{menu}
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<Button type="text" block onClick={addApiExtension}>
|
||||
Add item
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{/* 是否必填 */}
|
||||
<FormItem
|
||||
name="required"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox>{t('application.required')}</Checkbox>
|
||||
</FormItem>
|
||||
{/* 是否隐藏 */}
|
||||
{/* <FormItem
|
||||
name="hidden"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox>{t('application.hidden')}</Checkbox>
|
||||
</FormItem> */}
|
||||
</Form>
|
||||
</RbModal>
|
||||
|
||||
<ApiExtensionModal
|
||||
ref={apiExtensionModalRef}
|
||||
refresh={refreshApiExtensionList}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default VariableEditModal;
|
||||
131
web/src/views/ApplicationConfig/components/VariableList.tsx
Normal file
131
web/src/views/ApplicationConfig/components/VariableList.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { type FC, useRef, useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Space, Button, Switch } from 'antd'
|
||||
import variablesEmpty from '@/assets/images/application/variablesEmpty.svg'
|
||||
import Card from './Card'
|
||||
import Table from '@/components/Table';
|
||||
import type { Variable, VariableEditModalRef } from '../types'
|
||||
import Empty from '@/components/Empty'
|
||||
import VariableEditModal from './VariableEditModal'
|
||||
|
||||
interface VariableListProps {
|
||||
data?: Variable[];
|
||||
onUpdate: (data: Variable[]) => void;
|
||||
}
|
||||
const VariableList: FC<VariableListProps> = ({data = [], onUpdate}) => {
|
||||
const { t } = useTranslation()
|
||||
const variableEditModalRef = useRef<VariableEditModalRef>(null)
|
||||
const [variableList, setVariableList] = useState<Variable[]>([])
|
||||
const [maxIndex, setMaxIndex] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || data.length === 0) return
|
||||
const list = data.map((item, index) => ({
|
||||
...item,
|
||||
index
|
||||
}))
|
||||
setVariableList(list)
|
||||
onUpdate(list)
|
||||
setMaxIndex(list.length)
|
||||
}, [data])
|
||||
|
||||
const handleAddVariable = () => {
|
||||
variableEditModalRef.current?.handleOpen()
|
||||
}
|
||||
const handleSaveVariable = (value: Variable) => {
|
||||
if (value.index !== undefined && value.index >= 0) {
|
||||
const index = variableList.findIndex(item => item.index === value.index)
|
||||
if (index !== -1) {
|
||||
const newData = [...variableList]
|
||||
newData[index] = value
|
||||
setVariableList([...newData])
|
||||
onUpdate([...newData])
|
||||
}
|
||||
} else {
|
||||
const list = [...variableList, {
|
||||
index: maxIndex + 1,
|
||||
...value
|
||||
}]
|
||||
setVariableList(list)
|
||||
onUpdate([...list])
|
||||
setMaxIndex(maxIndex + 1)
|
||||
}
|
||||
}
|
||||
const handleDeleteVariable = (index: number) => {
|
||||
const list = variableList.filter((_, i) => i !== index)
|
||||
setVariableList(list)
|
||||
onUpdate([...list])
|
||||
}
|
||||
return (
|
||||
<Card title={t('application.variableConfiguration')}>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-[11px]">
|
||||
<div className="rb:font-medium rb:leading-[20px]">
|
||||
{t('application.VariableManagement')}
|
||||
<span className="rb:font-regular rb:text-[12px] rb:text-[#5B6167]"> ({t('application.VariableManagementDesc')})</span>
|
||||
</div>
|
||||
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddVariable}>+{t('application.addVariables')}</Button>
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
{variableList.length > 0
|
||||
? (
|
||||
<div className="rb:mt-[12px]">
|
||||
<Table
|
||||
rowKey="index"
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
title: t('application.variableType'),
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
render: (type) => t(`application.${type}`)
|
||||
},
|
||||
{
|
||||
title: t('application.variableKey'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: t('application.variableName'),
|
||||
dataIndex: 'display_name',
|
||||
key: 'display_name',
|
||||
},
|
||||
{
|
||||
title: t('application.optional'),
|
||||
dataIndex: 'required',
|
||||
key: 'required',
|
||||
render: (required) => <Switch checked={!required} disabled />
|
||||
},
|
||||
{
|
||||
title: t('common.operation'),
|
||||
key: 'action',
|
||||
render: (_, record, index: number) => (
|
||||
<Space size="middle">
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => variableEditModalRef.current?.handleOpen(record as Variable)}
|
||||
>
|
||||
{t('common.edit')}
|
||||
</Button>
|
||||
<Button type="link" danger onClick={() => handleDeleteVariable(index)}>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
initialData={variableList as unknown as Record<string, unknown>[]}
|
||||
emptySize={88}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: <Empty url={variablesEmpty} size={88} subTitle={t('application.variablesEmpty')} />
|
||||
}
|
||||
<VariableEditModal
|
||||
ref={variableEditModalRef}
|
||||
refreshTable={handleSaveVariable}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
export default VariableList
|
||||
29
web/src/views/ApplicationConfig/index.module.css
Normal file
29
web/src/views/ApplicationConfig/index.module.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.tabs {
|
||||
height: 100%;
|
||||
}
|
||||
.tabs :global(.ant-tabs-nav) {
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.tabs :global(.ant-tabs-tab) {
|
||||
line-height: 20px;
|
||||
padding-bottom: 18px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.tabs :global(.ant-tabs-tab-active) {
|
||||
font-weight: 500;
|
||||
}
|
||||
.tabs :global(.ant-tabs-tab+.ant-tabs-tab) {
|
||||
margin-left: 78px;
|
||||
}
|
||||
.tabs:global(.ant-tabs-top>.ant-tabs-nav .ant-tabs-ink-bar),
|
||||
.tabs:global(.ant-tabs-top>div>.ant-tabs-nav .ant-tabs-ink-bar) {
|
||||
height: 4px;
|
||||
}
|
||||
.tabs:global(.ant-tabs-top>.ant-tabs-nav::before),
|
||||
.tabs:global(.ant-tabs-top>div>.ant-tabs-nav::before) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.tabs:global(.ant-tabs .ant-tabs-tab) {
|
||||
color: #5B6167;
|
||||
}
|
||||
61
web/src/views/ApplicationConfig/index.tsx
Normal file
61
web/src/views/ApplicationConfig/index.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import ConfigHeader from './components/ConfigHeader'
|
||||
import type { AgentRef } from './types'
|
||||
import type { Application } from '@/views/ApplicationManagement/types'
|
||||
import Agent from './Agent'
|
||||
import Api from './Api'
|
||||
import ReleasePage from './ReleasePage'
|
||||
import Cluster from './Cluster'
|
||||
import { getApplication } from '@/api/application'
|
||||
import { randomString } from '@/utils/common'
|
||||
|
||||
const apiKeyList = [`app-${randomString(24, false)}`]
|
||||
const ApplicationConfig: React.FC = () => {
|
||||
const { id } = useParams();
|
||||
const agentRef = useRef<AgentRef>(null)
|
||||
const [application, setApplication] = useState<Application | null>(null);
|
||||
const [activeTab, setActiveTab] = useState('arrangement');
|
||||
|
||||
const handleChangeTab = async (key: string) => {
|
||||
if (activeTab === 'arrangement' && application?.type === 'agent' && agentRef.current) {
|
||||
agentRef.current.handleSave(false)
|
||||
.then(() => {
|
||||
setActiveTab(key)
|
||||
})
|
||||
} else {
|
||||
setActiveTab(key)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getApplicationInfo()
|
||||
}, [id])
|
||||
|
||||
const getApplicationInfo = () => {
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
getApplication(id as string).then(res => {
|
||||
const response = res as Application
|
||||
setApplication(response)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfigHeader
|
||||
activeTab={activeTab}
|
||||
handleChangeTab={handleChangeTab}
|
||||
application={application as Application}
|
||||
refresh={getApplicationInfo}
|
||||
/>
|
||||
{activeTab === 'arrangement' && application?.type === 'agent' && <Agent ref={agentRef} />}
|
||||
{activeTab === 'arrangement' && application?.type === 'multi_agent' && <Cluster application={application as Application} />}
|
||||
{activeTab === 'api' && <Api apiKeyList={apiKeyList} />}
|
||||
{activeTab === 'release' && <ReleasePage data={application as Application} refresh={getApplicationInfo} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApplicationConfig;
|
||||
194
web/src/views/ApplicationConfig/types.ts
Normal file
194
web/src/views/ApplicationConfig/types.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
||||
|
||||
export interface ModelConfig {
|
||||
label?: string;
|
||||
default_model_config_id?: string;
|
||||
temperature: number;
|
||||
max_tokens: number;
|
||||
top_p: number;
|
||||
frequency_penalty: number;
|
||||
presence_penalty: number;
|
||||
n: number;
|
||||
stop?: string;
|
||||
}
|
||||
|
||||
/*************** 知识库相关 ******************/
|
||||
export interface RerankerConfig {
|
||||
rerank_model?: boolean | undefined;
|
||||
reranker_id?: string | undefined;
|
||||
reranker_top_k?: number | undefined;
|
||||
}
|
||||
export interface KnowledgeConfigForm {
|
||||
kb_id?: string;
|
||||
similarity_threshold?: number;
|
||||
vector_similarity_weight?: number;
|
||||
top_k?: number;
|
||||
retrieve_type?: 'participle' | 'semantic' | 'hybrid';
|
||||
}
|
||||
export interface KnowledgeBase extends KnowledgeBaseListItem, KnowledgeConfigForm {
|
||||
config?: KnowledgeConfigForm
|
||||
}
|
||||
export interface KnowledgeConfig extends RerankerConfig {
|
||||
knowledge_bases: KnowledgeBase[];
|
||||
}
|
||||
|
||||
export interface KnowledgeConfigModalRef {
|
||||
handleOpen: (data: KnowledgeBase) => void;
|
||||
}
|
||||
export interface KnowledgeGlobalConfigModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
/*********** end 知识库相关 ******************/
|
||||
|
||||
/*************** 变量相关 ******************/
|
||||
export interface Variable {
|
||||
index?: number;
|
||||
name: string;
|
||||
display_name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
max_length?: number;
|
||||
description?: string;
|
||||
|
||||
key: string;
|
||||
default_value?: string;
|
||||
options?: string[];
|
||||
api_extension?: string;
|
||||
hidden?: boolean;
|
||||
}
|
||||
export interface VariableEditModalRef {
|
||||
handleOpen: (values?: Variable) => void;
|
||||
}
|
||||
/*********** end 变量相关 ******************/
|
||||
export interface MemoryConfig {
|
||||
enabled: boolean;
|
||||
memory_content?: string;
|
||||
max_history?: number | string;
|
||||
}
|
||||
|
||||
export interface Config extends MultiAgentConfig {
|
||||
id: string;
|
||||
app_id: string;
|
||||
system_prompt: string;
|
||||
default_model_config_id?: string;
|
||||
model_parameters: ModelConfig;
|
||||
knowledge_retrieval: KnowledgeConfig | null;
|
||||
memory?: MemoryConfig;
|
||||
variables: Variable[];
|
||||
tools: {
|
||||
web_search: {
|
||||
enabled: boolean;
|
||||
config: {
|
||||
web_search: boolean;
|
||||
}
|
||||
}
|
||||
};
|
||||
is_active: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
export interface MultiAgentConfig {
|
||||
id: string;
|
||||
app_id: string;
|
||||
// system_prompt: string;
|
||||
// default_model_config_id?: string;
|
||||
// model_parameters: ModelConfig;
|
||||
// knowledge_retrieval: KnowledgeConfig | null;
|
||||
// memory?: MemoryConfig;
|
||||
// variables: Variable[];
|
||||
// tools: Record<string, string>;
|
||||
// is_active: boolean;
|
||||
// created_at: number;
|
||||
// updated_at: number;
|
||||
master_agent_id?: string;
|
||||
sub_agents?: SubAgentItem[];
|
||||
}
|
||||
|
||||
// 创建表单数据类型
|
||||
export interface ApplicationModalData {
|
||||
name: string;
|
||||
type: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
// 定义组件暴露的方法接口
|
||||
export interface AgentRef {
|
||||
handleSave: (flag?: boolean) => Promise<any>;
|
||||
}
|
||||
export interface ApplicationModalRef {
|
||||
handleOpen: (application?: Config) => void;
|
||||
}
|
||||
export interface ModelConfigModalRef {
|
||||
handleOpen: (source: 'chat' | 'model') => void;
|
||||
}
|
||||
export interface ModelConfigModalData {
|
||||
model: string;
|
||||
[key: string]: string;
|
||||
}
|
||||
export interface AiPromptModalRef {
|
||||
handleOpen: (application?: Config) => void;
|
||||
}
|
||||
export interface KnowledgeModalRef {
|
||||
handleOpen: (config?: KnowledgeConfig[]) => void;
|
||||
}
|
||||
export interface ApiExtensionModalData {
|
||||
name: string;
|
||||
apiEndpoint: string;
|
||||
apiKey: string;
|
||||
}
|
||||
export interface ApiExtensionModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
export interface ChatItem {
|
||||
role: 'answer' | 'question';
|
||||
content?: string;
|
||||
time: number;
|
||||
}
|
||||
export interface ChatData {
|
||||
label?: string;
|
||||
model_config_id?: string;
|
||||
model_parameters?: ModelConfig;
|
||||
list?: ChatItem[];
|
||||
conversation_id?: string | null;
|
||||
}
|
||||
export interface Release {
|
||||
id: string;
|
||||
app_id: string;
|
||||
version: string;
|
||||
release_notes: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
icon: string;
|
||||
icon_type?: string;
|
||||
type: string;
|
||||
visibility: string;
|
||||
config: Config;
|
||||
default_model_config_id?: string;
|
||||
published_by?: string;
|
||||
published_at: number;
|
||||
publisher_name?: string;
|
||||
is_active?: boolean;
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
status?: string;
|
||||
version_name?: string;
|
||||
tagKey: 'current' | 'rolledBack' | 'history';
|
||||
}
|
||||
export interface ReleaseModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
export interface ReleaseShareModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
export interface CopyModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
export interface SubAgentItem {
|
||||
agent_id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
capabilities: string[];
|
||||
}
|
||||
export interface SubAgentModalRef {
|
||||
handleOpen: (agent?: SubAgentItem) => void;
|
||||
}
|
||||
Reference in New Issue
Block a user