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

Refactor process data handling and UI components

- Updated `useProcessData` hooks across multiple modules to include `status` from `useProcessProgressQuery`. - Enhanced `ButtonModal` components to handle process status and navigation more effectively. - Changed modal properties from `destroyOnClose` to `destroyOnHidden` for better state management. - Removed unused demo request files and related routes to clean up the codebase. - Improved error handling and loading states in various components.
上级 0b62f7b1
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -11,15 +11,17 @@
},
"dependencies": {
"@ant-design/icons": "^6.3.4",
"@ant-design/x": "^1.1.1",
"@ant-design/x": "^2.9.0",
"@ant-design/x-sdk": "^2.9.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fortaine/fetch-event-source": "^3.0.6",
"@streamdown/cjk": "^1.0.3",
"@streamdown/code": "^1.1.1",
"@tanstack/react-query": "^5.102.8",
"@tanstack/react-query-devtools": "^5.102.8",
"antd": "^5.29.3",
"antd": "^6.6.2",
"axios": "^1.20.0",
"blueimp-md5": "^2.19.0",
"echarts": "^5.6.0",
......@@ -31,11 +33,8 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^5.0.0",
"react-markdown": "^10.1.0",
"react-router": "^7.18.3",
"rehype-highlight": "^7.0.2",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"streamdown": "^2.6.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"zustand": "^5.0.15"
},
......
......@@ -46,11 +46,27 @@ body,
}
.app-process-steps {
--ant-steps-dot-size: 100px !important;
--ant-steps-dot-current-size: 100px !important;
.app-progress-step-box {
width: 100%;
height: 100%;
--ant-steps-icon-size: 100px !important;
--ant-steps-icon-size-active: 100px !important;
.ant-steps-item-icon {
width: 100px !important;
height: 100px !important;
position: relative;
z-index: 2;
background: #fff;
}
.ant-steps-item-rail {
margin-top: 0 !important;
transform: translateY(50px);
z-index: 0;
}
.app-progress-step-box {
width: 100px;
height: 100px;
box-sizing: border-box;
position: relative;
z-index: 3;
display: flex;
align-items: center;
justify-content: center;
......@@ -63,15 +79,9 @@ body,
background-color: var(--main-color);
color: #fff;
}
}
.ai-thinking {
margin-bottom: 20px;
padding-left: 16px;
border-left: 2px solid #e5e5e5;
color: #8b8b8b;
line-height: 26px;
&:empty {
display: none;
.ant-steps-item-error .app-progress-step-box {
border-color: var(--ant-color-error);
background-color: var(--ant-color-error);
color: #fff;
}
}
......@@ -10,7 +10,7 @@ const App = () => {
const element = useRoutes(routes)
return (
<ErrorBoundary
fallbackRender={({ error }) => <Alert message="系统错误" description={error.message} type="error" />}>
fallbackRender={({ error }) => <Alert title="系统错误" description={error.message} type="error" />}>
<Suspense
fallback={
<div
......
import { Bubble, BubbleProps } from '@ant-design/x'
import MarkdownRender from '@/components/MarkdownRender'
const renderMarkdown = (content: string) => <MarkdownRender>{content}</MarkdownRender>
import { Spin } from 'antd'
import AIMessageContent from './AIMessageContent'
import type { AIMessage } from './types'
export default function AIBubble(props: BubbleProps) {
return <Bubble messageRender={renderMarkdown} typing={{ step: 5, interval: 50 }} {...props}></Bubble>
export default function AIBubble({ message, loading }: { message?: AIMessage | null; loading?: boolean }) {
return (
<div className="ai-message-row assistant">
<div className="ai-message-body">{loading ? <Spin size="small" /> : <AIMessageContent {...message} />}</div>
</div>
)
}
import { Bubble } from '@ant-design/x'
import { GetProp } from 'antd'
import type { AIMessage } from './types'
import MarkdownRender from '@/components/MarkdownRender'
import { useMemo } from 'react'
const renderMarkdown = (content: string) => <MarkdownRender>{content}</MarkdownRender>
const roles: GetProp<typeof Bubble.List, 'roles'> = {
assistant: {
placement: 'start',
typing: { step: 5, interval: 50 },
messageRender: renderMarkdown,
styles: { content: { maxWidth: '80%' } },
},
user: {
placement: 'end',
messageRender: renderMarkdown,
styles: { content: { maxWidth: '80%' } },
},
}
import AIMessageContent from './AIMessageContent'
import { useLayoutEffect, useRef } from 'react'
export default function AIBubbleList({ messages, style }: { messages: AIMessage[]; style?: React.CSSProperties }) {
const items = useMemo(() => {
return messages.map((item: AIMessage) => {
return {
key: item.id,
role: item.role,
content: item.full_content || item.content,
loading: item.status === 'loading',
const listRef = useRef<HTMLDivElement>(null)
const stickToBottom = useRef(true)
useLayoutEffect(() => {
if (messages.at(-1)?.role === 'user') stickToBottom.current = true
if (stickToBottom.current && listRef.current) {
listRef.current.scrollTop = listRef.current.scrollHeight
}
})
}, [messages])
return <Bubble.List roles={roles} items={items} style={style} />
return (
<div
ref={listRef}
className="ai-message-list"
style={style}
onScroll={(event) => {
const list = event.currentTarget
stickToBottom.current = list.scrollHeight - list.scrollTop - list.clientHeight < 48
}}>
{messages.map((item, index) => (
<div className={`ai-message-row ${item.role}`} key={item.id ?? `${item.role}-${index}`}>
<div className="ai-message-body">
<AIMessageContent {...item} />
</div>
</div>
))}
</div>
)
}
......@@ -6,6 +6,35 @@
display: flex;
flex-direction: column;
}
.ai-message-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 8px 4px;
}
.ai-message-row {
display: flex;
align-items: flex-start;
gap: 10px;
width: 100%;
padding: 10px 4px;
box-sizing: border-box;
&.user {
justify-content: flex-end;
.ai-message-body {
max-width: 80%;
padding: 10px 14px;
border-radius: 18px;
background: #f1f1f1;
}
}
&.assistant {
.ai-message-body {
flex: 1;
min-width: 0;
}
}
}
.ant-card-extra {
cursor: pointer;
}
......
......@@ -26,8 +26,10 @@ export default function AIChat() {
}
}
const handleAI = () => {
const nextContent = content.trim()
if (!nextContent || isLoading) return
setContent('')
post({ messages: [{ role: 'user', content }] })
post({ messages: [{ role: 'user', content: nextContent }] })
}
if (collapsed) {
......
import { Think } from '@ant-design/x'
import { Typography } from 'antd'
import MarkdownRender from '@/components/MarkdownRender'
import type { AIMessage } from './types'
import { useEffect, useState } from 'react'
export default function AIMessageContent({ content, reasoning_content, thinking_seconds, status, error }: Partial<AIMessage>) {
const isAnimating = status === 'typing'
const isThinking = isAnimating && !content
const thinkingTitle = thinking_seconds ? `思考了 ${thinking_seconds}s` : isThinking ? '思考中…' : '思考过程'
const [expanded, setExpanded] = useState(isThinking)
useEffect(() => {
setExpanded(isThinking)
}, [isThinking])
return (
<>
{reasoning_content && (
<Think
title={thinkingTitle}
loading={isThinking}
expanded={expanded}
onExpand={setExpanded}
style={{ marginBottom: 16 }}>
<MarkdownRender isAnimating={isThinking}>{reasoning_content}</MarkdownRender>
</Think>
)}
<MarkdownRender isAnimating={isAnimating}>{content}</MarkdownRender>
{status === 'error' && <div role="alert"><Typography.Text type="danger">{error || '生成失败,请重试'}</Typography.Text></div>}
{status === 'stopped' && <div><Typography.Text type="secondary">已停止生成</Typography.Text></div>}
</>
)
}
import axios from 'axios'
import md5 from 'blueimp-md5'
import { AIMessage, ChatRequestData, GenerateImageRequestData, GenerateVideoRequestData } from './types'
import { AIMessage, ChatRequestData } from './types'
import { request, RequestOptions } from './request'
function transform(message: any): AIMessage | null {
......@@ -15,152 +13,32 @@ function transform(message: any): AIMessage | null {
return null
}
// 文心一言
export async function chatBaidu(data: ChatRequestData, options: RequestOptions) {
const AK = 'wY7bvMpkWeZbDVq9w3EDvpjU'
const SK = 'XJwpiJWxs5HXkOtbo6tQrvYPZFJAWdAy'
const resp = await axios.post(
`/api/qianfan/oauth/2.0/token?grant_type=client_credentials&client_id=${AK}&client_secret=${SK}`
)
await request(
`/api/qianfan/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant?access_token=${resp.data.access_token}`,
{
...options,
body: JSON.stringify({ stream: true, ...data }),
},
transform
)
}
// DeepSeek
export async function chatDeepSeek(data: ChatRequestData, options: RequestOptions) {
await request(
'/api/deepseek/chat/completions',
{
...options,
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer sk-f1a6f0a7013241de8393cb2cb108e777' },
body: JSON.stringify({ model: 'deepseek-reasoner', stream: true, ...data }),
},
transform
)
}
// SiliconFlow
export async function chatSiliconFlow(data: ChatRequestData, options: RequestOptions) {
if (data.model === 'Qwen/QwQ-32B') {
delete data.response_format
}
await request(
return request(
'/api/siliconflow/v1/chat/completions',
{
...options,
stream: true,
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer sk-bivnwauskdbvpspvmdorrgkrpwlyfxbfcezqsfsevowzubdj',
},
body: JSON.stringify({ model: 'deepseek-ai/DeepSeek-R1', stream: true, ...data }),
},
transform
)
}
// 通义千问
export async function chatQwen(data: ChatRequestData, options: RequestOptions) {
await request(
'/api/qwen/compatible-mode/v1/chat/completions',
{
...options,
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer sk-afd0fcdb53bf4058b2068b8548820150' },
body: JSON.stringify({ ...data, model: 'qwen-max-latest', stream: true }),
},
transform
)
}
// 天工
export async function chatTiangong(data: ChatRequestData, options: RequestOptions) {
const appKey = 'a8701b73637562d33a53c668a90ee3be'
const appSecret = 'e191593f486bb88a39c634f46926762dddc97b9082e192af'
const timestamp = Math.floor(Date.now() / 1000).toString()
const sign = md5(`${appKey}${appSecret}${timestamp}`)
await request('/api/tiangong/sky-saas-writing/api/v1/chat', {
...options,
headers: {
'Content-Type': 'application/json',
app_key: appKey,
sign,
timestamp,
stream: 'true',
Authorization: 'Bearer sk-itonwwzwojejcciuawjqomqxrnaptwxoebbmdkbnvpsuorrt',
},
body: JSON.stringify({ chat_history: data.messages, stream_resp_type: 'delta' }),
})
}
// OpenAI
export async function chatCreate(data: ChatRequestData, options: RequestOptions) {
if (data.model === 'Qwen/QwQ-32B') {
delete data.response_format
}
await request(
'/api/openai/chat/create',
{
...options,
headers: { 'Content-Type': 'application/json', Authorization: 'ezijing@20250331' },
body: JSON.stringify({ stream: true, ...data }),
body: JSON.stringify({ model: 'deepseek-ai/DeepSeek-V4-Flash', stream: true, ...data }),
},
transform
transform,
)
}
// 文生图
export async function imageCreate(data: GenerateImageRequestData, options: RequestOptions) {
const resp = await request(
'/api/openai/image/create',
{
...options,
headers: { 'Content-Type': 'application/json', Authorization: 'ezijing@20250331' },
body: JSON.stringify({ ...data, model: 'image-01' }),
},
(response) => {
return { ...response.data, content: '图片生成成功' }
}
)
return resp
}
// 文生视频
export async function videoCreate(data: GenerateVideoRequestData, options: RequestOptions) {
const resp = await request('/api/openai/video/create', {
...options,
headers: { 'Content-Type': 'application/json', Authorization: 'ezijing@20250331' },
body: JSON.stringify(data),
})
return resp
}
const aiService = {
async post(data: any, options: RequestOptions, method?: string) {
async post(data: ChatRequestData, options: RequestOptions) {
const messages: AIMessage[] = []
const dataset = localStorage.getItem('dataset')
if (dataset) {
const datasetInfo = JSON.parse(dataset)
messages.push({ role: 'system', content: `这是一个数据集:${JSON.stringify(datasetInfo)}` })
}
data.messages = [...messages, ...data.messages]
const providers = {
chatBaidu,
chatDeepSeek,
chatSiliconFlow,
chatQwen,
chatTiangong,
chatCreate,
imageCreate,
videoCreate,
}
const provider = providers[method as keyof typeof providers] || chatCreate
await provider(data, options)
return chatSiliconFlow({ ...data, messages: [...messages, ...data.messages] }, options)
},
}
......
import { AIOption } from './types'
export const AI_OPTIONS: AIOption[] = [
{ label: 'DeepSeek-R1', value: 'Pro/deepseek-ai/DeepSeek-R1' },
{ label: 'DeepSeek-V3', value: 'Pro/deepseek-ai/DeepSeek-V3' },
{ label: '通义千问-QwQ', value: 'Qwen/QwQ-32B' },
{ label: '通义千问-Max', value: 'qwen-max-latest' },
{ label: '通义千问-Long', value: 'qwen-long' },
{ label: '豆包-Pro', value: 'doubao-1-5-pro-256k-250115' },
// { label: 'deepseek-r1-250120', value: 'deepseek-r1-250120' },
// { label: 'hunyuan-t1-latest', value: 'hunyuan-t1-latest' },
// { label: 'hunyuan-standard-256K', value: 'hunyuan-standard-256K' },
{ label: 'DeepSeek-V4-Flash', value: 'deepseek-ai/DeepSeek-V4-Flash' },
{ label: 'DeepSeek-V4-Pro', value: 'deepseek-ai/DeepSeek-V4-Pro' },
]
import { XStream } from '@ant-design/x'
import { XStream } from '@ant-design/x-sdk'
import { message as antdMessage } from 'antd'
import { uniqueId, throttle } from 'lodash-es'
import { uniqueId } from 'lodash-es'
import { extractJSON } from '@/utils/helper'
import { AIMessage } from './types'
export interface RequestOptions extends RequestInit {
onUpdate?: (data: any) => void
stream?: boolean
onSuccess?: (data: any) => void
onError?: (error: any) => void
onUpdate?: (data: AIMessage) => void
}
function transformFn<T>(data: T, transform?: (data: T) => any): any {
return transform ? transform(data) : data
}
function appendMessage(message: AIMessage, patch: Partial<AIMessage>, status: AIMessage['status']): AIMessage {
const content = message.content + (patch.content || '')
const reasoning_content = (message.reasoning_content || '') + (patch.reasoning_content || '')
function mergeFn(messages: AIMessage[], merge?: (messages: AIMessage[]) => AIMessage) {
return merge
? merge(messages)
: messages.reduce(
(result, message) => {
const content = result.content + (message.content || '')
const reasoning_content = result.reasoning_content + (message.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 {
...result,
...message,
...patch,
content,
reasoning_content,
full_content,
json: extractJSON(content),
status,
}
},
{ content: '', reasoning_content: '' }
)
}
export async function request(
url: string,
options: RequestOptions = {},
transform?: (data: any) => any,
merge?: (messages: AIMessage[]) => AIMessage
) {
const { onUpdate, onSuccess, onError, body, ...fetchOptions } = options
const { onUpdate, stream = false, body, ...fetchOptions } = options
const id = uniqueId('chat_')
const baseMessage: AIMessage = { id, role: 'assistant', content: '', status: 'loading' }
const messages: AIMessage[] = []
// 创建节流版本的 onUpdate
const throttledOnUpdate = onUpdate ? throttle(onUpdate, 400, { leading: true, trailing: true }) : undefined
let message: AIMessage = { id, role: 'assistant', content: '', status: 'loading' }
let thinkingStartedAt: number | null = null
let thinkingSeconds: number | undefined
try {
messages.push({ ...baseMessage, status: 'loading' })
onUpdate?.(message)
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
......@@ -74,31 +49,64 @@ export async function request(
throw new Error(`${response.status}:${response.statusText}`)
}
if (response.body && typeof body === 'string' && body.includes('stream')) {
if (stream) {
if (!response.body) throw new Error('响应没有可读取的内容')
let completed = false
const stream = XStream({ readableStream: response.body })
for await (const chunk of stream) {
const { data } = chunk
if (data && !data.includes('[DONE]')) {
messages.push({ ...baseMessage, ...transformFn(JSON.parse(data), transform), status: 'typing' })
throttledOnUpdate?.(mergeFn(messages, merge))
if (data?.trim() === '[DONE]') {
completed = true
break
}
if (chunk.event === 'error' && !data?.trim()) throw new Error('生成失败')
if (data?.trim()) {
const json = JSON.parse(data)
if (json.error || chunk.event === 'error') {
throw new Error(json.error?.message || json.message || (typeof json.error === 'string' ? json.error : '生成失败'))
}
const finishReason = json.choices?.[0]?.finish_reason
if (finishReason === 'stop') completed = true
const patch = transform ? transform(json) || {} : json
if (patch.reasoning_content && thinkingStartedAt === null) thinkingStartedAt = Date.now()
if (thinkingStartedAt !== null && thinkingSeconds === undefined && patch.content) {
thinkingSeconds = Math.max(1, Math.round((Date.now() - thinkingStartedAt) / 1000))
}
message = appendMessage(message, patch, 'typing')
if (thinkingStartedAt !== null) {
message = { ...message, thinking_seconds: thinkingSeconds ?? Math.max(1, Math.round((Date.now() - thinkingStartedAt) / 1000)) }
}
// ponytail: update per chunk; add throttling only if profiling shows render pressure.
onUpdate?.(message)
if (finishReason === 'length') throw new Error('回复达到长度限制,内容未生成完整')
if (finishReason === 'content_filter') throw new Error('回复被内容审核中断')
}
}
fetchOptions.signal?.throwIfAborted()
if (!completed) throw new Error('连接提前结束,回复未生成完整,请重试')
if (!message.content.trim()) throw new Error('模型未返回正文,请重试')
if (thinkingStartedAt !== null && thinkingSeconds === undefined) {
thinkingSeconds = Math.max(1, Math.round((Date.now() - thinkingStartedAt) / 1000))
message = { ...message, thinking_seconds: thinkingSeconds }
}
messages.push({ ...baseMessage, status: 'success' })
message = { ...message, status: 'success' }
} else {
const json = await response.json()
messages.push({ ...baseMessage, ...transformFn(json, transform), status: 'success' })
if (json.error) throw new Error(json.error.message || String(json.error))
message = appendMessage(message, transform ? transform(json) || {} : json, 'success')
}
onSuccess?.(mergeFn(messages, merge))
} catch (error: any) {
console.error(error)
const errorMsg = error?.message || '请求失败'
if (error.name !== 'AbortError') {
antdMessage.error(errorMsg)
fetchOptions.signal?.throwIfAborted()
} catch (cause) {
const error = cause instanceof Error ? cause : new Error(String(cause || '请求失败'))
const stopped = error.name === 'AbortError' || fetchOptions.signal?.aborted
message = { ...message, status: stopped ? 'stopped' : 'error', error: stopped ? undefined : error.message }
onUpdate?.(message)
if (!stopped) {
antdMessage.error(error.message)
}
messages.push({ ...baseMessage, content: errorMsg, status: 'error' })
onError?.(mergeFn(messages, merge))
throw error
}
return mergeFn(messages, merge)
return message
}
......@@ -8,10 +8,9 @@ export interface AIMessage {
role: 'user' | 'assistant' | 'system'
content: string
reasoning_content?: string
full_content?: string
thinking_seconds?: number
json?: any
status?: 'loading' | 'success' | 'error' | 'typing'
image_urls?: string[]
status?: 'loading' | 'success' | 'error' | 'typing' | 'stopped'
error?: string
}
......@@ -21,18 +20,4 @@ export interface ChatRequestData {
messages: AIMessage[]
}
export interface GenerateImageRequestData {
model: string
prompt: string
n: number
aspect_ratio: string
subject_reference?: { type: string; image_file: string }[]
}
export interface GenerateVideoRequestData {
model: string
prompt: string
n: number
}
export type AIRequestData = ChatRequestData | GenerateImageRequestData | GenerateVideoRequestData
export type AIRequestData = ChatRequestData
import { useState, useEffect, useCallback, useRef } from 'react'
import { AI_OPTIONS } from './config'
import aiService from './api'
import type { AIMessage, AIRequestData, GenerateImageRequestData, GenerateVideoRequestData } from './types'
import type { AIMessage, AIRequestData } from './types'
import { RequestOptions } from './request'
import { useAIStore } from './useAIStore'
......@@ -23,6 +23,8 @@ export function useAI(globalOptions?: RequestOptions) {
controllerRef.current.abort()
controllerRef.current = null
setIsLoading(false)
setMessage((item) => item && (item.status === 'typing' || item.status === 'loading') ? { ...item, status: 'stopped' } : item)
setMessages((items) => items.map((item) => item.status === 'typing' || item.status === 'loading' ? { ...item, status: 'stopped' } : item))
}
}, [])
......@@ -35,46 +37,46 @@ export function useAI(globalOptions?: RequestOptions) {
}
const post = useCallback(
async (data: AIRequestData, method?: string) => {
async (data: AIRequestData) => {
// 如果已经有正在进行的请求,先取消它
abort()
return new Promise<AIMessage>((resolve, reject) => {
const controller = new AbortController()
controllerRef.current = controller
setIsLoading(true)
setError(null)
// 创建新的 controller
controllerRef.current = new AbortController()
// 添加用户消息
if ('messages' in data) {
setMessage(null)
const userMessages = data.messages.filter((item) => item.role !== 'system')
setMessages((prev) => [...prev, ...userMessages])
}
aiService.post(
try {
const result = await aiService.post(
{ model: ai, ...data },
{
signal: controllerRef.current.signal,
...globalOptions,
signal: controller.signal,
onUpdate: (message) => {
if (controllerRef.current !== controller || (controller.signal.aborted && message.status !== 'stopped')) return
callback(message)
globalOptions?.onUpdate?.(message)
},
onSuccess: (message) => {
callback(message)
controllerRef.current = null
setIsLoading(false)
resolve(message)
},
onError: (err) => {
)
controller.signal.throwIfAborted()
if (controllerRef.current === controller) callback(result)
return result
} catch (cause) {
const error = cause instanceof Error ? cause : new Error(String(cause || '请求失败'))
if (controllerRef.current === controller && !controller.signal.aborted && error.name !== 'AbortError') {
setError(error.message)
}
return null
} finally {
if (controllerRef.current === controller) {
controllerRef.current = null
setIsLoading(false)
setError(err.message)
reject(err)
},
...globalOptions,
},
method
)
})
}
}
},
[ai, globalOptions, abort]
)
......@@ -83,12 +85,6 @@ export function useAI(globalOptions?: RequestOptions) {
setMessages([])
}, [])
// 文生图
const generateImage = useCallback(async (data: GenerateImageRequestData) => post(data, 'imageCreate'), [post])
// 文生视频
const generateVideo = useCallback(async (data: GenerateVideoRequestData) => post(data, 'videoCreate'), [post])
// 组件卸载时取消请求
useEffect(() => {
return () => {
......@@ -108,7 +104,5 @@ export function useAI(globalOptions?: RequestOptions) {
clearError,
abort,
clear,
generateImage,
generateVideo,
}
}
......@@ -14,12 +14,16 @@ interface AIState {
controller: AbortController | null
setAI: (ai: string) => void
toggleCollapsed: () => void
post: (data: AIRequestData) => Promise<AIMessage>
post: (data: AIRequestData) => Promise<AIMessage | null>
clearError: () => void
}
const savedAI = localStorage.getItem('ai')
const initialAI = AI_OPTIONS.find((option) => option.value === savedAI)?.value ?? AI_OPTIONS[0].value
localStorage.setItem('ai', initialAI)
export const useAIStore = create<AIState>((set, get) => ({
ai: localStorage.getItem('ai') || 'Pro/deepseek-ai/DeepSeek-R1',
ai: initialAI,
options: AI_OPTIONS,
message: null,
messages: [],
......@@ -39,18 +43,21 @@ export const useAIStore = create<AIState>((set, get) => ({
},
post: async (data) => {
const { ai, messages } = get()
get().controller?.abort()
const controller = new AbortController()
set({
controller,
collapsed: true,
isLoading: true,
error: null,
message: null,
messages: [
...messages,
...messages.map((item): AIMessage => item.status === 'typing' || item.status === 'loading' ? { ...item, status: 'stopped' } : item),
...(data as ChatRequestData).messages.filter((item: AIMessage) => item.role !== 'system'),
],
})
const callback = (message: AIMessage) => {
if (get().controller !== controller || (controller.signal.aborted && message.status !== 'stopped')) return
set((state) => {
const messageIndex = state.messages.findIndex((item) => item.id === message.id)
const messages =
......@@ -60,26 +67,28 @@ export const useAIStore = create<AIState>((set, get) => ({
return { message, messages }
})
}
return new Promise<AIMessage>((resolve, reject) => {
aiService.post(
try {
const result = await aiService.post(
{ model: ai, ...data },
{
signal: controller.signal,
onUpdate: (message) => {
callback(message)
},
onSuccess: (message) => {
callback(message)
resolve(message)
set({ isLoading: false, controller: null })
},
onError: (err) => {
reject(err)
set({ isLoading: false, error: err.message, controller: null })
},
onUpdate: callback,
}
)
})
controller.signal.throwIfAborted()
callback(result)
return result
} catch (cause) {
const error = cause instanceof Error ? cause : new Error(String(cause || '请求失败'))
if (get().controller === controller && !controller.signal.aborted && error.name !== 'AbortError') {
set({ error: error.message })
}
return null
} finally {
if (get().controller === controller) {
set({ isLoading: false, controller: null })
}
}
},
}))
......
......@@ -16,7 +16,7 @@ export default function AppButtonModal({ buttonText = 'Open Modal', open: extern
<Button type="primary" onClick={() => setOpen(true)}>
{buttonText}
</Button>
<Modal open={actionOpen} onCancel={() => setOpen(false)} destroyOnClose {...props}>
<Modal open={actionOpen} onCancel={() => setOpen(false)} destroyOnHidden {...props}>
{childrenWithProps}
</Modal>
</>
......
......@@ -6,42 +6,37 @@ function stepToChinese(step: number): string {
return stepMap[step] || ''
}
export default function AppSteps({ items, ...props }: StepsProps) {
const customDot = (dot, { status }) => {
const statusMap: any = {
wait: '待处理',
process: '处理中',
finish: '已完成',
}
return (
type AppStepsProps = StepsProps & {
processStatus?: 'idle' | 'running' | 'success' | 'failed'
}
export default function AppSteps({ items, processStatus = 'idle', ...props }: AppStepsProps) {
const failedIndex = processStatus === 'failed' ? Math.max((props.current ?? 1) - 1, 0) : -1
const stepItems = items?.map(({ title, ...item }, index) => ({
...item,
status: processStatus === 'failed' ? (index < failedIndex ? 'finish' : index === failedIndex ? 'error' : 'wait') : item.status,
title: (
<>
<div className="app-progress-step-box">
{status === 'process' ? <Spin size="large"></Spin> : statusMap[status]}
</div>
{stepToChinese(index)}
<br />
{title}
</>
)
}
),
}))
return (
<Steps
{...props}
items={stepItems}
className="app-process-steps"
progressDot={customDot}
labelPlacement="vertical"
type="dot"
iconRender={(_, { item }) => (
<div className="app-progress-step-box">
{item.status === 'process' ? <Spin size="large" /> : item.status === 'error' ? '失败' : item.status === 'finish' ? '已完成' : '待处理'}
</div>
)}
titlePlacement="vertical"
style={{ margin: '80px' }}
{...props}>
{items?.map(({ title, ...item }, index) => {
return (
<Steps.Step
key={index}
title={
<>
{stepToChinese(index)}
<br />
{title}
</>
}
{...item}></Steps.Step>
)
})}
</Steps>
/>
)
}
......@@ -2,16 +2,13 @@ import type { StepsProps } from 'antd'
import { Steps } from 'antd'
export default function AppSteps({ items = [], ...props }: StepsProps) {
const stepItems = items.map((item) => ({
...item,
icon: <></>,
title: <div className="app-step-box">{item.title}</div>,
}))
return (
<Steps className="app-steps" {...props}>
{items.map((item, index) => (
<Steps.Step
key={index}
icon={<></>}
title={<div className="app-step-box">{item.title}</div>}
description={item.description}
/>
))}
</Steps>
<Steps className="app-steps" items={stepItems} {...props} />
)
}
......@@ -30,7 +30,13 @@ export default function AppUpload({ value, onChange, ...props }: any) {
}
return (
<Upload customRequest={customRequest} fileList={fileList} showUploadList={false} maxCount={1} {...props}>
<Upload
customRequest={customRequest}
fileList={fileList}
onChange={() => {}}
showUploadList={false}
maxCount={1}
{...props}>
<Button type="primary" shape="circle" size="large" icon={<PlusOutlined />} />
<span style={{ marginLeft: '10px' }}>{file?.name}</span>
</Upload>
......
......@@ -151,35 +151,29 @@ $table-bg-color: #f6f8fa;
font-size: 85%;
}
// 代码高亮颜色
.hljs {
color: #24292e;
&-keyword,
&-selector-tag,
&-subst {
color: #d73a49;
// Streamdown 使用现有 SCSS,不依赖 Tailwind 工具类。
[data-streamdown='strong'] {
font-weight: 600;
}
&-string,
&-doctag {
color: #032f62;
[data-streamdown='code-block'] {
min-width: 0;
margin: 1em 0;
}
&-title,
&-section,
&-selector-id {
color: #6f42c1;
[data-streamdown='code-block-header'] {
font-size: 12px;
color: $blockquote-color;
}
&-comment,
&-quote {
color: #6a737d;
[data-streamdown='code-block-body'],
[data-streamdown='table-wrapper'] > div {
overflow: auto;
}
&-number,
&-literal {
color: #005cc5;
}
[data-streamdown='code-block-body'] pre {
margin: 0;
code > span { display: block; }
span[style] { color: var(--sdm-c, inherit); }
}
}
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import rehypeHighlight from 'rehype-highlight'
import rehypeRaw from 'rehype-raw'
import { Streamdown } from 'streamdown'
import { code } from '@streamdown/code'
import { cjk } from '@streamdown/cjk'
import 'streamdown/styles.css'
import './MarkdownRender.scss'
interface MarkdownRenderProps {
children?: string
isAnimating?: boolean
}
const MarkdownRender = function MarkdownRender({ children }: MarkdownRenderProps) {
const MarkdownRender = function MarkdownRender({ children, isAnimating = false }: MarkdownRenderProps) {
if (!children) {
return null
}
return (
<div className="markdown-render">
<Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw, rehypeHighlight]}>
<Streamdown
plugins={{ code, cjk }}
controls={false}
lineNumbers={false}
mode={isAnimating ? 'streaming' : 'static'}
isAnimating={isAnimating}>
{children}
</Markdown>
</Streamdown>
</div>
)
}
......
......@@ -40,7 +40,7 @@ const showOptions = [
const Step1 = ({ fieldOptions, numberFields, type }: any) => {
return (
<>
<Divider orientation="left" orientationMargin="0">
<Divider orientation="left" styles={{ content: { margin: 0 } }}>
数字字段设置
</Divider>
<Row gutter={20}>
......@@ -87,7 +87,7 @@ const Step1 = ({ fieldOptions, numberFields, type }: any) => {
const Step2 = ({ labelOptions, type, showTitle }: any) => {
return (
<>
<Divider orientation="left" orientationMargin="0">
<Divider orientation="left" styles={{ content: { margin: 0 } }}>
辅助可视化设置
</Divider>
<Row gutter={20}>
......@@ -176,7 +176,7 @@ const TableStep = ({ fieldOptions }: any) => {
const GaugeStep = ({ numberFields, showTitle }: any) => {
return (
<>
<Divider orientation="left" orientationMargin="0">
<Divider orientation="left" styles={{ content: { margin: 0 } }}>
辅助可视化设置
</Divider>
<Row gutter={20}>
......@@ -217,7 +217,7 @@ const GaugeStep = ({ numberFields, showTitle }: any) => {
const WordCloudStep = ({ fieldOptions, numberFields, showTitle }: any) => {
return (
<>
<Divider orientation="left" orientationMargin="0">
<Divider orientation="left" styles={{ content: { margin: 0 } }}>
辅助可视化设置
</Divider>
<Row gutter={20}>
......@@ -350,6 +350,7 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => {
})
const { post } = useAI()
// 步骤导航
const handlePreview = async () => {
const values = form.getFieldsValue()
const message = await post({
......@@ -394,6 +395,7 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => {
},
],
})
if (!message) return
if (message.json && message.json.results) {
setResults(message.json.results)
}
......@@ -448,7 +450,7 @@ const ModalContent = ({ setOpen, type, id = '' }: Props) => {
showTitle={showTitle}
currentFieldColors={fieldColors}
/>
<Divider orientation="left" orientationMargin="0">
<Divider orientation="left" styles={{ content: { margin: 0 } }}>
预览组件效果
</Divider>
<Chart type={type} {...config} style={{ height: '400px', width: '960px' }} />
......@@ -475,7 +477,7 @@ export default function ButtonModal({ type = '1', id = '' }) {
<Button color="primary" variant={id ? 'text' : 'solid'} onClick={() => setOpen(true)}>
{title}
</Button>
<Modal title={title} open={open} footer={null} destroyOnClose width={1000} onCancel={() => setOpen(false)}>
<Modal title={title} open={open} footer={null} destroyOnHidden width={1000} onCancel={() => setOpen(false)}>
<ModalContent type={type} setOpen={(open) => setOpen(open)} id={id} />
</Modal>
</>
......
......@@ -11,7 +11,7 @@ export default function ViewDataButtonModal({ id }: { id: string }) {
<Button color="primary" variant="text" onClick={() => setOpen(true)}>
查看
</Button>
<Modal title="查看" width={'80%'} open={open} footer={null} onCancel={() => setOpen(false)} destroyOnClose={true}>
<Modal title="查看" width={'80%'} open={open} footer={null} onCancel={() => setOpen(false)} destroyOnHidden={true}>
<ModalContent id={id} />
</Modal>
</>
......
......@@ -5,7 +5,7 @@ import AIBubble from '@/ai/AIBubble'
export default function AIModal({ prompt }: { prompt: string }) {
const [open, setOpen] = useState(false)
const { post, message } = useAI()
const { post, message, isLoading } = useAI()
useEffect(() => {
if (open) {
......@@ -17,8 +17,8 @@ export default function AIModal({ prompt }: { prompt: string }) {
<Button type="primary" onClick={() => setOpen(true)}>
AI建议
</Button>
<Modal title="AI建议" open={open} footer={null} width={1000} onCancel={() => setOpen(false)} destroyOnClose>
<AIBubble loading={!message?.full_content} content={message?.full_content}></AIBubble>
<Modal title="AI建议" open={open} footer={null} width={1000} onCancel={() => setOpen(false)} destroyOnHidden>
<AIBubble loading={isLoading && !message?.content && !message?.reasoning_content} message={message} />
</Modal>
</>
)
......
......@@ -6,7 +6,7 @@ export default function DataRender({ pagination = {}, ...props }: TableProps) {
bordered
scroll={{ x: 'max-content' }}
tableLayout="auto"
size="middle"
size="medium"
pagination={{
hideOnSinglePage: true,
showSizeChanger: true,
......
......@@ -20,7 +20,7 @@ const withModal = (WrappedComponent) => {
<Button type="primary" onClick={() => setOpen(true)}>
{buttonText}
</Button>
<Modal open={actionOpen} onOk={handleOk} onCancel={handleCancel} destroyOnClose {...props}>
<Modal open={actionOpen} onOk={handleOk} onCancel={handleCancel} destroyOnHidden {...props}>
<WrappedComponent setOpen={setOpen} />
</Modal>
</>
......
......@@ -138,6 +138,8 @@ export function useDataFieldQuery() {
}
// 进度查询
export type ProcessStatus = 'idle' | 'running' | 'success' | 'failed'
export function useProcessProgressQuery(params: { function_name: string }) {
const queryClient = useQueryClient()
const [enabled, setEnabled] = useState(false)
......@@ -158,6 +160,7 @@ export function useProcessProgressQuery(params: { function_name: string }) {
})
const remove = () => {
setEnabled(false)
queryClient.removeQueries({ queryKey: ['processProgress', params] })
}
......@@ -175,6 +178,9 @@ export function useProcessProgressQuery(params: { function_name: string }) {
}
}, [query.data, queryClient])
const status: ProcessStatus =
query.data?.result === 'FAIL' ? 'failed' : query.data?.result === 'SUCCESS' ? 'success' : enabled ? 'running' : 'idle'
useEffect(() => {
if (!enabled) return
if (query.isFetching) return
......@@ -189,5 +195,5 @@ export function useProcessProgressQuery(params: { function_name: string }) {
}
}, [])
return { ...query, start, stop, remove, enabled }
return { ...query, start, stop, remove, enabled, status }
}
......@@ -36,6 +36,7 @@ json
},
],
})
if (!message) return
console.log(message.json)
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
......@@ -71,6 +72,7 @@ json
},
],
})
if (!message) return
console.log(message.json)
if (message.json && message.json.results) {
setResults(message.json.results)
......@@ -108,7 +110,7 @@ json
content: (
<>
<Form.Item label="请选择特征列" name="independent_variables" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Flex gap={20}>
......@@ -326,7 +328,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -37,6 +37,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
}
......@@ -70,6 +71,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
setResults(message.json.results)
}
......@@ -106,7 +108,7 @@ json
content: (
<>
<Form.Item label="请选择特征列" name="independent_variables" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Form.Item label="请选择目标列" name="dependent_variable" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" />
......@@ -275,7 +277,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -36,6 +36,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
}
......@@ -67,6 +68,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
setResults(message.json.results)
}
......@@ -103,7 +105,7 @@ json
content: (
<>
<Form.Item label="请选择特征列" name="independent_variables" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Flex gap={20}>
......@@ -255,7 +257,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -36,6 +36,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
}
......@@ -68,6 +69,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
setResults(message.json.results)
}
......@@ -109,7 +111,7 @@ json
content: (
<>
<Form.Item label="请选择特征列" name="independent_variables" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Flex gap={20}>
......@@ -289,7 +291,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -37,6 +37,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
}
......@@ -69,6 +70,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
setResults(message.json.results)
}
......@@ -241,7 +243,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -36,6 +36,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
}
......@@ -70,6 +71,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
setResults(message.json.results)
}
......@@ -106,7 +108,7 @@ json
content: (
<>
<Form.Item label="请选择特征列" name="independent_variables" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Flex gap={20}>
......@@ -307,7 +309,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -37,6 +37,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
}
......@@ -70,6 +71,7 @@ json
},
],
})
if (!message) return
console.log(message.json)
if (message.json && message.json.results) {
setResults(message.json.results)
......@@ -121,7 +123,7 @@ json
label="请选择自变量"
name="independent_variables"
rules={[{ required: true, message: '请选择自变量' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Flex gap={20}>
<div style={{ flex: 1 }}>
......@@ -272,7 +274,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -37,6 +37,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
}
......@@ -71,6 +72,7 @@ json
},
],
})
if (!message) return
console.log(message.json)
if (message.json && message.json.results) {
setResults(message.json.results)
......@@ -117,7 +119,7 @@ json
label="请选择自变量"
name="independent_variables"
rules={[{ required: true, message: '请选择自变量' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Flex gap={20}>
<div style={{ flex: 1 }}>
......@@ -322,7 +324,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -37,6 +37,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
}
......@@ -70,6 +71,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
setResults(message.json.results)
}
......@@ -111,7 +113,7 @@ json
content: (
<>
<Form.Item label="请选择特征列" name="independent_variables" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Form.Item label="请选择目标列" name="dependent_variable" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" />
......@@ -280,7 +282,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -37,6 +37,7 @@ json
},
],
})
if (!message) return
console.log(message)
if (message.json && message.json.results) {
form.setFieldsValue(message.json.results)
......@@ -71,6 +72,7 @@ json
},
],
})
if (!message) return
if (message.json && message.json.results) {
setResults(message.json.results)
}
......@@ -107,7 +109,7 @@ json
content: (
<>
<Form.Item label="请选择特征列" name="independent_variables" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" optionFilterProp="label" />
<Select options={fieldNameOptions} placeholder="请选择" mode="multiple" showSearch={{ optionFilterProp: 'label' }} />
</Form.Item>
<Form.Item label="请选择目标列" name="dependent_variable" rules={[{ required: true, message: '请选择' }]}>
<Select options={fieldNameOptions} placeholder="请选择" />
......@@ -274,7 +276,7 @@ json
title={steps[current].title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -30,6 +30,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.error }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -53,7 +54,7 @@ export default function ButtonModal() {
const ruleOptions = ['取该字段平均值', '删除逻辑错误值', '不处理']
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -61,8 +62,20 @@ export default function ButtonModal() {
})
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -108,18 +121,19 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{
title: '检查字段处理规则',
description: message[1],
content: message[1],
},
{
title: '逻辑错误值处理',
description: message[2],
content: message[2],
},
{
title: '处理结果',
description: message[3],
content: message[3],
},
]}
/>
......@@ -144,9 +158,13 @@ export default function ButtonModal() {
检查逻辑错误值字段
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!field}>
<Button type="primary" onClick={handleNext} disabled={!field}>
下一步
</Button>
)}
......@@ -157,8 +175,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={initialValues}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'abnormal' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'abnormal' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -30,6 +30,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.max }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -54,7 +55,7 @@ export default function ButtonModal() {
const ruleOptions = ['取该字段平均值', '删除过大值', '不处理']
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -62,8 +63,20 @@ export default function ButtonModal() {
})
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -109,18 +122,19 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{
title: '检查字段处理规则',
description: message[1],
content: message[1],
},
{
title: '过大值处理',
description: message[2],
content: message[2],
},
{
title: '处理结果',
description: message[3],
content: message[3],
},
]}
/>
......@@ -145,9 +159,13 @@ export default function ButtonModal() {
检查过大值字段
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!field}>
<Button type="primary" onClick={handleNext} disabled={!field}>
下一步
</Button>
)}
......@@ -158,8 +176,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={initialValues}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'outOfRange' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'outOfRange' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -32,6 +32,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.min }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -55,7 +56,7 @@ export default function ButtonModal() {
const ruleOptions = ['取该字段平均值', '删除过小值', '不处理']
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -63,8 +64,20 @@ export default function ButtonModal() {
})
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -110,18 +123,19 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{
title: '检查字段处理规则',
description: message[1],
content: message[1],
},
{
title: '过小值处理',
description: message[2],
content: message[2],
},
{
title: '处理结果',
description: message[3],
content: message[3],
},
]}
/>
......@@ -146,9 +160,13 @@ export default function ButtonModal() {
检查过小值字段
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!field}>
<Button type="primary" onClick={handleNext} disabled={!field}>
下一步
</Button>
)}
......@@ -159,8 +177,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={initialValues}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'underRange' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'underRange' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -46,6 +46,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.null }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -72,7 +73,7 @@ export default function ButtonModal() {
const rule = Form.useWatch('rule', form)
const rules = Form.useWatch('rules', form) || {}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -81,8 +82,20 @@ export default function ButtonModal() {
})
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -159,10 +172,11 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '检查字段处理规则', description: message[1] },
{ title: '缺失值处理', description: message[2] },
{ title: '处理结果', description: message[3] },
{ title: '检查字段处理规则', content: message[1] },
{ title: '缺失值处理', content: message[2] },
{ title: '处理结果', content: message[3] },
]}
/>
</Flex>
......@@ -186,9 +200,13 @@ export default function ButtonModal() {
检查缺失值字段
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!fields.length}>
<Button type="primary" onClick={handleNext} disabled={!fields.length}>
下一步
</Button>
)}
......@@ -199,8 +217,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={initialValues}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'missing' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'missing' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -31,6 +31,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.repeat }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -49,7 +50,7 @@ export default function ButtonModal() {
const ruleOptions = ['保留1条,删除剩余重复数据', '全部删除', '不处理']
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -58,8 +59,20 @@ export default function ButtonModal() {
})
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -113,10 +126,11 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '检查字段处理规则', description: message[1] },
{ title: '重复值处理', description: message[2] },
{ title: '处理结果', description: message[3] },
{ title: '检查字段处理规则', content: message[1] },
{ title: '重复值处理', content: message[2] },
{ title: '处理结果', content: message[3] },
]}
/>
</Flex>
......@@ -140,9 +154,13 @@ export default function ButtonModal() {
检查重复值字段
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!checkedList.length}>
<Button type="primary" onClick={handleNext} disabled={!checkedList.length}>
下一步
</Button>
)}
......@@ -153,8 +171,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={initialValues}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'duplicate' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'duplicate' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -45,7 +45,7 @@ export default function ButtonModal() {
const [current, setCurrent] = useState(0)
const [form] = Form.useForm()
const [dataSource, setDataSource] = useState<DataType[]>([])
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
const fields = dataSource.map((item: any) => {
......@@ -54,8 +54,20 @@ export default function ButtonModal() {
mutate({ fields: JSON.stringify(fields) })
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -149,10 +161,11 @@ export default function ButtonModal() {
<AppProgressSteps
style={{ margin: '80px' }}
current={progress}
processStatus={processStatus}
items={[
{ title: '第一步\n开始', description: message[1] },
{ title: '第二步\n数据排序', description: message[2] },
{ title: '第三步\n处理结果', description: message[3] },
{ title: '第一步\n开始', content: message[1] },
{ title: '第二步\n数据排序', content: message[2] },
{ title: '第三步\n处理结果', content: message[3] },
]}
/>
</Flex>
......@@ -170,9 +183,13 @@ export default function ButtonModal() {
open={open}
footer={
<Flex justify="center" gap={20}>
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)}>
<Button type="primary" onClick={handleNext}>
下一步
</Button>
)}
......@@ -183,8 +200,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'sort' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'sort' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -58,6 +58,7 @@ export default function ButtonModal() {
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
......@@ -81,7 +82,7 @@ export default function ButtonModal() {
{ label: '全部去空格', value: '全部去空格' },
]
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -89,8 +90,20 @@ export default function ButtonModal() {
})
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -151,18 +164,19 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{
title: '检查字段处理规则',
description: message[1],
content: message[1],
},
{
title: '数据去空格处理',
description: message[2],
content: message[2],
},
{
title: '处理结果',
description: message[3],
content: message[3],
},
]}
/>
......@@ -187,9 +201,13 @@ export default function ButtonModal() {
AI去空格字段分析与建议
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!field}>
<Button type="primary" onClick={handleNext} disabled={!field}>
下一步
</Button>
)}
......@@ -200,8 +218,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={initialValues}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'trim' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'trim' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -12,7 +12,7 @@ export default function ButtonModal() {
const [form] = Form.useForm()
const [fields, setFields] = useState<{ field: string; order: number }[]>([])
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -20,8 +20,15 @@ export default function ButtonModal() {
})
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -173,18 +180,19 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{
title: '新增字段',
description: message[1],
content: message[1],
},
{
title: '数据拼接',
description: message[2],
content: message[2],
},
{
title: '处理结果',
description: message[3],
content: message[3],
},
]}
/>
......@@ -204,7 +212,11 @@ export default function ButtonModal() {
open={open}
footer={
<Flex justify="center" gap={20}>
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -217,8 +229,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'merge' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'merge' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -26,7 +26,7 @@ export default function ButtonModal() {
// { label: '前几个字符', value: '5' },
]
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -34,8 +34,20 @@ export default function ButtonModal() {
})
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -80,26 +92,27 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{
title: '计算拆分字段',
description: message[1],
content: message[1],
},
{
title: '数据库表增加拆分字段',
description: message[2],
content: message[2],
},
{
title: '更新拆分字段',
description: message[3],
content: message[3],
},
{
title: '更新原始字段',
description: message[4],
content: message[4],
},
{
title: '处理结果',
description: message[5],
content: message[5],
},
]}
/>
......@@ -119,9 +132,13 @@ export default function ButtonModal() {
open={open}
footer={
<Flex justify="center" gap={20}>
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!field}>
<Button type="primary" onClick={handleNext} disabled={!field}>
下一步
</Button>
)}
......@@ -132,8 +149,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={initialValues}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'split' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'split' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -18,7 +18,7 @@ export default function ButtonModal() {
const field = Form.useWatch('field', form)
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
form.validateFields().then((values) => {
......@@ -26,8 +26,20 @@ export default function ButtonModal() {
})
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -72,18 +84,19 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{
title: '开始',
description: message[1],
content: message[1],
},
{
title: '数据去标点处理',
description: message[2],
content: message[2],
},
{
title: '处理结果',
description: message[3],
content: message[3],
},
]}
/>
......@@ -103,9 +116,13 @@ export default function ButtonModal() {
open={open}
footer={
<Flex justify="center" gap={20}>
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)} disabled={!field}>
<Button type="primary" onClick={handleNext} disabled={!field}>
下一步
</Button>
)}
......@@ -116,8 +133,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={initialValues}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'punctuation' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'punctuation' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -12,7 +12,7 @@ export default function ButtonModal() {
const [current, setCurrent] = useState(0)
const [form] = Form.useForm()
const [dataSource, setDataSource] = useState<{ key: number; raw_value: string; new_value: string }[]>([])
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
const [selectedRowKey, setSelectedRowKey] = useState()
......@@ -30,8 +30,20 @@ export default function ButtonModal() {
}
}
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 步骤导航
const handleNext = () => {
setCurrent(current + 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -100,10 +112,11 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '开始', description: message[1] },
{ title: '数据类型转换', description: message[2] },
{ title: '处理结果', description: message[3] },
{ title: '开始', content: message[1] },
{ title: '数据类型转换', content: message[2] },
{ title: '处理结果', content: message[3] },
]}
/>
</Flex>
......@@ -122,9 +135,13 @@ export default function ButtonModal() {
open={open}
footer={
<Flex justify="center" gap={20}>
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={() => setCurrent(current + 1)}>
<Button type="primary" onClick={handleNext}>
下一步
</Button>
)}
......@@ -135,8 +152,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'type' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'type' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -16,5 +16,5 @@ export function useProcessData() {
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -91,6 +91,7 @@ ${action === '固定步长分箱' ? `最小值 ${min}、最大值 ${max}、步
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
// 处理下一步按钮逻辑
......@@ -104,7 +105,7 @@ ${action === '固定步长分箱' ? `最小值 ${min}、最大值 ${max}、步
setCurrent(current + 1)
}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
......@@ -113,8 +114,15 @@ ${action === '固定步长分箱' ? `最小值 ${min}、最大值 ${max}、步
mutate(params)
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -281,11 +289,12 @@ ${action === '固定步长分箱' ? `最小值 ${min}、最大值 ${max}、步
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '新建字段', description: message[1] },
{ title: '复制数据', description: message[2] },
{ title: '数据分箱处理', description: message[3] },
{ title: '处理结果', description: message[4] },
{ title: '新建字段', content: message[1] },
{ title: '复制数据', content: message[2] },
{ title: '数据分箱处理', content: message[3] },
{ title: '处理结果', content: message[4] },
]}
/>
</Flex>
......@@ -309,7 +318,11 @@ ${action === '固定步长分箱' ? `最小值 ${min}、最大值 ${max}、步
AI智能建议
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -322,8 +335,11 @@ ${action === '固定步长分箱' ? `最小值 ${min}、最大值 ${max}、步
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'binning' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'binning' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -15,5 +15,5 @@ export function useProcessData() {
start()
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -126,6 +126,7 @@ export default function ButtonModal() {
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
......@@ -140,7 +141,7 @@ export default function ButtonModal() {
setCurrent(current + 1)
}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
......@@ -157,8 +158,15 @@ export default function ButtonModal() {
mutate(params)
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -261,11 +269,12 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '新建字段', description: message[1] },
{ title: '复制数据', description: message[2] },
{ title: '日期计算处理', description: message[3] },
{ title: '处理结果', description: message[4] },
{ title: '新建字段', content: message[1] },
{ title: '复制数据', content: message[2] },
{ title: '日期计算处理', content: message[3] },
{ title: '处理结果', content: message[4] },
]}
/>
</Flex>
......@@ -282,7 +291,11 @@ export default function ButtonModal() {
AI检查函数公式
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -306,8 +319,11 @@ export default function ButtonModal() {
title={steps[current]?.title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 6 }} preserve={false} initialValues={{ action: '快速日期计算' }}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'date' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'date' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -15,5 +15,5 @@ export function useProcessData() {
start()
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -69,6 +69,7 @@ export default function ButtonModal() {
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
......@@ -83,7 +84,7 @@ export default function ButtonModal() {
setCurrent(current + 1)
}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
......@@ -92,8 +93,15 @@ export default function ButtonModal() {
mutate(params)
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -270,11 +278,12 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '新建字段', description: message[1] },
{ title: '复制数据', description: message[2] },
{ title: '数据脱敏处理', description: message[3] },
{ title: '处理结果', description: message[4] },
{ title: '新建字段', content: message[1] },
{ title: '复制数据', content: message[2] },
{ title: '数据脱敏处理', content: message[3] },
{ title: '处理结果', content: message[4] },
]}
/>
</Flex>
......@@ -298,7 +307,11 @@ export default function ButtonModal() {
AI智能建议
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -311,8 +324,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false} initialValues={{ action: '基础脱敏方法' }}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'anonymization' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'anonymization' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -15,5 +15,5 @@ export function useProcessData() {
start()
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -82,6 +82,7 @@ export default function ButtonModal() {
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
......@@ -96,7 +97,7 @@ export default function ButtonModal() {
setCurrent(current + 1)
}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
......@@ -115,8 +116,15 @@ export default function ButtonModal() {
mutate(params)
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -232,11 +240,12 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '新建字段', description: <>{message[1]}</> },
{ title: '复制数据', description: <>{message[2]}</> },
{ title: '数据分组处理', description: <>{message[3]}</> },
{ title: '处理结果', description: <>{message[4]}</> },
{ title: '新建字段', content: <>{message[1]}</> },
{ title: '复制数据', content: <>{message[2]}</> },
{ title: '数据分组处理', content: <>{message[3]}</> },
{ title: '处理结果', content: <>{message[4]}</> },
]}
/>
</Flex>
......@@ -260,7 +269,11 @@ export default function ButtonModal() {
AI智能建议
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -273,8 +286,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'grouping' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'grouping' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -15,5 +15,5 @@ export function useProcessData() {
start()
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -57,6 +57,7 @@ export default function ButtonModal() {
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
......@@ -71,7 +72,7 @@ export default function ButtonModal() {
setCurrent(current + 1)
}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
......@@ -85,8 +86,15 @@ export default function ButtonModal() {
mutate(params)
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -150,11 +158,12 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '新建字段', description: message[1] },
{ title: '复制数据', description: message[2] },
{ title: '逻辑计算处理', description: message[3] },
{ title: '处理结果', description: message[4] },
{ title: '新建字段', content: message[1] },
{ title: '复制数据', content: message[2] },
{ title: '逻辑计算处理', content: message[3] },
{ title: '处理结果', content: message[4] },
]}
/>
</Flex>
......@@ -171,7 +180,11 @@ export default function ButtonModal() {
AI检查函数公式
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -195,8 +208,11 @@ export default function ButtonModal() {
title={steps[current]?.title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 6 }} preserve={false}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'logic' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'logic' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -15,5 +15,5 @@ export function useProcessData() {
start()
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -81,6 +81,7 @@ export default function ButtonModal() {
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
......@@ -93,7 +94,7 @@ export default function ButtonModal() {
setCurrent(current + 1)
}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
......@@ -105,8 +106,15 @@ export default function ButtonModal() {
mutate(params)
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -195,11 +203,12 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '新建字段', description: message[1] },
{ title: '复制数据', description: message[2] },
{ title: '值映射处理', description: message[3] },
{ title: '处理结果', description: message[4] },
{ title: '新建字段', content: message[1] },
{ title: '复制数据', content: message[2] },
{ title: '值映射处理', content: message[3] },
{ title: '处理结果', content: message[4] },
]}
/>
</Flex>
......@@ -223,7 +232,11 @@ export default function ButtonModal() {
AI智能建议
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -236,8 +249,11 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'mapping' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'mapping' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -15,5 +15,5 @@ export function useProcessData() {
start()
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -66,6 +66,7 @@ export default function ButtonModal() {
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
// 处理下一步按钮逻辑
......@@ -79,7 +80,7 @@ export default function ButtonModal() {
setCurrent(current + 1)
}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
// 开始处理
const handleStart = () => {
......@@ -93,8 +94,15 @@ export default function ButtonModal() {
mutate(params)
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -158,11 +166,12 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '新建字段', description: message[1] },
{ title: '复制数据', description: message[2] },
{ title: '数值计算处理', description: message[3] },
{ title: '处理结果', description: message[4] },
{ title: '新建字段', content: message[1] },
{ title: '复制数据', content: message[2] },
{ title: '数值计算处理', content: message[3] },
{ title: '处理结果', content: message[4] },
]}
/>
</Flex>
......@@ -179,7 +188,11 @@ export default function ButtonModal() {
AI检查函数公式
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -203,8 +216,11 @@ export default function ButtonModal() {
title={steps[current]?.title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 6 }} preserve={false}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'numeric' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'numeric' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -15,5 +15,5 @@ export function useProcessData() {
start()
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -46,7 +46,7 @@ export default function ButtonModal() {
setCurrent(current + 1)
}
const { mutate, isPending, progress, message, remove } = useProcessData()
const { mutate, isPending, progress, message, status: processStatus, remove } = useProcessData()
const [result, setResult] = useState('')
const { isLoading, post } = useAI()
......@@ -80,6 +80,7 @@ export default function ButtonModal() {
},
],
})
if (!message) return
if (message.content) setResult(message.content)
}
// 开始处理
......@@ -94,8 +95,15 @@ export default function ButtonModal() {
mutate(params)
}
// 步骤导航
const handlePrev = () => {
if (isPending) return
remove()
setCurrent(current - 1)
}
// 关闭并重置
const handleClose = () => {
if (isPending) return
setOpen(false)
setCurrent(0)
remove()
......@@ -160,11 +168,12 @@ export default function ButtonModal() {
</Button>
<AppProgressSteps
current={progress}
processStatus={processStatus}
items={[
{ title: '新建字段', description: message[1] },
{ title: '复制数据', description: message[2] },
{ title: '文本计算处理', description: message[3] },
{ title: '处理结果', description: message[4] },
{ title: '新建字段', content: message[1] },
{ title: '复制数据', content: message[2] },
{ title: '文本计算处理', content: message[3] },
{ title: '处理结果', content: message[4] },
]}
/>
</Flex>
......@@ -181,7 +190,11 @@ export default function ButtonModal() {
AI检查函数公式
</Button>
)}
{current > 0 && <Button onClick={() => setCurrent(current - 1)}>上一步</Button>}
{current > 0 && (
<Button onClick={handlePrev} disabled={isPending}>
上一步
</Button>
)}
{current < steps.length - 1 && (
<Button type="primary" onClick={handleNext}>
下一步
......@@ -205,8 +218,11 @@ export default function ButtonModal() {
title={steps[current]?.title}
open={open}
footer={<Footer />}
destroyOnClose
destroyOnHidden
width={800}
closable={!isPending}
keyboard={!isPending}
mask={{ closable: !isPending }}
onCancel={handleClose}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
<Form form={form} labelCol={{ span: 6 }} preserve={false}>
......
......@@ -5,7 +5,7 @@ import { useProcessProgressQuery } from '@/hooks/useQuery'
// 处理数据
export function useProcessData() {
const { data, start, remove, enabled } = useProcessProgressQuery({ function_name: 'text' })
const { data, start, remove, enabled, status: progressStatus } = useProcessProgressQuery({ function_name: 'text' })
const query = useMutation({
mutationFn: (data: ProcessDataParams) => {
......@@ -15,5 +15,5 @@ export function useProcessData() {
start()
},
})
return { ...query, progress: data.progress, message: data.message, remove, isPending: enabled || query.isPending }
return { ...query, progress: data.progress, message: data.message, result: data.result, status: query.isPending ? 'running' : progressStatus, remove, isPending: enabled || query.isPending }
}
......@@ -29,6 +29,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.error }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -90,7 +91,7 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
......
......@@ -29,6 +29,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.max }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -97,7 +98,7 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
......
......@@ -29,6 +29,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.min }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -90,7 +91,7 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
......
......@@ -31,6 +31,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.null }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -149,7 +150,7 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
......
......@@ -133,7 +133,7 @@ export default function ButtonModal() {
</Button>
</Flex>
}
destroyOnClose
destroyOnHidden
width={1000}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '20px 0' }}>
......
......@@ -22,6 +22,7 @@ export default function ButtonModal() {
response_format: { type: 'json_object' },
messages: [{ role: 'user', content: prompt.repeat }],
})
if (!message) return
console.log(message.json)
if (message.json && Array.isArray(message.json.results)) {
setResults(message.json.results)
......@@ -74,7 +75,7 @@ export default function ButtonModal() {
)}
</Flex>
}
destroyOnClose
destroyOnHidden
width={800}
onCancel={() => setOpen(false)}>
<div style={{ minHeight: 300, padding: '40px 0' }}>
......
......@@ -46,6 +46,7 @@ const ModalContent = ({ setOpen, id = '' }: Props) => {
const { mutate: mutateUpdate } = useUpdateLargeScreen()
// 预览
// 步骤导航
const handlePreview = () => {
form.validateFields().then((values) => {
const ids = selectedCharts.map((item) => item.id)
......@@ -259,7 +260,7 @@ export default function ButtonModal({ id = '' }) {
<Button color="primary" variant={id ? 'text' : 'solid'} onClick={() => setOpen(true)}>
{title}
</Button>
<Modal title={title} open={open} footer={null} destroyOnClose width={1000} onCancel={() => setOpen(false)}>
<Modal title={title} open={open} footer={null} destroyOnHidden width={1000} onCancel={() => setOpen(false)}>
<ModalContent setOpen={(open) => setOpen(open)} id={id} />
</Modal>
</>
......
......@@ -37,7 +37,7 @@ export default function FormButtonModal({ title = '添加数据集' }: { title?:
onCancel={() => setOpen(false)}
confirmLoading={isPending}
okText="保存"
destroyOnClose>
destroyOnHidden>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
<Form.Item label="数据集名称" name="name" rules={[{ required: true, message: '请输入数据集名称' }]}>
<Input placeholder="请输入" />
......
......@@ -29,7 +29,7 @@ export default function CopyButtonModal({ children, data }: { children?: string;
onCancel={() => setOpen(false)}
confirmLoading={isPending}
okText="复制"
destroyOnClose>
destroyOnHidden>
<Form form={form} labelCol={{ span: 5 }} preserve={false}>
<Form.Item label="数据集名称" name="name">
<Input placeholder="请输入" />
......
......@@ -5,7 +5,7 @@ import AIBubble from '@/ai/AIBubble'
export default function DataReport() {
const [open, setOpen] = useState(false)
const { post, message } = useAI()
const { post, message, isLoading } = useAI()
useEffect(() => {
if (open) {
......@@ -25,8 +25,8 @@ export default function DataReport() {
<Button type="primary" onClick={() => setOpen(true)}>
数据分析报告
</Button>
<Modal title="数据分析报告" open={open} footer={null} width={1000} onCancel={() => setOpen(false)} destroyOnClose>
<AIBubble loading={!message?.full_content} content={message?.full_content}></AIBubble>
<Modal title="数据分析报告" open={open} footer={null} width={1000} onCancel={() => setOpen(false)} destroyOnHidden>
<AIBubble loading={isLoading && !message?.content && !message?.reasoning_content} message={message} />
</Modal>
</>
)
......
import { XStream } from '@ant-design/x'
import { sseRequest } from '@/utils/sseRequest'
async function request() {
const response = await fetch('/api/openai/chat/create', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'ezijing@20250331',
},
body: JSON.stringify({
stream: true,
model: 'qwen-max-latest',
messages: [{ role: 'user', content: '你好' }],
}),
})
console.log(response)
for await (const chunk of XStream({
readableStream: response.body,
})) {
console.log(chunk)
}
}
const controller = new AbortController()
async function request2() {
const res = await sseRequest('/api/openai/chat/create', {
method: 'POST',
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
Authorization: 'ezijing@20250331',
},
body: JSON.stringify({
stream: true,
model: 'qwen-max-latest',
messages: [{ role: 'user', content: '你好' }],
}),
onUpdate: (data) => {
console.log('update', data)
},
onSuccess: (data) => {
console.log('success', data)
},
onError: (error) => {
console.log('error', error)
},
})
console.log(res)
}
export default function App() {
return (
<div>
<button onClick={request2}>请求</button>
<br />
<br />
<button onClick={() => controller.abort()}>取消</button>
</div>
)
}
......@@ -14,8 +14,4 @@ export const routes: RouteObject[] = [
path: '/demo/chart',
Component: lazy(() => import('./chart')),
},
{
path: '/demo/request',
Component: lazy(() => import('./request')),
},
]
import { fetchEventSource, FetchEventSourceInit, EventSourceMessage } from '@fortaine/fetch-event-source'
import { message } from 'antd'
export interface SSEOptions extends Omit<FetchEventSourceInit, 'onopen' | 'onmessage' | 'onerror' | 'onclose'> {
/**
* 每次接收服务端消息时触发
*/
onUpdate?: (data: any, event: EventSourceMessage) => void
/**
* SSE 正常结束时触发,包含所有已接收数据
*/
onSuccess?: (data: any) => void
/**
* SSE 发生错误时触发
*/
onError?: (error: any) => void
}
export async function sseRequest(url: string, options: SSEOptions = {}, transform?: (data: any[]) => any) {
const { onUpdate, onError, onSuccess, ...rest } = options
const accumulatedData: string[] = []
await fetchEventSource(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
async onopen(response) {
if (response.ok) return
else throw response
},
...rest,
onmessage: (event) => {
if (event.data && event.data !== '[DONE]') {
try {
const data = JSON.parse(event.data)
accumulatedData.push(data)
onUpdate?.(transform ? transform(accumulatedData) : accumulatedData, event)
} catch (error) {
console.error(error)
}
}
},
onclose: () => {
onSuccess?.(transform ? transform(accumulatedData) : accumulatedData)
},
onerror: (err) => {
onError?.(err)
message.error(err.message || '请求失败')
},
})
}
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论