提交 4e4e165d authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 35a924d2
差异被折叠。
......@@ -14,12 +14,13 @@
"@fortaine/fetch-event-source": "^3.0.6",
"@tanstack/react-query": "^5.67.1",
"antd": "^5.24.3",
"axios": "^1.8.1",
"axios": "^1.8.3",
"blueimp-md5": "^2.19.0",
"lodash-es": "^4.17.21",
"lucide-react": "^0.477.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"openai": "^4.87.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^10.1.0",
"react-router": "^7.3.0",
"remark-gfm": "^4.0.1",
......@@ -30,17 +31,17 @@
"@types/blueimp-md5": "^2.18.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.13.9",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-react-swc": "^3.8.0",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"sass-embedded": "^1.85.1",
"typescript": "^5.2.2",
"vite": "^5.2.0",
"vite-plugin-mkcert": "^1.17.7"
"vite": "^6.2.2",
"vite-plugin-mkcert": "^1.17.8"
}
}
差异被折叠。
......@@ -55,6 +55,7 @@ const AppList = forwardRef<AppListRef, AppListProps>(
}
const pagination = {
hideOnSinglePage: true,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: [10, 20, 30, 40, 50],
......
......@@ -13,6 +13,8 @@
height: calc(100% - 56px);
}
.input-container {
padding: 10px;
background-color: #fff;
margin-top: 10px;
fill: #fff;
border: 1px solid #e0e3e6;
......@@ -21,17 +23,9 @@
stroke: #d9d9d9;
filter: drop-shadow(0px 0px 11px rgba(0, 0, 0, 0.05));
transition: border-color 0.3s;
overflow: hidden;
}
.input-box {
display: flex;
align-items: center;
padding: 10px;
background-color: #fff;
.edit-area {
flex: 1;
position: relative;
max-height: 160px;
max-height: 100px;
overflow: hidden auto;
}
.pre-prompt {
......@@ -54,11 +48,16 @@
font-size: 14px;
line-height: 30px;
}
}
.input-tools {
align-self: flex-end;
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
.ant-select {
--ant-border-radius: 15px;
}
}
}
......
import { useState, KeyboardEvent } from 'react'
import { useState, KeyboardEvent, useEffect, useRef } from 'react'
import { Button, Card, FloatButton, Select } from 'antd'
import { CircleArrowLeft, CircleArrowRight } from 'lucide-react'
import './AIChat.scss'
import { OpenAIOutlined, SendOutlined } from '@ant-design/icons'
import { OpenAIOutlined, ArrowUpOutlined } from '@ant-design/icons'
import TextArea from 'antd/es/input/TextArea'
import { useAI, AIMessage } from '@/hooks/useAI'
import Markdown from 'react-markdown'
......@@ -21,7 +21,7 @@ export const MessageItem = ({ message }: { message: AIMessage }) => {
}
export default function AIChat() {
const [collapsed, setCollapsed] = useState(false)
const [collapsed, setCollapsed] = useState(true)
const toggleCollapsed = () => {
setCollapsed(!collapsed)
......@@ -42,6 +42,14 @@ export default function AIChat() {
post({ content })
}
const messageScrollRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
if (messageScrollRef.current) {
const scrollContainer = messageScrollRef.current
scrollContainer.scrollTop = scrollContainer.scrollHeight
}
}, [isLoading])
if (collapsed) {
return (
<Card
......@@ -49,12 +57,11 @@ export default function AIChat() {
title="AI对话"
extra={<span onClick={toggleCollapsed}>{collapsed ? <CircleArrowRight /> : <CircleArrowLeft />}</span>}>
<div className="ai-chat-container">
<div className="message-scroll">
<div className="message-scroll" ref={messageScrollRef}>
{messages.map((message) => {
return <MessageItem message={message}></MessageItem>
})}
</div>
<Select value={ai} options={options} onChange={setAI} style={{ width: '100%' }}></Select>
<div className="input-container">
<div className="input-box">
<div className="edit-area">
......@@ -62,21 +69,28 @@ export default function AIChat() {
className="content"
autoSize
value={content}
placeholder="今天需要我做些什么?shift+enter换行"
placeholder="shift+enter换行"
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleEnterSearch}
/>
</div>
</div>
<div className="input-tools">
<Select
value={ai}
options={options}
onChange={setAI}
variant="filled"
suffixIcon={null}
popupMatchSelectWidth={100}></Select>
<Button
type="primary"
size="large"
icon={<SendOutlined />}
shape="circle"
icon={<ArrowUpOutlined />}
onClick={handleSearch}
loading={isLoading}
/>
</div>
</div>
<div className="upload-list"></div>
</div>
</div>
......
......@@ -17,7 +17,7 @@ export default function DataWrap({
buttons?: ReactNode
headerRender?: (data: any) => ReactNode
empty?: ReactNode
hasAI: boolean
hasAI?: boolean
}) {
const [pagination, setPagination] = useState({ page: 1, 'per-page': 100 })
const { data, isPending } = useDataQuery(pagination)
......
import { useState } from 'react'
import { Button, Radio, Divider, Flex, Modal, Steps } from 'antd'
import type { RadioChangeEvent } from 'antd'
import { useNavigate } from 'react-router'
const plainOptions = ['会员情况', '客户满意度', '出生日期', '访问页面时长', '交易状态', '商品状态', '快递反馈']
export default function ButtonModal() {
const navigate = useNavigate()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const [value, setValue] = useState(1)
const onChange = (e: RadioChangeEvent) => {
setValue(e.target.value)
}
const next = () => {
setCurrent(current + 1)
}
const prev = () => {
setCurrent(current - 1)
}
const steps = [
{
title: '探索逻辑错误字段',
},
{
title: '处理逻辑错误',
},
]
const style: React.CSSProperties = {
display: 'flex',
flexDirection: 'column',
gap: 8,
}
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
逻辑错误值
</Button>
<Modal
title="逻辑错误值探索"
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 0 && <Button type="primary">智能探索字段数据逻辑错误</Button>}
{current > 0 && (
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
下一步
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => navigate('/data/preprocess/error')}>
跳转数据预处理
</Button>
)}
</Flex>
}
destroyOnClose
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<Radio.Group options={plainOptions} value={value} onChange={onChange} style={style} />
</div>
</Modal>
</>
)
}
import { useState } from 'react'
import { Button, Radio, Divider, Flex, Modal, Steps } from 'antd'
import type { RadioChangeEvent } from 'antd'
import { useNavigate } from 'react-router'
const plainOptions = ['会员情况', '客户满意度', '出生日期', '访问页面时长', '交易状态', '商品状态', '快递反馈']
export default function ButtonModal() {
const navigate = useNavigate()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const [value, setValue] = useState(1)
const onChange = (e: RadioChangeEvent) => {
setValue(e.target.value)
}
const next = () => {
setCurrent(current + 1)
}
const prev = () => {
setCurrent(current - 1)
}
const steps = [
{
title: '探索过大值字段',
},
{
title: '处理过大值',
},
]
const style: React.CSSProperties = {
display: 'flex',
flexDirection: 'column',
gap: 8,
}
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
过大值
</Button>
<Modal
title="过大值探索"
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 0 && <Button type="primary">智能探索过大值字段</Button>}
{current > 0 && (
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
下一步
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => navigate('/data/preprocess/max')}>
跳转数据预处理
</Button>
)}
</Flex>
}
destroyOnClose
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<Radio.Group options={plainOptions} value={value} onChange={onChange} style={style} />
</div>
</Modal>
</>
)
}
import { useState } from 'react'
import { Button, Radio, Divider, Flex, Modal, Steps } from 'antd'
import type { RadioChangeEvent } from 'antd'
import { useNavigate } from 'react-router'
const plainOptions = ['会员情况', '客户满意度', '出生日期', '访问页面时长', '交易状态', '商品状态', '快递反馈']
export default function ButtonModal() {
const navigate = useNavigate()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const [value, setValue] = useState(1)
const onChange = (e: RadioChangeEvent) => {
setValue(e.target.value)
}
const next = () => {
setCurrent(current + 1)
}
const prev = () => {
setCurrent(current - 1)
}
const steps = [
{
title: '探索过小值字段',
},
{
title: '处理过小值',
},
]
const style: React.CSSProperties = {
display: 'flex',
flexDirection: 'column',
gap: 8,
}
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
过小值
</Button>
<Modal
title="过小值探索"
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 0 && <Button type="primary">智能探索过小值字段</Button>}
{current > 0 && (
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
下一步
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => navigate('/data/preprocess/min')}>
跳转数据预处理
</Button>
)}
</Flex>
}
destroyOnClose
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<Radio.Group options={plainOptions} value={value} onChange={onChange} style={style} />
</div>
</Modal>
</>
)
}
import { useState } from 'react'
import { Button, Checkbox, Divider, Flex, Modal, Steps } from 'antd'
import type { CheckboxProps } from 'antd'
import { useNavigate } from 'react-router'
const CheckboxGroup = Checkbox.Group
const plainOptions = ['会员情况', '客户满意度', '出生日期', '访问页面时长', '交易状态', '商品状态', '快递反馈']
const defaultCheckedList = ['会员情况', '客户满意度']
export default function ButtonModal() {
const navigate = useNavigate()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const [checkedList, setCheckedList] = useState<string[]>(defaultCheckedList)
const checkAll = plainOptions.length === checkedList.length
const indeterminate = checkedList.length > 0 && checkedList.length < plainOptions.length
const onChange = (list: string[]) => {
setCheckedList(list)
}
const onCheckAllChange: CheckboxProps['onChange'] = (e) => {
setCheckedList(e.target.checked ? plainOptions : [])
}
const next = () => {
setCurrent(current + 1)
}
const prev = () => {
setCurrent(current - 1)
}
const steps = [
{
title: '探索缺失值字段',
content: (
<>
<Flex justify="space-between">
探索结果:
<Checkbox indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
全选
</Checkbox>
</Flex>
<Divider dashed />
<CheckboxGroup options={plainOptions} value={checkedList} onChange={onChange} />
</>
),
},
{
title: '探索字段缺失值',
content: (
<>
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<p>字段1:该字段有缺失值的数据有:A000001、A000002...</p>
<p>字段1:该字段有缺失值的数据有:A000001、A000002...</p>
<p>字段1:该字段有缺失值的数据有:A000001、A000002...</p>
<p>字段1:该字段有缺失值的数据有:A000001、A000002...</p>
<p>字段1:该字段有缺失值的数据有:A000001、A000002...</p>
</>
),
},
{
title: '填充字段缺失值',
content: (
<>
<Flex justify="space-between">
请选择需要填充的字段:
<Checkbox indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
全选
</Checkbox>
</Flex>
<Divider dashed />
<CheckboxGroup options={plainOptions} value={checkedList} onChange={onChange} />
</>
),
},
]
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
缺失值
</Button>
<Modal
title="缺失值探索"
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 0 && <Button type="primary">智能探索缺失值字段</Button>}
{current === 0 && (
<Button type="primary" disabled={!checkedList.length} onClick={() => next()}>
进一步探索字段缺失值情况
</Button>
)}
{current > 0 && (
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
上一步
</Button>
)}
{current > 0 && current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
下一步
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => navigate('/data/preprocess/null')}>
跳转数据预处理
</Button>
)}
</Flex>
}
destroyOnClose
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<div>{steps[current].content}</div>
</div>
</Modal>
</>
)
}
import { useState, useMemo } from 'react'
import { Button, Flex, Modal, Table, Checkbox } from 'antd'
import { DownloadOutlined } from '@ant-design/icons'
export default function ButtonModal() {
const [open, setOpen] = useState(false)
const [title, setTitle] = useState('')
const handleOpen = (button: { name: string; key: string }) => {
setSelectedButtons([button.key])
setTitle(`探索` + button.name)
setOpen(true)
}
const buttons = useMemo(() => {
return [
{ name: '最大值', key: 'max' },
{ name: '最小值', key: 'min' },
{ name: '平均值', key: 'mean' },
{ name: '中位数', key: 'median' },
{ name: '众数', key: 'mode' },
{ name: '1/4位数', key: 'q1' },
{ name: '3/4位数', key: 'q3' },
{ name: '方差', key: 'variance' },
{ name: '标准差', key: 'stdDev' },
{ name: '极差', key: 'range' },
]
}, [])
const [selectedButtons, setSelectedButtons] = useState<string[]>(['max'])
const dataSource = [
{
key: '1',
name: '学段1',
max: 99,
min: '--',
mean: 50,
median: 50,
mode: 50,
q1: 25,
q3: 75,
variance: 100,
stdDev: 10,
range: 99,
},
{
key: '2',
name: '学段2',
max: 102586,
min: '--',
mean: 51293,
median: 51293,
mode: 51293,
q1: 25646,
q3: 76939,
variance: 1000000,
stdDev: 1000,
range: 102586,
},
{
key: '3',
name: '学段3',
max: 859,
min: '--',
mean: 430,
median: 430,
mode: 430,
q1: 215,
q3: 645,
variance: 10000,
stdDev: 100,
range: 859,
},
{
key: '4',
name: '学段4',
max: 1568,
min: '--',
mean: 784,
median: 784,
mode: 784,
q1: 392,
q3: 1176,
variance: 20000,
stdDev: 141,
range: 1568,
},
]
const columns = useMemo(() => {
const baseColumns = [
{
title: '序号',
dataIndex: 'key',
width: 62,
align: 'center' as const,
},
{
title: '字段名称',
dataIndex: 'name',
align: 'center' as const,
},
]
const selectedColumns = buttons
.filter((button) => selectedButtons.includes(button.key))
.map((button) => ({
title: button.name,
dataIndex: button.key,
align: 'center' as const,
}))
return [...baseColumns, ...selectedColumns]
}, [selectedButtons, buttons])
const handleButtonChange = (checkedValues: string[]) => {
setSelectedButtons(checkedValues)
}
return (
<>
{buttons.map((button) => (
<Button type="primary" key={button.key} onClick={() => handleOpen(button)}>
{button.name}
</Button>
))}
<Modal
title={title}
open={open}
footer={
<Flex justify="center" gap={20}>
<Button type="primary">一键计算</Button>
</Flex>
}
destroyOnClose
width={1000}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Flex justify="space-between" align="center" style={{ marginBottom: '20px' }}>
<div>
请选择字段:
<Button type="text" icon={<DownloadOutlined />} size="small">
导出
</Button>
</div>
<Checkbox.Group value={selectedButtons} onChange={handleButtonChange}>
{buttons.map((button) => (
<Checkbox key={button.key} value={button.key}>
{button.name}
</Checkbox>
))}
</Checkbox.Group>
</Flex>
<Table bordered dataSource={dataSource} columns={columns} pagination={false} scroll={{ x: 'max-content' }} />
</div>
</Modal>
</>
)
}
import { useState } from 'react'
import { Button, Divider, Flex, Modal, Steps } from 'antd'
import { useNavigate } from 'react-router'
export default function ButtonModal() {
const navigate = useNavigate()
const [open, setOpen] = useState(false)
const [current, setCurrent] = useState(0)
const next = () => {
setCurrent(current + 1)
}
const prev = () => {
setCurrent(current - 1)
}
const steps = [
{
title: '探索重复值',
},
{
title: '处理重复值',
},
]
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
重复值
</Button>
<Modal
title="重复值探索"
open={open}
footer={
<Flex justify="center" gap={20}>
{current === 0 && <Button type="primary">智能探索重复值字段</Button>}
{current > 0 && (
<Button style={{ margin: '0 8px' }} onClick={() => prev()}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => next()}>
下一步
</Button>
)}
{current === steps.length - 1 && (
<Button type="primary" onClick={() => navigate('/data/preprocess/repeat')}>
跳转数据预处理
</Button>
)}
</Flex>
}
destroyOnClose
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
<Steps current={current} labelPlacement="vertical" items={steps} />
<Divider />
<Flex justify="space-between">探索结果:</Flex>
<Divider dashed />
<p>1、第XX行、第XX行的数据完全一样</p>
<p>2、第XX行、第XX行、第XX行的数据完全一样</p>
</div>
</Modal>
</>
)
}
import { lazy } from 'react'
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
const NullButtonModal = lazy(() => import('../components/NullButtonModal'))
const RepeatButtonModal = lazy(() => import('../components/RepeatButtonModal'))
const MaxButtonModal = lazy(() => import('../components/MaxButtonModal'))
const MinButtonModal = lazy(() => import('../components/MinButtonModal'))
const ErrorButtonModal = lazy(() => import('../components/ErrorButtonModal'))
const OtherButtonModal = lazy(() => import('../components/OtherButtonModal'))
export default function DataWriteUpload() {
const buttonsRender = () => {
const buttons = [
{ name: '缺失值' },
{ name: '重复值' },
{ name: '过大值' },
{ name: '过小值' },
{ name: '逻辑错误值' },
{ name: '最大值' },
{ name: '最小值' },
{ name: '平均值' },
{ name: '中位数' },
{ name: '众数' },
{ name: '1/4位数' },
{ name: '3/4位数' },
{ name: '方差' },
{ name: '标准差' },
{ name: '极差' },
]
return buttons.map((item) => {
return (
<Button type="primary" key={item.name}>
{item.name}
</Button>
<DataWrap
title="数据探索"
buttons={
<>
<NullButtonModal />
<RepeatButtonModal />
<MaxButtonModal />
<MinButtonModal />
<ErrorButtonModal />
<OtherButtonModal />
</>
}></DataWrap>
)
})
}
return <DataWrap title="数据探索" buttons={<>{buttonsRender()}</>}></DataWrap>
}
import DataWrap from '@/components/data/DataWrap'
import { Button } from 'antd'
import { useAI } from '@/hooks/useAI'
import { useDataQuery } from '@/hooks/useQuery'
export default function DataWriteUpload() {
const { post } = useAI()
const { data } = useDataQuery()
function handleClick(type: number) {
if (type == 1) {
post({
content: '请仔细阅读我给你的数据集里面的数据,然后帮助我详细解释一下数据集里面每一个字段的含义。',
extra: JSON.stringify(data.list),
})
} else {
post({
content: '请仔细阅读我给你的数据集里面的数据,然后帮助我梳理数据集里面字段与字段之间的业务逻辑关系。',
extra: JSON.stringify(data.list),
})
}
}
return (
<DataWrap
title="数据理解"
buttons={
<>
<Button type="primary">一键理解数据字段</Button>
<Button type="primary">一键梳理字段关系</Button>
<Button type="primary" onClick={() => handleClick(1)}>
一键理解数据字段
</Button>
<Button type="primary" onClick={() => handleClick(2)}>
一键梳理字段关系
</Button>
</>
}></DataWrap>
)
......
......@@ -7,20 +7,23 @@ import DataRender from '@/components/data/DataRender'
import { useImportDataset } from '../query'
import { upload } from '@/utils/upload'
import { useNavigate } from 'react-router'
import { uniqueId } from 'lodash-es'
export default function DataWriteUpload() {
const [file, setFile] = useState<File | null>(null)
const [data, setData] = useState<any[]>([])
const [dataset, setDataset] = useState<any[]>([])
const columns: any =
data.length > 0
? Object.keys(data[0]).map((key) => ({
dataset.length > 0
? Object.keys(dataset[0]).map((key) => ({
title: key,
dataIndex: key,
align: 'center',
}))
: []
const dataSource = dataset.map((item) => ({ ...item, pk_id: uniqueId() }))
const props: UploadProps = {
showUploadList: false,
accept: '.xlsx,.csv',
......@@ -31,7 +34,7 @@ export default function DataWriteUpload() {
const sheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[sheetName]
const jsonData = utils.sheet_to_json(worksheet)
setData(jsonData)
setDataset(jsonData)
return false
},
......@@ -69,12 +72,12 @@ export default function DataWriteUpload() {
<Button type="primary" shape="circle" size="large" icon={<PlusOutlined />}></Button>
<span style={{ marginLeft: '10px' }}>{file?.name}</span>
</Upload>
<p>共计:{data.length}条数据</p>
<p>共计:{dataSource.length}条数据</p>
<Button type="primary" onClick={handleSave}>
保存
</Button>
</Flex>
<DataRender dataSource={data} columns={columns}></DataRender>
<DataRender rowKey={'pk_id'} dataSource={dataSource} columns={columns}></DataRender>
</div>
) : (
<Upload.Dragger {...props}>
......
import { create } from 'zustand'
import axios from 'axios'
export interface Message {
id?: string
role: 'user' | 'assistant'
content: string
}
interface State {
ai: string
setAI: (ai: string) => void
messages: Message[]
isLoading: boolean
post: (data: { content: string }) => Promise<void>
}
// Zustand 状态管理
export const useAIStore = create<State>((set, get) => ({
ai: localStorage.getItem('ai') || 'yiyan',
setAI: (ai) => {
localStorage.setItem('ai', ai)
set({ ai })
},
messages: [],
isLoading: false,
post: async (data) => {
const { ai, messages } = get()
set({ isLoading: true, messages: [...messages, { role: 'user', content: data.content }] })
try {
let response = ''
switch (ai) {
case 'yiyan':
response = await yiyan(data)
break
case 'deepseek':
response = await deepseek(data)
break
case 'qwen':
response = await qwen(data)
break
case 'tiangong':
response = await tiangong(data)
break
default:
throw new Error('未找到对应的 AI 配置')
}
set((state) => ({
messages: [...state.messages, { role: 'assistant', content: response }],
isLoading: false,
}))
} catch (err) {
console.error('AI 请求失败:', err)
set({ isLoading: false })
}
},
}))
// AI API 调用函数
async function yiyan(data: any) {
const resp = await axios.post('/api/qianfan/chat', { messages: [{ role: 'user', content: data.content }] })
return resp.data.result
}
async function deepseek(data: any) {
const resp = await axios.post('/api/deepseek/chat/completions', {
model: 'deepseek-chat',
messages: [{ role: 'user', content: data.content }],
})
return resp.data.choices[0]?.message?.content || ''
}
async function qwen(data: any) {
const resp = await axios.post('/api/qwen/chat/completions', {
model: 'qwen-max',
messages: [{ role: 'user', content: data.content }],
})
return resp.data.choices[0]?.message?.content || ''
}
async function tiangong(data: any) {
const resp = await axios.post('/api/tiangong/chat', {
chat_history: [{ role: 'user', content: data.content }],
})
return resp.data.result
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论