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

chore: update

上级 f41baf12
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^6.0.0", "@ant-design/icons": "^6.0.0",
"@ant-design/x": "^1.1.1", "@ant-design/x": "^1.1.1",
"@antv/g2": "^5.2.12",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
...@@ -28,6 +27,7 @@ ...@@ -28,6 +27,7 @@
"echarts-wordcloud": "^2.1.0", "echarts-wordcloud": "^2.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lucide-react": "^0.484.0", "lucide-react": "^0.484.0",
"normalize.css": "^8.0.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-error-boundary": "^5.0.0", "react-error-boundary": "^5.0.0",
......
body, @import 'normalize.css';
h1,
h2,
h3,
h4,
h5,
h6,
hr,
p,
blockquote,
dl,
dt,
dd,
ul,
ol,
li,
pre,
form,
fieldset,
legend,
button,
input,
textarea,
th,
td {
margin: 0;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: 100%;
}
ul,
ol,
li {
list-style: none;
}
em,
i {
font-style: normal;
}
strong,
b {
font-weight: normal;
}
img {
border: none;
}
input,
img {
vertical-align: middle;
}
a {
color: inherit;
text-decoration: none;
}
input,
button,
select,
textarea {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
appearance: none;
border: 0;
border-radius: 0;
font: inherit;
}
textarea:focus {
outline: 0;
}
html, html,
body, body,
#root { #root {
...@@ -139,3 +64,9 @@ body, ...@@ -139,3 +64,9 @@ body,
color: #fff; color: #fff;
} }
} }
.ai-thinking {
margin-bottom: 20px;
padding-left: 16px;
border-left: 1px solid rgba(0, 0, 0, 0.45);
}
...@@ -4,34 +4,35 @@ import { AIData, AIMessage } from './types' ...@@ -4,34 +4,35 @@ import { AIData, AIMessage } from './types'
import { extractJSON } from '@/utils/helper' import { extractJSON } from '@/utils/helper'
import { sseRequest, SSEOptions } from '@/utils/sseRequest' import { sseRequest, SSEOptions } from '@/utils/sseRequest'
function updateTransform(res: any): AIMessage | null { function transform(messages: any[]): AIMessage {
try { return messages.reduce(
const message = JSON.parse(res) (result, message) => {
let delta = null
if (message.choices && message.choices.length > 0) { if (message.choices && message.choices.length > 0) {
const delta = message.choices[0].delta delta = message.choices[0].delta
}
const content = result.content + (delta.content || '')
const reasoning_content = result.reasoning_content + (delta.reasoning_content || '')
let full_content = ''
if (reasoning_content) {
full_content = `<div class="ai-thinking">${reasoning_content}`
if (content) {
full_content += `</div>${content}`
}
} else {
full_content = content
}
return { return {
id: message.id, id: message.id,
role: 'assistant', role: 'assistant',
content: delta.content || '', content,
reasoning_content: delta.reasoning_content || '', reasoning_content,
} full_content,
json: extractJSON(content),
} }
} catch (error) { },
console.error(error)
}
return null
}
function successTransform(messages: AIMessage[]) {
const result = messages.reduce(
(acc, curr) => ({
...acc,
content: acc.content + curr.content,
reasoning_content: acc.reasoning_content + curr.reasoning_content,
}),
{ content: '', reasoning_content: '' } { content: '', reasoning_content: '' }
) )
return { ...result, json: extractJSON(result.content) }
} }
// 文心一言 // 文心一言
...@@ -47,8 +48,7 @@ export async function yiyan(data: AIData, options: SSEOptions) { ...@@ -47,8 +48,7 @@ export async function yiyan(data: AIData, options: SSEOptions) {
...options, ...options,
body: JSON.stringify({ stream: true, ...data }), body: JSON.stringify({ stream: true, ...data }),
}, },
updateTransform, transform
successTransform
) )
} }
...@@ -61,8 +61,7 @@ export async function deepseek(data: AIData, options: SSEOptions) { ...@@ -61,8 +61,7 @@ export async function deepseek(data: AIData, options: SSEOptions) {
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer sk-f1a6f0a7013241de8393cb2cb108e777' }, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer sk-f1a6f0a7013241de8393cb2cb108e777' },
body: JSON.stringify({ model: 'deepseek-reasoner', stream: true, ...data }), body: JSON.stringify({ model: 'deepseek-reasoner', stream: true, ...data }),
}, },
updateTransform, transform
successTransform
) )
} }
...@@ -78,8 +77,7 @@ export async function siliconflow(data: AIData, options: SSEOptions) { ...@@ -78,8 +77,7 @@ export async function siliconflow(data: AIData, options: SSEOptions) {
}, },
body: JSON.stringify({ model: 'deepseek-ai/DeepSeek-R1', stream: true, ...data }), body: JSON.stringify({ model: 'deepseek-ai/DeepSeek-R1', stream: true, ...data }),
}, },
updateTransform, transform
successTransform
) )
} }
...@@ -92,8 +90,7 @@ export async function qwen(data: AIData, options: SSEOptions) { ...@@ -92,8 +90,7 @@ export async function qwen(data: AIData, options: SSEOptions) {
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer sk-afd0fcdb53bf4058b2068b8548820150' }, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer sk-afd0fcdb53bf4058b2068b8548820150' },
body: JSON.stringify({ model: 'qwen-max-latest', stream: true, ...data }), body: JSON.stringify({ model: 'qwen-max-latest', stream: true, ...data }),
}, },
updateTransform, transform
successTransform
) )
} }
...@@ -126,8 +123,7 @@ export async function openAI(data: AIData, options: SSEOptions) { ...@@ -126,8 +123,7 @@ export async function openAI(data: AIData, options: SSEOptions) {
headers: { 'Content-Type': 'application/json', Authorization: 'ezijing@20250331' }, headers: { 'Content-Type': 'application/json', Authorization: 'ezijing@20250331' },
body: JSON.stringify({ stream: true, ...data }), body: JSON.stringify({ stream: true, ...data }),
}, },
updateTransform, transform
successTransform
) )
} }
...@@ -150,7 +146,7 @@ const aiService = { ...@@ -150,7 +146,7 @@ const aiService = {
openAI, openAI,
} }
const provider = providers[data.model as keyof typeof providers] || qwen const provider = providers[data.model as keyof typeof providers] || openAI
await provider(data, options) await provider(data, options)
}, },
} }
......
...@@ -8,8 +8,9 @@ export interface AIMessage { ...@@ -8,8 +8,9 @@ export interface AIMessage {
role: 'user' | 'assistant' | 'system' role: 'user' | 'assistant' | 'system'
content: string content: string
reasoning_content?: string reasoning_content?: string
full_content?: string
json?: any json?: any
loading?: boolean status?: 'loading' | 'success' | 'error'
} }
export interface AIData { export interface AIData {
......
...@@ -12,11 +12,6 @@ export function useAI(globalOptions?: SSEOptions) { ...@@ -12,11 +12,6 @@ export function useAI(globalOptions?: SSEOptions) {
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const controllerRef = useRef<AbortController | null>(null) const controllerRef = useRef<AbortController | null>(null)
// 使用 useRef 存储最新消息,避免闭包问题
const latestMessageRef = useRef<AIMessage | null>(null)
// 使用 useRef 存储消息列表,避免异步更新问题
const messagesRef = useRef<AIMessage[]>([])
useEffect(() => { useEffect(() => {
localStorage.setItem('ai', ai) localStorage.setItem('ai', ai)
}, [ai]) }, [ai])
...@@ -47,7 +42,6 @@ export function useAI(globalOptions?: SSEOptions) { ...@@ -47,7 +42,6 @@ export function useAI(globalOptions?: SSEOptions) {
// 添加用户消息 // 添加用户消息
const userMessages = data.messages.filter((item) => item.role !== 'system') const userMessages = data.messages.filter((item) => item.role !== 'system')
messagesRef.current = [...messagesRef.current, ...userMessages]
setMessages((prev) => [...prev, ...userMessages]) setMessages((prev) => [...prev, ...userMessages])
aiService.post( aiService.post(
...@@ -55,31 +49,13 @@ export function useAI(globalOptions?: SSEOptions) { ...@@ -55,31 +49,13 @@ export function useAI(globalOptions?: SSEOptions) {
{ {
signal: controllerRef.current.signal, signal: controllerRef.current.signal,
onUpdate: (message) => { onUpdate: (message) => {
const messageIndex = messagesRef.current.findIndex((msg) => msg.id === message.id)
if (messageIndex === -1) {
// 新消息
latestMessageRef.current = message
messagesRef.current = [...messagesRef.current, message]
setMessage(message) setMessage(message)
setMessages(messagesRef.current) setMessages((prev) => {
} else { const messageIndex = prev.findIndex((item) => item.id === message.id)
// 更新现有消息 return messageIndex === -1
messagesRef.current = messagesRef.current.map((msg) => { ? [...prev, message]
if (msg.id === message.id) { : prev.map((item) => (item.id === message.id ? message : item))
const updatedMessage = {
...msg,
content: msg.content + message.content,
reasoning_content: msg.reasoning_content + message.reasoning_content,
}
latestMessageRef.current = updatedMessage
setMessage(updatedMessage)
return updatedMessage
}
return msg
}) })
setMessages([...messagesRef.current])
}
}, },
onSuccess: (message) => { onSuccess: (message) => {
controllerRef.current = null controllerRef.current = null
......
...@@ -54,25 +54,13 @@ export const useAIStore = create<AIState>((set, get) => ({ ...@@ -54,25 +54,13 @@ export const useAIStore = create<AIState>((set, get) => ({
{ {
signal: controller.signal, signal: controller.signal,
onUpdate: (message) => { onUpdate: (message) => {
console.log(message)
set((state) => { set((state) => {
const messageIndex = state.messages.findIndex((msg) => msg.id === message.id) const messageIndex = state.messages.findIndex((item) => item.id === message.id)
const messages =
if (messageIndex === -1) { messageIndex === -1
return { message, messages: [...state.messages, message] } ? [...state.messages, message]
} else { : state.messages.map((msg) => (msg.id === message.id ? message : msg))
const updatedMessages = state.messages.map((msg) => return { message, messages }
msg.id === message.id
? {
...msg,
content: msg.content + message.content,
reasoning_content: msg.reasoning_content + message.reasoning_content,
}
: msg
)
return { message: updatedMessages[messageIndex], messages: updatedMessages }
}
}) })
}, },
onSuccess: (message) => { onSuccess: (message) => {
......
...@@ -41,12 +41,10 @@ $table-bg-color: #f6f8fa; ...@@ -41,12 +41,10 @@ $table-bg-color: #f6f8fa;
font-size: 1.25em; font-size: 1.25em;
} }
// 段落样式
p { p {
margin: 1em 0; margin: 0;
} }
// 列表样式
ul, ul,
ol { ol {
padding-left: 2em; padding-left: 2em;
......
...@@ -65,7 +65,7 @@ const transformData = (data: any[], yField: string[], yRule: string, ySort: stri ...@@ -65,7 +65,7 @@ const transformData = (data: any[], yField: string[], yRule: string, ySort: stri
: { all: transformedData } : { all: transformedData }
// Calculate results for each group // Calculate results for each group
transformedData = Object.entries(groupedData).map(([_, group]) => { transformedData = Object.entries(groupedData).map(([, group]) => {
const newItem = { ...group[0] } const newItem = { ...group[0] }
yField.forEach((field) => { yField.forEach((field) => {
const values = group.map((d) => parseFloat(d[field]) || 0) const values = group.map((d) => parseFloat(d[field]) || 0)
...@@ -134,6 +134,7 @@ export default function Chart({ ...@@ -134,6 +134,7 @@ export default function Chart({
style = { height: '400px', width: '100%' }, style = { height: '400px', width: '100%' },
yRule = '', yRule = '',
ySort = '', ySort = '',
fieldColors = [],
...props ...props
}) { }) {
const { data } = useDataQuery() const { data } = useDataQuery()
...@@ -147,6 +148,13 @@ export default function Chart({ ...@@ -147,6 +148,13 @@ export default function Chart({
const datalist = transformData(data.list, yField, yRule, ySort, xField) const datalist = transformData(data.list, yField, yRule, ySort, xField)
const dataset = { dimensions: [xField, ...yField].filter(Boolean), source: datalist } const dataset = { dimensions: [xField, ...yField].filter(Boolean), source: datalist }
const fieldColorMap = fieldColors.reduce((acc: Record<string, string>, item: any) => {
if (item.field && item.color) {
acc[item.field] = item.color
}
return acc
}, {})
let defaultOptions: any = { let defaultOptions: any = {
legend: {}, legend: {},
tooltip: {}, tooltip: {},
...@@ -172,7 +180,7 @@ export default function Chart({ ...@@ -172,7 +180,7 @@ export default function Chart({
return datalist[params.dataIndex][labelField] return datalist[params.dataIndex][labelField]
}, },
}, },
itemStyle: { borderRadius, color: itemStyleColor }, itemStyle: { borderRadius, color: fieldColorMap[field] || itemStyleColor },
})), })),
}) })
break break
...@@ -198,7 +206,8 @@ export default function Chart({ ...@@ -198,7 +206,8 @@ export default function Chart({
dataset, dataset,
xAxis: { show: false }, xAxis: { show: false },
yAxis: { show: false }, yAxis: { show: false },
series: yField.map(() => ({ series: yField.map((field) => ({
name: getFieldName(field),
type: 'pie', type: 'pie',
label: { label: {
show: !!labelField, show: !!labelField,
...@@ -384,7 +393,6 @@ export default function Chart({ ...@@ -384,7 +393,6 @@ export default function Chart({
show: !!labelField, show: !!labelField,
position: 'top', position: 'top',
formatter: (params: any) => { formatter: (params: any) => {
console.log(params.dataIndex)
return datalist[params.dataIndex][labelField] return datalist[params.dataIndex][labelField]
}, },
}, },
......
...@@ -4,6 +4,7 @@ import { useDataFieldQuery } from '@/hooks/useQuery' ...@@ -4,6 +4,7 @@ import { useDataFieldQuery } from '@/hooks/useQuery'
import { useCreateChart, useUpdateChart, useViewChartQuery } from '@/hooks/useChartQuery' import { useCreateChart, useUpdateChart, useViewChartQuery } from '@/hooks/useChartQuery'
import { useAI } from '@/ai/useAI' import { useAI } from '@/ai/useAI'
import Chart from './Chart' import Chart from './Chart'
import ColorField from './ColorField'
interface Props { interface Props {
id?: string id?: string
...@@ -59,7 +60,6 @@ const Step1 = ({ fieldOptions, numberFields, type }: any) => { ...@@ -59,7 +60,6 @@ const Step1 = ({ fieldOptions, numberFields, type }: any) => {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
{!['11', '10'].includes(type) && (
<Row gutter={20}> <Row gutter={20}>
<Col span={8}> <Col span={8}>
<Form.Item label='请选择"维度"字段' name="x"> <Form.Item label='请选择"维度"字段' name="x">
...@@ -67,7 +67,6 @@ const Step1 = ({ fieldOptions, numberFields, type }: any) => { ...@@ -67,7 +67,6 @@ const Step1 = ({ fieldOptions, numberFields, type }: any) => {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
)}
<Row gutter={20}> <Row gutter={20}>
<Col span={8}> <Col span={8}>
<Form.Item label="是否显示行轴" name="showX" hidden={!['1', '2'].includes(type)}> <Form.Item label="是否显示行轴" name="showX" hidden={!['1', '2'].includes(type)}>
...@@ -85,7 +84,7 @@ const Step1 = ({ fieldOptions, numberFields, type }: any) => { ...@@ -85,7 +84,7 @@ const Step1 = ({ fieldOptions, numberFields, type }: any) => {
} }
// 辅助可视化设置 // 辅助可视化设置
const Step2 = ({ fieldOptions, type, showTitle }: any) => { const Step2 = ({ labelOptions, type, showTitle }: any) => {
return ( return (
<> <>
<Divider orientation="left" orientationMargin="0"> <Divider orientation="left" orientationMargin="0">
...@@ -95,16 +94,22 @@ const Step2 = ({ fieldOptions, type, showTitle }: any) => { ...@@ -95,16 +94,22 @@ const Step2 = ({ fieldOptions, type, showTitle }: any) => {
{!['11'].includes(type) && ( {!['11'].includes(type) && (
<Col span={12}> <Col span={12}>
<Form.Item label='请选择"标签"字段' name="labelField"> <Form.Item label='请选择"标签"字段' name="labelField">
<Select options={fieldOptions} placeholder="请选择" allowClear></Select> <Select options={labelOptions} placeholder="请选择" allowClear></Select>
</Form.Item> </Form.Item>
</Col> </Col>
)} )}
<Col span={12}> <Col span={12}>
<Form.Item label="请选择颜色规则" name="color"> <Form.Item label="请选择颜色规则">
<Form.Item name="color" noStyle>
<Radio.Group options={[{ label: '自动颜色', value: 'auto' }]}></Radio.Group> <Radio.Group options={[{ label: '自动颜色', value: 'auto' }]}></Radio.Group>
</Form.Item> </Form.Item>
<Form.Item name="fieldColors" noStyle>
<ColorField fieldOptions={labelOptions} />
</Form.Item>
</Form.Item>
</Col> </Col>
</Row> </Row>
<Row gutter={20}> <Row gutter={20}>
<Col span={12}> <Col span={12}>
<Flex> <Flex>
...@@ -256,7 +261,15 @@ const WordCloudStep = ({ fieldOptions, numberFields, showTitle }: any) => { ...@@ -256,7 +261,15 @@ const WordCloudStep = ({ fieldOptions, numberFields, showTitle }: any) => {
) )
} }
const FormStep = ({ type, fieldOptions, numberFields, showTitle }: any) => { const FormStep = ({
type,
fieldOptions,
labelOptions,
numberFields,
showTitle,
onFieldColorsChange,
currentFieldColors,
}: any) => {
if (type === '12') { if (type === '12') {
return <TableStep fieldOptions={fieldOptions} /> return <TableStep fieldOptions={fieldOptions} />
} else if (type === '9') { } else if (type === '9') {
...@@ -267,7 +280,14 @@ const FormStep = ({ type, fieldOptions, numberFields, showTitle }: any) => { ...@@ -267,7 +280,14 @@ const FormStep = ({ type, fieldOptions, numberFields, showTitle }: any) => {
return ( return (
<> <>
<Step1 fieldOptions={fieldOptions} numberFields={numberFields} type={type} /> <Step1 fieldOptions={fieldOptions} numberFields={numberFields} type={type} />
<Step2 fieldOptions={fieldOptions} type={type} showTitle={showTitle} /> <Step2
fieldOptions={fieldOptions}
labelOptions={labelOptions}
type={type}
showTitle={showTitle}
onFieldColorsChange={onFieldColorsChange}
currentFieldColors={currentFieldColors}
/>
</> </>
) )
} }
...@@ -304,6 +324,8 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => { ...@@ -304,6 +324,8 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => {
const fillPattern = Form.useWatch('fillPattern', form) const fillPattern = Form.useWatch('fillPattern', form)
const yRule = Form.useWatch('yRule', form) const yRule = Form.useWatch('yRule', form)
const ySort = Form.useWatch('ySort', form) const ySort = Form.useWatch('ySort', form)
const fieldColors = Form.useWatch('fieldColors', form) || []
const config = { const config = {
title: { show: showTitle, text: title }, title: { show: showTitle, text: title },
legend: { show: showLegend }, legend: { show: showLegend },
...@@ -317,9 +339,15 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => { ...@@ -317,9 +339,15 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => {
fillPattern, fillPattern,
yRule, yRule,
ySort, ySort,
fieldColors,
...results, ...results,
} }
console.log(config)
const labelOptions = fieldOptions.filter((item: any) => {
return item.value === xField || yField?.includes(item.value)
})
const { post } = useAI() const { post } = useAI()
const handlePreview = async () => { const handlePreview = async () => {
...@@ -406,13 +434,20 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => { ...@@ -406,13 +434,20 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => {
showLegend: true, showLegend: true,
showTitle: false, showTitle: false,
fillPattern: 'solid', fillPattern: 'solid',
yRule: '', yRule: ['6', '7'].includes(type) ? '' : 'sum',
ySort: '', ySort: '',
}}> }}>
<Form.Item label="组件名称" name="name" rules={[{ required: true, message: '请输入组件名称' }]}> <Form.Item label="组件名称" name="name" rules={[{ required: true, message: '请输入组件名称' }]}>
<Input placeholder="请输入" /> <Input placeholder="请输入" />
</Form.Item> </Form.Item>
<FormStep type={type} fieldOptions={fieldOptions} numberFields={numberFields} showTitle={showTitle} /> <FormStep
type={type}
fieldOptions={fieldOptions}
labelOptions={labelOptions}
numberFields={numberFields}
showTitle={showTitle}
currentFieldColors={fieldColors}
/>
<Divider orientation="left" orientationMargin="0"> <Divider orientation="left" orientationMargin="0">
预览组件效果 预览组件效果
</Divider> </Divider>
......
import { useState } from 'react'
import { Button, Col, ColorPicker, Form, Modal, Row, Select } from 'antd'
function ColorField({ onChange, fieldOptions }: any) {
const [modalVisible, setModalVisible] = useState(false)
const [modalForm] = Form.useForm()
const handleModalOk = () => {
modalForm.validateFields().then((values) => {
onChange?.(values.fieldColors)
setModalVisible(false)
})
}
return (
<>
<Button type="dashed" onClick={() => setModalVisible(true)}>
自定义颜色字段
</Button>
<Modal
title="自定义颜色字段"
open={modalVisible}
onOk={handleModalOk}
onCancel={() => setModalVisible(false)}
width={400}>
<Form form={modalForm} layout="vertical">
<Form.List name="fieldColors">
{(fields, { add, remove }) => (
<>
{fields.map((field) => (
<Row gutter={20} key={field.key} align="top">
<Col span={14}>
<Form.Item
{...field}
name={[field.name, 'field']}
rules={[{ required: true, message: '请选择字段' }]}>
<Select options={fieldOptions} placeholder="请选择字段" />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item
{...field}
name={[field.name, 'color']}
getValueFromEvent={(event) => event.toHexString()}
rules={[{ required: true, message: '请选择颜色' }]}>
<ColorPicker />
</Form.Item>
</Col>
<Col span={6}>
<Button type="dashed" onClick={() => remove(field.name)}>
删除
</Button>
</Col>
</Row>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block>
添加字段颜色
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Modal>
</>
)
}
export default ColorField
...@@ -190,7 +190,7 @@ export default function DataLayout() { ...@@ -190,7 +190,7 @@ export default function DataLayout() {
<div className={`data-layout ${collapsed ? 'collapsed' : ''}`}> <div className={`data-layout ${collapsed ? 'collapsed' : ''}`}>
<div className="data-layout-sidebar"> <div className="data-layout-sidebar">
<div className="data-layout-sidebar-header"> <div className="data-layout-sidebar-header">
{collapsed ? '' : <h2>AI数据分析功能区</h2>} {collapsed ? '' : <h4>AI数据分析功能区</h4>}
<span onClick={toggleCollapsed}>{collapsed ? <CircleArrowRight /> : <CircleArrowLeft />}</span> <span onClick={toggleCollapsed}>{collapsed ? <CircleArrowRight /> : <CircleArrowLeft />}</span>
</div> </div>
<div className="data-layout-sidebar-nav"> <div className="data-layout-sidebar-nav">
......
...@@ -37,6 +37,7 @@ json ...@@ -37,6 +37,7 @@ json
}, },
], ],
}) })
console.log(message)
if (message.json && message.json.results) { if (message.json && message.json.results) {
form.setFieldsValue(message.json.results) form.setFieldsValue(message.json.results)
} }
......
...@@ -26,7 +26,7 @@ export default function DataReport() { ...@@ -26,7 +26,7 @@ export default function DataReport() {
数据分析报告 数据分析报告
</Button> </Button>
<Modal title="数据分析报告" open={open} footer={null} width={1000} onCancel={() => setOpen(false)} destroyOnClose> <Modal title="数据分析报告" open={open} footer={null} width={1000} onCancel={() => setOpen(false)} destroyOnClose>
<AIBubble loading={!message?.content} typing={isLoading} content={message?.content}></AIBubble> <AIBubble loading={!message?.full_content} typing={isLoading} content={message?.full_content}></AIBubble>
</Modal> </Modal>
</> </>
) )
......
import { fetchEventSource, FetchEventSourceInit, EventSourceMessage } from '@fortaine/fetch-event-source' import { fetchEventSource, FetchEventSourceInit, EventSourceMessage } from '@fortaine/fetch-event-source'
import { message } from 'antd'
export interface SSEOptions extends Omit<FetchEventSourceInit, 'onopen' | 'onmessage' | 'onerror' | 'onclose'> { export interface SSEOptions extends Omit<FetchEventSourceInit, 'onopen' | 'onmessage' | 'onerror' | 'onclose'> {
/** /**
...@@ -17,12 +18,7 @@ export interface SSEOptions extends Omit<FetchEventSourceInit, 'onopen' | 'onmes ...@@ -17,12 +18,7 @@ export interface SSEOptions extends Omit<FetchEventSourceInit, 'onopen' | 'onmes
onError?: (error: any) => void onError?: (error: any) => void
} }
export async function sseRequest( export async function sseRequest(url: string, options: SSEOptions = {}, transform?: (data: any[]) => any) {
url: string,
options: SSEOptions = {},
updateTransform?: (data: string) => any,
successTransform?: (data: any[]) => any
) {
const { onUpdate, onError, onSuccess, ...rest } = options const { onUpdate, onError, onSuccess, ...rest } = options
const accumulatedData: string[] = [] const accumulatedData: string[] = []
...@@ -39,18 +35,23 @@ export async function sseRequest( ...@@ -39,18 +35,23 @@ export async function sseRequest(
onmessage: (event) => { onmessage: (event) => {
if (event.data && event.data !== '[DONE]') { if (event.data && event.data !== '[DONE]') {
const data = updateTransform ? updateTransform(event.data) : event.data try {
const data = JSON.parse(event.data)
accumulatedData.push(data) accumulatedData.push(data)
onUpdate?.(data, event) onUpdate?.(transform ? transform(accumulatedData) : accumulatedData, event)
} catch (error) {
console.error(error)
}
} }
}, },
onclose: () => { onclose: () => {
onSuccess?.(successTransform ? successTransform(accumulatedData) : accumulatedData) onSuccess?.(transform ? transform(accumulatedData) : accumulatedData)
}, },
onerror: (err) => { onerror: (err) => {
onError?.(err) onError?.(err)
message.error(err.message || '请求失败')
}, },
}) })
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论