Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
S
saas-bi
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
saas-bi
Commits
350da781
提交
350da781
authored
3月 21, 2025
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
chore: update
上级
068ee8d5
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
7 个修改的文件
包含
111 行增加
和
46 行删除
+111
-46
MarkdownRender.tsx
src/components/MarkdownRender.tsx
+6
-0
AIChat.tsx
src/components/ai/AIChat.tsx
+2
-3
useAI.ts
src/hooks/useAI.ts
+40
-31
DataReport.tsx
src/modules/data/write/my/components/DataReport.tsx
+36
-0
Index.tsx
src/modules/data/write/my/views/Index.tsx
+2
-1
ai.ts
src/stores/ai.ts
+25
-11
ai.ts
src/utils/ai.ts
+0
-0
没有找到文件。
src/components/MarkdownRender.tsx
0 → 100644
浏览文件 @
350da781
import
Markdown
from
'react-markdown'
import
remarkGfm
from
'remark-gfm'
export
default
function
MarkdownRender
(
props
:
{
children
?:
string
})
{
return
<
Markdown
remarkPlugins=
{
[
remarkGfm
]
}
>
{
props
.
children
}
</
Markdown
>
}
src/components/ai/AIChat.tsx
浏览文件 @
350da781
...
...
@@ -2,9 +2,8 @@ import { useState, KeyboardEvent, useEffect, useRef } from 'react'
import
{
Button
,
Card
,
FloatButton
,
Input
,
Select
}
from
'antd'
import
{
CircleArrowLeft
,
CircleArrowRight
}
from
'lucide-react'
import
{
OpenAIOutlined
,
ArrowUpOutlined
}
from
'@ant-design/icons'
import
Markdown
from
'react-markdown'
import
remarkGfm
from
'remark-gfm'
import
{
useAIStore
,
AIMessage
}
from
'@/stores/ai'
import
MarkdownRender
from
'../MarkdownRender'
import
'./AIChat.scss'
export
const
MessageItem
=
({
message
}:
{
message
:
AIMessage
})
=>
{
...
...
@@ -12,7 +11,7 @@ export const MessageItem = ({ message }: { message: AIMessage }) => {
<
div
className=
{
`message-item ${message.role}`
}
>
<
div
className=
"message-box"
>
<
div
className=
"message-content"
>
<
Markdown
remarkPlugins=
{
[
remarkGfm
]
}
>
{
message
.
content
}
</
Markdown
>
<
Markdown
Render
>
{
message
.
content
}
</
MarkdownRender
>
</
div
>
</
div
>
</
div
>
...
...
src/hooks/useAI.ts
浏览文件 @
350da781
import
{
useState
,
useEffect
,
useCallback
}
from
'react'
import
aiService
,
{
AIMessage
,
AIData
,
AI_OPTIONS
}
from
'@/utils/ai'
import
aiService
,
{
AIMessage
,
AIData
,
AI_OPTIONS
,
InitOptions
}
from
'@/utils/ai'
export
function
useAI
()
{
export
function
useAI
(
globalOptions
?:
InitOptions
)
{
const
[
ai
,
setAI
]
=
useState
<
string
>
(
localStorage
.
getItem
(
'ai'
)
||
'qwen'
)
const
[
messages
,
setMessages
]
=
useState
<
AIMessage
[]
>
([])
const
[
message
,
setMessage
]
=
useState
<
AIMessage
|
null
>
(
null
)
// 存储最新的消息
const
[
isLoading
,
setIsLoading
]
=
useState
<
boolean
>
(
false
)
useEffect
(()
=>
{
localStorage
.
setItem
(
'ai'
,
ai
)
},
[
ai
])
const
post
=
useCallback
(
async
(
data
:
AIData
)
=>
{
async
(
data
:
AIData
,
options
?:
InitOptions
)
=>
{
setIsLoading
(
true
)
setMessages
((
prevMessages
)
=>
[...
prevMessages
,
...
data
.
messages
.
filter
((
item
)
=>
item
.
role
!==
'system'
)])
try
{
await
aiService
.
post
(
ai
,
data
,
{
onUpdate
:
(
response
)
=>
{
setMessages
((
prevMessages
)
=>
{
const
messageIndex
=
prevMessages
.
findIndex
((
msg
)
=>
msg
.
id
===
response
.
id
)
if
(
messageIndex
===
-
1
)
{
return
[...
prevMessages
,
{
id
:
response
.
id
,
role
:
'assistant'
,
content
:
response
.
content
}]
}
else
{
return
prevMessages
.
map
((
msg
)
=>
msg
.
id
===
response
.
id
?
{
...
msg
,
content
:
msg
.
content
+
response
.
content
}
:
msg
)
}
})
},
onError
:
(
err
)
=>
{
console
.
error
(
'AI 请求失败:'
,
err
)
setIsLoading
(
false
)
},
onComplete
:
()
=>
{
setIsLoading
(
false
)
},
})
}
catch
(
err
)
{
console
.
error
(
'AI 请求失败:'
,
err
)
setIsLoading
(
false
)
}
// 先添加用户消息(去掉 system 角色的消息)
const
userMessages
=
data
.
messages
.
filter
((
item
)
=>
item
.
role
!==
'system'
)
setMessages
((
prevMessages
)
=>
[...
prevMessages
,
...
userMessages
])
await
aiService
.
post
(
ai
,
data
,
{
onMessage
:
(
response
)
=>
{
setMessages
((
prevMessages
)
=>
{
const
messageIndex
=
prevMessages
.
findIndex
((
msg
)
=>
msg
.
id
===
response
.
id
)
if
(
messageIndex
===
-
1
)
{
const
newMessage
:
AIMessage
=
{
id
:
response
.
id
,
role
:
'assistant'
,
content
:
response
.
content
}
setMessage
(
newMessage
)
// 更新最新消息
return
[...
prevMessages
,
newMessage
]
}
else
{
return
prevMessages
.
map
((
msg
)
=>
{
if
(
msg
.
id
===
response
.
id
)
{
const
updatedMessage
=
{
...
msg
,
content
:
msg
.
content
+
response
.
content
}
setMessage
(
updatedMessage
)
// 更新最新消息
return
updatedMessage
}
return
msg
})
}
})
},
onerror
:
()
=>
{
setIsLoading
(
false
)
},
onclose
:
()
=>
{
setIsLoading
(
false
)
},
...
globalOptions
,
...
options
,
})
},
[
ai
]
)
return
{
ai
,
setAI
,
options
:
AI_OPTIONS
,
post
,
messages
,
isLoading
}
return
{
ai
,
setAI
,
options
:
AI_OPTIONS
,
post
,
messages
,
message
,
isLoading
}
}
src/modules/data/write/my/components/DataReport.tsx
0 → 100644
浏览文件 @
350da781
import
{
Button
,
Flex
,
Modal
,
Spin
}
from
'antd'
import
{
useEffect
,
useState
}
from
'react'
import
{
useAI
}
from
'@/hooks/useAI'
import
MarkdownRender
from
'@/components/MarkdownRender'
export
default
function
DataReport
()
{
const
[
open
,
setOpen
]
=
useState
(
false
)
const
{
post
,
isLoading
,
message
}
=
useAI
()
useEffect
(()
=>
{
if
(
open
)
{
post
({
messages
:
[
{
role
:
'user'
,
content
:
'作为数据分析师,请基于提供的数据集,生成一份结构化的数据质量分析报告。需包含字段解释、关系梳理、质量评估及改进建议。'
,
},
],
})
}
},
[
open
])
return
(
<>
<
Button
type=
"primary"
onClick=
{
()
=>
setOpen
(
true
)
}
>
数据分析报告
</
Button
>
<
Modal
title=
"数据分析报告"
open=
{
open
}
footer=
{
null
}
width=
{
1000
}
onCancel=
{
()
=>
setOpen
(
false
)
}
destroyOnClose
>
<
MarkdownRender
>
{
message
?.
content
}
</
MarkdownRender
>
<
Flex
justify=
"center"
>
<
Spin
size=
"large"
spinning=
{
isLoading
}
></
Spin
>
</
Flex
>
</
Modal
>
</>
)
}
src/modules/data/write/my/views/Index.tsx
浏览文件 @
350da781
import
{
Link
}
from
'react-router'
import
{
Button
,
Empty
,
Flex
,
Space
}
from
'antd'
import
DataWrap
from
'@/components/data/DataWrap'
import
DataReport
from
'../components/DataReport'
// 无数据渲染
const
EmptyRender
=
()
=>
{
...
...
@@ -37,7 +38,7 @@ export default function DataWriteMy() {
headerRender=
{
(
data
)
=>
(
<
Flex
justify=
"space-between"
align=
"middle"
style=
{
{
marginBottom
:
'20px'
}
}
>
<
h4
>
数据集名称:
{
data
.
info
.
name
}
</
h4
>
<
Button
type=
"primary"
>
数据质量分析报告
</
Button
>
<
DataReport
/
>
</
Flex
>
)
}
empty=
{
<
EmptyRender
/>
}
></
DataWrap
>
...
...
src/stores/ai.ts
浏览文件 @
350da781
import
{
create
}
from
'zustand'
import
aiService
,
{
AIOption
,
AIMessage
,
AIData
,
AI_OPTIONS
}
from
'@/utils/ai'
import
aiService
,
{
AIOption
,
AIMessage
,
AIData
,
AI_OPTIONS
,
InitOptions
}
from
'@/utils/ai'
interface
AIState
{
ai
:
string
options
:
AIOption
[]
message
:
AIMessage
|
null
messages
:
AIMessage
[]
isLoading
:
boolean
collapsed
:
boolean
setAI
:
(
ai
:
string
)
=>
void
toggleCollapsed
:
()
=>
void
post
:
(
data
:
AIData
)
=>
Promise
<
void
>
post
:
(
data
:
AIData
,
options
?:
InitOptions
)
=>
Promise
<
void
>
}
export
const
useAIStore
=
create
<
AIState
>
((
set
,
get
)
=>
({
ai
:
localStorage
.
getItem
(
'ai'
)
||
'qwen'
,
options
:
AI_OPTIONS
,
message
:
null
,
messages
:
[],
isLoading
:
false
,
collapsed
:
false
,
...
...
@@ -25,40 +27,52 @@ export const useAIStore = create<AIState>((set, get) => ({
toggleCollapsed
:
()
=>
{
set
((
state
)
=>
({
collapsed
:
!
state
.
collapsed
}))
},
post
:
async
(
data
)
=>
{
post
:
async
(
data
,
options
)
=>
{
const
{
ai
,
messages
}
=
get
()
// 处理用户消息(去掉 system 角色的消息)
const
userMessages
=
data
.
messages
.
filter
((
item
)
=>
item
.
role
!==
'system'
)
set
({
collapsed
:
true
,
isLoading
:
true
,
messages
:
[...
messages
,
...
data
.
messages
.
filter
((
item
)
=>
item
.
role
!==
'system'
)
],
messages
:
[...
messages
,
...
userMessages
],
})
try
{
await
aiService
.
post
(
ai
,
data
,
{
on
Updat
e
:
(
response
)
=>
{
on
Messag
e
:
(
response
)
=>
{
set
((
state
)
=>
{
const
messageIndex
=
state
.
messages
.
findIndex
((
msg
)
=>
msg
.
id
===
response
.
id
)
if
(
messageIndex
===
-
1
)
{
// 新的 AI 回复
const
newMessage
:
AIMessage
=
{
id
:
response
.
id
,
role
:
'assistant'
,
content
:
response
.
content
}
return
{
messages
:
[...
state
.
messages
,
{
id
:
response
.
id
,
role
:
'assistant'
,
content
:
response
.
content
}],
message
:
newMessage
,
// 存储最新的 AI 消息
messages
:
[...
state
.
messages
,
newMessage
],
// 追加到历史消息
}
}
else
{
// 追加内容到已有的消息
const
updatedMessages
=
state
.
messages
.
map
((
msg
)
=>
msg
.
id
===
response
.
id
?
{
...
msg
,
content
:
msg
.
content
+
response
.
content
}
:
msg
)
return
{
messages
:
state
.
messages
.
map
((
msg
)
=>
msg
.
id
===
response
.
id
?
{
...
msg
,
content
:
msg
.
content
+
response
.
content
}
:
msg
),
message
:
updatedMessages
[
messageIndex
],
// 更新最新的 AI 消息
messages
:
updatedMessages
,
}
}
})
},
on
E
rror
:
(
err
)
=>
{
on
e
rror
:
(
err
)
=>
{
console
.
error
(
'AI 请求失败:'
,
err
)
set
({
isLoading
:
false
})
},
on
Complet
e
:
()
=>
{
on
clos
e
:
()
=>
{
set
({
isLoading
:
false
})
},
...
options
,
})
}
catch
(
err
)
{
console
.
error
(
'AI 请求失败:'
,
err
)
...
...
src/utils/ai.ts
浏览文件 @
350da781
差异被折叠。
点击展开。
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论