feat(ai): 完善知识库接口

This commit is contained in:
v-zhangjc9
2025-05-16 19:00:55 +08:00
parent be976290b6
commit 5d49c82190
22 changed files with 800 additions and 128 deletions

View File

@@ -0,0 +1,105 @@
import React from 'react'
import {useParams} from 'react-router'
import {amisRender, crudCommonOptions} from '../../../util/amis.tsx'
const DataDetail: React.FC = () => {
const {name} = useParams()
return (
<div className="import-detail h-full">
{amisRender(
{
className: 'h-full',
type: 'page',
title: `数据详情 (知识库:${name}`,
size: 'lg',
actions: [],
body: [
{
type: 'crud',
api: {
url: 'http://127.0.0.1:8080/knowledge/list_points?name=${name}',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
...crudCommonOptions(),
headerToolbar: [
'reload',
],
columns: [
{
name: 'id',
hidden: true,
},
{
name: 'text',
label: '内容',
},
{
type: 'operation',
label: '操作',
width: 100,
buttons: [
{
type: 'action',
label: '编辑',
level: 'link',
size: 'lg',
actionType: 'dialog',
dialog: {
title: '编辑文段',
size: 'md',
body: {
type: 'form',
body: [
{
type: 'input-text',
name: 'id',
disabled: true,
label: '文段ID',
},
{
type: 'editor',
label: '内容',
name: 'text',
language: 'plaintext',
options: {
lineNumbers: 'off',
wordWrap: 'bounded',
},
},
],
},
},
},
{
type: 'action',
label: '删除',
className: 'text-danger hover:text-red-600',
level: 'link',
size: 'xs',
actionType: 'ajax',
api: {
method: 'get',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
confirmText: '确认删除',
confirmTitle: '删除',
},
],
},
],
},
],
},
{
name: name,
},
)}
</div>
)
}
export default DataDetail

View File

@@ -0,0 +1,144 @@
import React from 'react'
import {useParams} from 'react-router'
import styled from 'styled-components'
import {amisRender} from '../../../util/amis.tsx'
const ImportDataDiv = styled.div`
.antd-EditorControl {
min-height: 500px !important;
}
`
const DataImport: React.FC = () => {
const {name} = useParams()
return (
<ImportDataDiv className="import-data h-full">
{amisRender({
type: 'page',
title: `数据导入 (知识库:${name}`,
body: [
[
{
className: 'h-full',
type: 'grid',
columns: [
{
body: [
{
type: 'form',
wrapWithPanel: false,
mode: 'horizontal',
actions: [],
body: [
{
name: 'mode',
type: 'radios',
label: '解析模式',
value: 'normal',
options: [
{
value: 'normal',
label: '常规模式',
},
{
value: 'llm',
label: '智能模式',
},
{
value: 'qa',
label: 'Q/A模式',
},
],
},
{
name: 'type',
type: 'radios',
label: '数据形式',
value: 'text',
options: [
{
value: 'text',
label: '文本',
},
{
value: 'file',
label: '文件',
},
],
},
{
visibleOn: 'type === \'text\'',
type: 'editor',
label: '数据内容',
name: 'content',
language: 'plaintext',
options: {
lineNumbers: 'off',
wordWrap: 'bounded',
},
},
{
visibleOn: 'type === \'file\'',
type: 'input-file',
name: 'files',
label: '数据文件',
accept: '.txt,.csv',
autoUpload: false,
drag: true,
multiple: true,
},
{
className: 'text-right',
type: 'button-toolbar',
buttons: [
{
type: 'action',
label: '预览',
},
{
type: 'submit',
label: '提交',
level: 'primary',
},
],
},
],
},
],
},
{
body: [
{
type: 'card',
className: 'h-full',
header: {
title: '解析预览',
subTitle: '截取部份文本进行解析预览',
},
body: [
{
type: 'list',
source: '${rows}',
listItem: [
{
body: {
type: 'tpl',
tpl: '${content}',
},
},
],
},
],
},
],
},
],
},
],
],
})}
</ImportDataDiv>
)
}
export default DataImport

View File

@@ -0,0 +1,176 @@
import React from 'react'
import {useNavigate} from 'react-router'
import {amisRender, crudCommonOptions, mappingField, mappingItem} from '../../../util/amis.tsx'
const strategyMapping = [
mappingItem('文本', 'Cosine'),
mappingItem('图片', 'Euclid'),
]
const statusMapping = [
mappingItem('正常', 'Green', 'label-success'),
mappingItem('优化中', 'Yellow', 'label-warning'),
mappingItem('错误', 'Red', 'label-danger'),
mappingItem('等待中', 'Grey', 'label-primary'),
]
const Knowledge: React.FC = () => {
const navigate = useNavigate()
return (
<div className="knowledge">
{amisRender(
{
type: 'page',
title: '知识库',
body: [
{
type: 'crud',
api: {
url: 'http://127.0.0.1:8080/knowledge/list',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
...crudCommonOptions(),
headerToolbar: [
'reload',
{
type: 'action',
label: '',
icon: 'fa fa-plus',
actionType: 'dialog',
dialog: {
title: '新增知识库',
size: 'md',
body: {
type: 'form',
api: {
url: 'http://127.0.0.1:8080/knowledge/add',
dataType: 'form',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
body: [
{
type: 'input-text',
name: 'name',
label: '名称',
},
{
type: 'select',
name: 'strategy',
label: '类型',
value: 'Cosine',
options: [
{
label: '文本',
value: 'Cosine',
},
{
label: '图片',
value: 'Euclid',
disabled: true,
},
],
},
],
},
},
},
],
columns: [
{
name: 'name',
label: '名称',
},
{
label: '类型',
width: 80,
align: 'center',
...mappingField('strategy', strategyMapping),
},
{
name: 'points',
label: '文本数',
width: 80,
align: 'center',
},
{
label: '状态',
width: 80,
align: 'center',
...mappingField('status', statusMapping),
},
{
type: 'operation',
label: '操作',
width: 150,
buttons: [
{
type: 'action',
label: '详情',
level: 'link',
size: 'xs',
onEvent: {
click: {
actions: [
{
actionType: 'custom',
// @ts-ignore
script: (context, action, event) => {
navigate(`/ai/knowledge/detail/${context.props.data['name']}`)
},
},
],
},
},
},
{
type: 'action',
label: '导入',
level: 'link',
size: 'xs',
onEvent: {
click: {
actions: [
{
actionType: 'custom',
// @ts-ignore
script: (context, action, event) => {
navigate(`/ai/knowledge/import/${context.props.data['name']}`)
},
},
],
},
},
},
{
type: 'action',
label: '删除',
className: 'text-danger hover:text-red-600',
level: 'link',
size: 'xs',
actionType: 'ajax',
api: {
method: 'get',
url: 'http://127.0.0.1:8080/knowledge/delete?name=${name}',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
confirmText: '确认删除',
confirmTitle: '删除',
},
],
},
],
},
],
},
)}
</div>
)
}
export default Knowledge

View File

@@ -6,7 +6,7 @@ import {
serviceLogByAppName,
serviceLogByAppNameAndHost,
time,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
const cloudCrud = (title: string, path: string) => {
return {

View File

@@ -1,5 +1,5 @@
import React from 'react'
import {amisRender, commonInfo, crudCommonOptions, readOnlyDialogOptions} from '../../util/amis.ts'
import {amisRender, commonInfo, crudCommonOptions, readOnlyDialogOptions} from '../../util/amis.tsx'
const color = (number: number) => {
let color = 'text-success'

View File

@@ -6,7 +6,7 @@ import {
paginationCommonOptions,
time,
yarnQueueCrud,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
const queueCrud = (name: string) => {
return {

View File

@@ -14,7 +14,7 @@ import {
tableMetaDialog,
tableRunningStateMapping,
timeAndFrom,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
function Table() {
return (

View File

@@ -1,5 +1,5 @@
import React from 'react'
import {amisRender, commonInfo, paginationCommonOptions, serviceLogByAppName, yarnCrudColumns} from '../../util/amis.ts'
import {amisRender, commonInfo, paginationCommonOptions, serviceLogByAppName, yarnCrudColumns} from '../../util/amis.tsx'
const Task: React.FC = () => {
return (

View File

@@ -10,7 +10,7 @@ import {
paginationCommonOptions,
readOnlyDialogOptions,
timelineColumns,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
const Tool: React.FC = () => {
return (

View File

@@ -10,7 +10,7 @@ import {
paginationCommonOptions,
tableMetaDialog,
versionUpdateStateMapping,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
function Version() {
return (

View File

@@ -7,7 +7,7 @@ import {
paginationCommonOptions,
yarnCrudColumns,
yarnQueueCrud,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
const Yarn: React.FC = () => {
const {clusters, queue, search} = useParams()

View File

@@ -1,5 +1,5 @@
import React from 'react'
import {amisRender, commonInfo, yarnQueueCrud} from '../../util/amis.ts'
import {amisRender, commonInfo, yarnQueueCrud} from '../../util/amis.tsx'
const YarnCluster: React.FC = () => {
return (

View File

@@ -3,6 +3,7 @@ import {
CloudOutlined,
ClusterOutlined,
CompressOutlined,
DatabaseOutlined,
InfoCircleOutlined,
OpenAIOutlined,
QuestionOutlined,
@@ -14,6 +15,9 @@ import {
import {Navigate, type RouteObject} from 'react-router'
import Conversation from './pages/ai/Conversation.tsx'
import Inspection from './pages/ai/Inspection.tsx'
import DataDetail from './pages/ai/knowledge/DataDetail.tsx'
import DataImport from './pages/ai/knowledge/DataImport.tsx'
import Knowledge from './pages/ai/knowledge/Knowledge.tsx'
import App from './pages/App.tsx'
import Cloud from './pages/overview/Cloud.tsx'
import Overview from './pages/overview/Overview.tsx'
@@ -24,7 +28,7 @@ import Tool from './pages/overview/Tool.tsx'
import Version from './pages/overview/Version.tsx'
import Yarn from './pages/overview/Yarn.tsx'
import YarnCluster from './pages/overview/YarnCluster.tsx'
import {commonInfo} from './util/amis.ts'
import {commonInfo} from './util/amis.tsx'
export const routes: RouteObject[] = [
{
@@ -86,6 +90,18 @@ export const routes: RouteObject[] = [
path: 'conversation',
Component: Conversation,
},
{
path: 'knowledge',
Component: Knowledge,
},
{
path: 'knowledge/import/:name',
Component: DataImport,
},
{
path: 'knowledge/detail/:name',
Component: DataDetail,
},
],
},
],
@@ -179,6 +195,11 @@ export const menus = {
name: '智能巡检',
icon: <CheckSquareOutlined/>,
},
{
path: '/ai/knowledge',
name: '知识库',
icon: <DatabaseOutlined/>,
},
],
},
],

View File

@@ -1,4 +1,4 @@
import {attachmentAdpator, makeTranslator, render, type Schema} from 'amis'
import {AlertComponent, attachmentAdpator, makeTranslator, render, type Schema, ToastComponent} from 'amis'
import 'amis/lib/themes/antd.css'
import 'amis/lib/helper.css'
@@ -58,85 +58,99 @@ const responseAdaptor = () => (response: any) => {
}
}
export const amisRender = (schema: Schema) => {
return render(
schema,
{
theme: 'antd',
},
{
fetcher: async (api: any) => {
let {url, method, data, responseType, config, headers} = api
config = config || {}
config.url = url
config.withCredentials = true
responseType && (config.responseType = responseType)
export const amisRender = (schema: Schema, data: Record<any, any> = {}) => {
const theme = 'antd'
const locale = 'zh-CN'
return (
<>
<ToastComponent
theme={theme}
key="toast"
position={'top-right'}
locale={locale}
/>
<AlertComponent theme={theme} key="alert" locale={locale}/>
{render(
schema,
{
data: data,
theme: theme,
},
{
fetcher: async (api: any) => {
let {url, method, data, responseType, config, headers} = api
config = config || {}
config.url = url
config.withCredentials = true
responseType && (config.responseType = responseType)
if (config.cancelExecutor) {
config.cancelToken = new (axios as any).CancelToken(
config.cancelExecutor,
)
}
config.headers = headers || {}
config.method = method
config.data = data
if (method === 'get' && data) {
config.params = data
} else if (data && data instanceof FormData) {
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data)
config.headers['Content-Type'] = 'application/json'
}
// 支持返回各种报错信息
config.validateStatus = function () {
return true
}
let response = await axios(config)
response = await attachmentAdpator(response, __, api)
response = responseAdaptor()(response)
if (response.status >= 400) {
if (response.data) {
// 主要用于 raw: 模式下,后端自己校验登录,
if (
response.status === 401 &&
response.data.location &&
response.data.location.startsWith('http')
) {
location.href = response.data.location.replace(
'{{redirect}}',
encodeURIComponent(location.href),
)
return new Promise(() => {
})
} else if (response.data.msg) {
throw new Error(response.data.msg)
} else {
throw new Error(
'System.requestError' + JSON.stringify(response.data, null, 2),
if (config.cancelExecutor) {
config.cancelToken = new (axios as any).CancelToken(
config.cancelExecutor,
)
}
} else {
throw new Error(
`${'System.requestErrorStatus'} ${response.status}`,
)
}
}
return response
},
isCancel: (value: any) => (axios as any).isCancel(value),
},
config.headers = headers || {}
config.method = method
config.data = data
if (method === 'get' && data) {
config.params = data
} else if (data && data instanceof FormData) {
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data)
config.headers['Content-Type'] = 'application/json'
}
// 支持返回各种报错信息
config.validateStatus = function () {
return true
}
let response = await axios(config)
response = await attachmentAdpator(response, __, api)
response = responseAdaptor()(response)
if (response.status >= 400) {
if (response.data) {
// 主要用于 raw: 模式下,后端自己校验登录,
if (
response.status === 401 &&
response.data.location &&
response.data.location.startsWith('http')
) {
location.href = response.data.location.replace(
'{{redirect}}',
encodeURIComponent(location.href),
)
return new Promise(() => {
})
} else if (response.data.msg) {
throw new Error(response.data.msg)
} else {
throw new Error(
'System.requestError' + JSON.stringify(response.data, null, 2),
)
}
} else {
throw new Error(
`${'System.requestErrorStatus'} ${response.status}`,
)
}
}
return response
},
isCancel: (value: any) => (axios as any).isCancel(value),
},
)}
</>
)
}