Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-book
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-book
Commits
1afbfb14
提交
1afbfb14
authored
2月 12, 2025
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
chore: update
上级
cf325497
隐藏空白字符变更
内嵌
并排
正在显示
7 个修改的文件
包含
202 行增加
和
37 行删除
+202
-37
AIChatDrawer.jsx
src/common/wangeditor-customer/menu/common/AIChatDrawer.jsx
+11
-17
AIChatDrawer.less
src/common/wangeditor-customer/menu/common/AIChatDrawer.less
+1
-4
AIModal.jsx
src/common/wangeditor-customer/menu/common/AIModal.jsx
+17
-14
index.jsx
src/components/markdownRender/index.jsx
+1
-1
useAI.js
src/hooks/useAI.js
+151
-0
useTiangongAI.js
src/hooks/useTiangongAI.js
+1
-1
vite.config.js
vite.config.js
+20
-0
没有找到文件。
src/common/wangeditor-customer/menu/common/AIChatDrawer.jsx
浏览文件 @
1afbfb14
import
{
useState
,
useEffect
}
from
'react'
import
{
useState
,
useEffect
}
from
'react'
import
{
SendOutlined
}
from
'@ant-design/icons'
import
{
SendOutlined
}
from
'@ant-design/icons'
import
{
Drawer
,
Input
,
Button
,
message
}
from
'antd'
import
{
Drawer
,
Input
,
Button
,
message
,
Select
}
from
'antd'
import
dayjs
from
'dayjs
'
import
MarkdownRender
from
'@/components/markdownRender
'
import
{
useAI
Chat
}
from
'@/hooks/useTiangong
AI'
import
{
useAI
}
from
'@/hooks/use
AI'
import
normalAvatar
from
'@/assets/images/icon-normal-avatar.png'
//
import normalAvatar from '@/assets/images/icon-normal-avatar.png'
import
'./AIChatDrawer.less'
import
'./AIChatDrawer.less'
...
@@ -12,7 +12,7 @@ const AIChatDrawer = (props) => {
...
@@ -12,7 +12,7 @@ const AIChatDrawer = (props) => {
const
selectText
=
props
.
editor
.
getSelectionText
()
const
selectText
=
props
.
editor
.
getSelectionText
()
const
[
open
,
setOpen
]
=
useState
(
true
)
const
[
open
,
setOpen
]
=
useState
(
true
)
const
{
messages
,
post
,
isLoading
}
=
useAIChat
()
const
{
ai
,
setAI
,
options
,
messages
,
post
,
isLoading
}
=
useAI
()
const
[
value
,
setValue
]
=
useState
(
selectText
)
// 输入值
const
[
value
,
setValue
]
=
useState
(
selectText
)
// 输入值
...
@@ -35,7 +35,7 @@ const AIChatDrawer = (props) => {
...
@@ -35,7 +35,7 @@ const AIChatDrawer = (props) => {
// 提交对话
// 提交对话
const
targetChat
=
async
()
=>
{
const
targetChat
=
async
()
=>
{
if
(
value
)
{
if
(
value
)
{
post
({
userChatInpu
t
:
value
})
post
({
conten
t
:
value
})
setValue
(
''
)
setValue
(
''
)
}
else
{
}
else
{
message
.
error
(
'请输入对话内容!'
)
message
.
error
(
'请输入对话内容!'
)
...
@@ -58,26 +58,19 @@ const AIChatDrawer = (props) => {
...
@@ -58,26 +58,19 @@ const AIChatDrawer = (props) => {
{
messages
.
map
((
item
,
index
)
=>
{
{
messages
.
map
((
item
,
index
)
=>
{
return
(
return
(
<
div
className=
{
`chat-content-item`
}
key=
{
index
}
>
<
div
className=
{
`chat-content-item`
}
key=
{
index
}
>
{
item
.
role_type
===
'user'
&&
(
{
item
.
role
===
'assistant'
&&
(
<
div
className=
"time"
>
{
dayjs
(
item
.
time
).
format
(
'YYYY-MM-DD HH:mm'
)
}
</
div
>
)
}
{
item
.
role_type
===
'ai'
&&
(
<
div
className=
"inside"
>
<
div
className=
"inside"
>
<
div
className=
"ai-in-content"
>
<
div
className=
"ai-in-content"
>
<
div
className=
"
img
"
>
<
div
className=
"
ask-content
"
>
<
img
src=
{
item
.
role
?.
avatar
}
/
>
<
MarkdownRender
>
{
item
.
content
}
</
MarkdownRender
>
</
div
>
</
div
>
<
div
className=
"ask-content"
>
{
item
.
content
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
)
}
)
}
{
item
.
role
_type
===
'user'
&&
(
{
item
.
role
===
'user'
&&
(
<
div
className=
"inside inside-user"
>
<
div
className=
"inside inside-user"
>
<
div
className=
"user-in-content"
>
<
div
className=
"user-in-content"
>
<
div
className=
"ask-content"
>
{
item
.
content
}
</
div
>
<
div
className=
"ask-content"
>
{
item
.
content
}
</
div
>
<
div
className=
"img"
>
<
img
src=
{
normalAvatar
}
/>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
)
}
)
}
...
@@ -87,6 +80,7 @@ const AIChatDrawer = (props) => {
...
@@ -87,6 +80,7 @@ const AIChatDrawer = (props) => {
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
<
Select
value=
{
ai
}
options=
{
options
}
onChange=
{
setAI
}
style=
{
{
marginTop
:
'10px'
}
}
></
Select
>
<
div
className=
"ai-chat-ask"
>
<
div
className=
"ai-chat-ask"
>
<
div
className=
"text"
>
<
div
className=
"text"
>
<
Input
.
TextArea
<
Input
.
TextArea
...
...
src/common/wangeditor-customer/menu/common/AIChatDrawer.less
浏览文件 @
1afbfb14
...
@@ -12,13 +12,12 @@
...
@@ -12,13 +12,12 @@
flex-direction: column;
flex-direction: column;
.ai-chat-container {
.ai-chat-container {
flex: 1;
flex: 1;
height: calc(100% - 100px);
height: calc(100% - 100px
- 32px
);
.chat-content {
.chat-content {
height: 100%;
height: 100%;
overflow-x: hidden;
overflow-x: hidden;
overflow-y: auto;
overflow-y: auto;
box-sizing: border-box;
box-sizing: border-box;
padding-right: 17px;
border-radius: 5px;
border-radius: 5px;
border: 1px solid #e5e5e5;
border: 1px solid #e5e5e5;
.chat-content-padd {
.chat-content-padd {
...
@@ -50,7 +49,6 @@
...
@@ -50,7 +49,6 @@
}
}
.ask-content {
.ask-content {
background-color: #ab1941;
background-color: #ab1941;
margin-left: 10px;
padding: 5px 8px;
padding: 5px 8px;
color: #fff;
color: #fff;
font-size: 14px;
font-size: 14px;
...
@@ -80,7 +78,6 @@
...
@@ -80,7 +78,6 @@
}
}
.ask-content {
.ask-content {
background-color: #e5e5e5;
background-color: #e5e5e5;
margin-right: 10px;
padding: 5px 8px;
padding: 5px 8px;
color: #666;
color: #666;
font-size: 14px;
font-size: 14px;
...
...
src/common/wangeditor-customer/menu/common/AIModal.jsx
浏览文件 @
1afbfb14
import
{
useEffect
,
useState
}
from
'react'
import
{
useEffect
,
useState
}
from
'react'
import
{
Modal
,
Input
,
Button
,
Flex
,
Spin
}
from
'antd'
import
{
Modal
,
Input
,
Button
,
Flex
,
Spin
,
Select
}
from
'antd'
import
{
useAIEdit
}
from
'@/hooks/useBaiduAI'
import
{
useAI
}
from
'@/hooks/useAI'
import
{
SlateTransforms
}
from
'@wangeditor/editor'
import
{
SlateTransforms
}
from
'@wangeditor/editor'
const
{
TextArea
}
=
Input
const
{
TextArea
}
=
Input
...
@@ -17,7 +18,7 @@ const actionMap = {
...
@@ -17,7 +18,7 @@ const actionMap = {
(1)带有错误标点符号的句子:
(1)带有错误标点符号的句子:
(2)纠正标点符号之后正确的句子:
(2)纠正标点符号之后正确的句子:
(3)更新符号之后的完整内容:
(3)更新符号之后的完整内容:
具体要校对的内容如下:`
具体要校对的内容如下:`
,
},
},
contentInspect
:
{
contentInspect
:
{
name
:
'内容检查'
,
name
:
'内容检查'
,
...
@@ -27,39 +28,40 @@ const actionMap = {
...
@@ -27,39 +28,40 @@ const actionMap = {
(1)带有敏感词或错别字的句子:
(1)带有敏感词或错别字的句子:
(2)纠正敏感词或错别字之后正确的句子:
(2)纠正敏感词或错别字之后正确的句子:
(3)更新之后的完整内容:
(3)更新之后的完整内容:
具体要校对的内容如下:`
具体要校对的内容如下:`
,
},
},
translate
:
{
translate
:
{
name
:
'翻译'
,
name
:
'翻译'
,
prompt
:
`请将以下文本翻译成英文。具体要求如下:
prompt
:
`请将以下文本翻译成英文。具体要求如下:
1、请确保翻译准确、流畅,并尽量保留原文的语义和风格。
1、请确保翻译准确、流畅,并尽量保留原文的语义和风格。
2、翻译之后的文本以“翻译结果:”作为开头
2、翻译之后的文本以“翻译结果:”作为开头
待翻译文本:`
待翻译文本:`
,
}
}
,
}
}
export
default
function
AIModal
({
editor
,
action
})
{
export
default
function
AIModal
({
editor
,
action
})
{
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
true
)
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
true
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
[
content
,
setContent
]
=
useState
(
''
)
const
{
text
,
fetch
,
isLoading
}
=
useAIEdit
()
const
{
ai
,
setAI
,
options
,
messages
,
post
,
isLoading
}
=
useAI
()
const
actionText
=
actionMap
[
action
]?.
name
const
actionText
=
actionMap
[
action
]?.
name
const
text
=
messages
.
findLast
((
item
)
=>
item
.
role
===
'assistant'
)?.
content
||
''
const
[
selectionText
,
setSelectionText
]
=
useState
(
''
)
const
[
selectionText
,
setSelectionText
]
=
useState
(
''
)
useEffect
(()
=>
{
useEffect
(()
=>
{
const
selection
=
editor
.
getSelectionText
()
const
selection
=
editor
.
getSelectionText
()
if
(
selection
)
{
if
(
selection
)
{
setSelectionText
(
selection
)
setSelectionText
(
selection
)
setContent
(
selection
)
setContent
(
selection
)
fetch
({
messages
:
[{
role
:
'user'
,
content
:
actionMap
[
action
].
prompt
+
selection
}]
})
post
({
content
:
actionMap
[
action
].
prompt
+
selection
})
}
}
},
[
action
,
editor
,
fetch
])
},
[
action
,
editor
,
post
])
useEffect
(()
=>
{
useEffect
(()
=>
{
setContent
(
text
)
setContent
(
text
)
},
[
text
])
},
[
text
])
const
handleFetch
=
()
=>
{
const
handleFetch
=
()
=>
{
fetch
({
messages
:
[{
role
:
'user'
,
content
:
actionMap
[
action
].
prompt
+
selectionText
}]
})
post
({
content
:
actionMap
[
action
].
prompt
+
selectionText
})
}
}
const
handlePrimary
=
()
=>
{
const
handlePrimary
=
()
=>
{
...
@@ -97,8 +99,8 @@ export default function AIModal({ editor, action }) {
...
@@ -97,8 +99,8 @@ export default function AIModal({ editor, action }) {
editor
.
restoreSelection
()
editor
.
restoreSelection
()
// 删除当前选中的节点
// 删除当前选中的节点
SlateTransforms
.
removeNodes
(
editor
)
SlateTransforms
.
removeNodes
(
editor
)
const
resultArr
=
result
.
split
(
'
\
n'
).
filter
(
item
=>
item
)
const
resultArr
=
result
.
split
(
'
\
n'
).
filter
(
(
item
)
=>
item
)
const
nodeList
=
resultArr
.
map
(
item
=>
{
const
nodeList
=
resultArr
.
map
(
(
item
)
=>
{
return
{
type
:
'paragraph'
,
indent
:
'2em'
,
children
:
[{
text
:
item
}]
}
return
{
type
:
'paragraph'
,
indent
:
'2em'
,
children
:
[{
text
:
item
}]
}
})
})
// 插入节点
// 插入节点
...
@@ -114,12 +116,13 @@ export default function AIModal({ editor, action }) {
...
@@ -114,12 +116,13 @@ export default function AIModal({ editor, action }) {
onOk=
{
handlePrimary
}
onOk=
{
handlePrimary
}
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
>
onCancel=
{
()
=>
setIsModalOpen
(
false
)
}
>
<
Spin
spinning=
{
isLoading
}
>
<
Spin
spinning=
{
isLoading
}
>
<
TextArea
autoSize=
{
{
minRows
:
4
}
}
value=
{
content
}
onChange=
{
e
=>
setContent
(
e
.
target
.
value
)
}
/>
<
Select
value=
{
ai
}
options=
{
options
}
onChange=
{
setAI
}
style=
{
{
margin
:
'10px 0'
,
width
:
'100%'
}
}
></
Select
>
<
TextArea
autoSize=
{
{
minRows
:
4
}
}
value=
{
content
}
onChange=
{
(
e
)
=>
setContent
(
e
.
target
.
value
)
}
/>
</
Spin
>
</
Spin
>
<
br
/>
<
br
/>
<
Flex
gap=
"small"
justify=
"center"
>
<
Flex
gap=
"small"
justify=
"center"
>
<
Button
onClick=
{
()
=>
setIsModalOpen
(
false
)
}
>
取消
</
Button
>
<
Button
onClick=
{
()
=>
setIsModalOpen
(
false
)
}
>
取消
</
Button
>
<
Button
type=
"primary"
onClick=
{
handleFetch
}
>
<
Button
type=
"primary"
onClick=
{
handleFetch
}
loading=
{
isLoading
}
>
重新
{
actionText
}
重新
{
actionText
}
</
Button
>
</
Button
>
<
Button
type=
"primary"
onClick=
{
handlePrimary
}
>
<
Button
type=
"primary"
onClick=
{
handlePrimary
}
>
...
...
src/components/markdownRender/index.jsx
浏览文件 @
1afbfb14
import
MarkdownIt
from
'markdown-it'
import
MarkdownIt
from
'markdown-it'
import
hljs
from
'highlight.js'
import
hljs
from
'highlight.js'
export
default
function
MarkdownRender
er
({
children
})
{
export
default
function
MarkdownRender
({
children
})
{
const
md
=
new
MarkdownIt
({
const
md
=
new
MarkdownIt
({
html
:
true
,
html
:
true
,
highlight
:
function
(
str
,
lang
)
{
highlight
:
function
(
str
,
lang
)
{
...
...
src/hooks/useAI.js
0 → 100644
浏览文件 @
1afbfb14
import
{
useState
,
useEffect
,
useCallback
}
from
'react'
import
md5
from
'js-md5'
import
axios
from
'axios'
import
{
fetchEventSource
}
from
'@fortaine/fetch-event-source'
export
function
useAI
()
{
const
options
=
[
{
label
:
'文心一言'
,
value
:
'yiyan'
},
{
label
:
'DeepSeek'
,
value
:
'deepseek'
},
{
label
:
'通义千问'
,
value
:
'qwen'
},
{
label
:
'天工'
,
value
:
'tiangong'
},
]
const
[
ai
,
setAI
]
=
useState
(
localStorage
.
getItem
(
'ai'
)
||
'yiyan'
)
const
[
messages
,
setMessages
]
=
useState
([])
const
[
isLoading
,
setIsLoading
]
=
useState
(
false
)
useEffect
(()
=>
{
localStorage
.
setItem
(
'ai'
,
ai
)
},
[
ai
])
const
post
=
useCallback
(
async
(
data
)
=>
{
setIsLoading
(
true
)
setMessages
((
prevMessages
)
=>
[...
prevMessages
,
{
role
:
'user'
,
content
:
data
.
content
}])
try
{
switch
(
ai
)
{
case
'yiyan'
:
await
yiyan
(
data
)
break
case
'deepseek'
:
await
deepseek
(
data
)
break
case
'qwen'
:
await
qwen
(
data
)
break
case
'tiangong'
:
await
tiangong
(
data
)
break
default
:
throw
new
Error
(
'未找到对应的 AI 配置'
)
}
}
catch
(
err
)
{
console
.
error
(
'AI 请求失败:'
,
err
)
}
finally
{
setIsLoading
(
false
)
}
},
[
ai
]
// 依赖 `ai`,当 `ai` 变化时,重新创建 `post`
)
async
function
yiyan
(
data
)
{
const
getAccessToken
=
async
()
=>
{
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
}
`
)
return
resp
.
data
.
access_token
}
const
resp
=
await
axios
.
post
(
`/api/qianfan/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/eb-instant?access_token=
${
await
getAccessToken
()}
`
,
{
messages
:
[{
role
:
'user'
,
content
:
data
.
content
}],
}
)
setMessages
((
prevMessages
)
=>
[...
prevMessages
,
{
role
:
'assistant'
,
content
:
resp
.
data
.
result
}])
}
async
function
deepseek
(
data
)
{
const
apiKey
=
'sk-f1a6f0a7013241de8393cb2cb108e777'
const
resp
=
await
axios
.
post
(
'/api/deepseek/chat/completions'
,
{
model
:
'deepseek-chat'
,
messages
:
[{
role
:
'user'
,
content
:
data
.
content
}],
},
{
headers
:
{
'Content-Type'
:
'application/json'
,
Authorization
:
`Bearer
${
apiKey
}
`
},
}
)
if
(
resp
.
data
)
{
const
[
choice
=
{}]
=
resp
.
data
.
choices
setMessages
((
prevMessages
)
=>
[...
prevMessages
,
{
role
:
'assistant'
,
content
:
choice
.
message
.
content
}])
}
}
async
function
qwen
(
data
)
{
const
apiKey
=
'sk-afd0fcdb53bf4058b2068b8548820150'
const
resp
=
await
axios
.
post
(
'/api/qwen/compatible-mode/v1/chat/completions'
,
{
model
:
'qwen-max'
,
messages
:
[{
role
:
'user'
,
content
:
data
.
content
}],
},
{
headers
:
{
'Content-Type'
:
'application/json'
,
Authorization
:
`Bearer
${
apiKey
}
`
},
}
)
if
(
resp
.
data
)
{
const
[
choice
=
{}]
=
resp
.
data
.
choices
setMessages
((
prevMessages
)
=>
[...
prevMessages
,
{
role
:
'assistant'
,
content
:
choice
.
message
.
content
}])
}
}
async
function
tiangong
(
data
)
{
const
appKey
=
'a8701b73637562d33a53c668a90ee3be'
const
appSecret
=
'e191593f486bb88a39c634f46926762dddc97b9082e192af'
const
timestamp
=
Math
.
floor
(
Date
.
now
()
/
1000
)
const
sign
=
md5
(
`
${
appKey
}${
appSecret
}${
timestamp
}
`
)
return
await
fetchEventSource
(
'/api/tiangong/sky-saas-writing/api/v1/chat'
,
{
method
:
'POST'
,
headers
:
{
'Content-Type'
:
'application/json'
,
app_key
:
appKey
,
sign
,
timestamp
,
stream
:
'true'
},
body
:
JSON
.
stringify
({
chat_history
:
[{
role
:
'user'
,
content
:
data
.
content
}],
stream_resp_type
:
'update'
,
}),
async
onopen
(
response
)
{
console
.
log
(
response
)
if
(
response
.
ok
)
{
return
response
}
else
{
throw
response
}
},
onmessage
(
res
)
{
console
.
log
(
res
.
data
)
const
message
=
JSON
.
parse
(
res
.
data
)
if
(
message
.
type
!==
1
)
return
setMessages
((
prevMessages
)
=>
{
const
messageId
=
message
.
conversation_id
const
messageIndex
=
prevMessages
.
findIndex
((
message
)
=>
message
.
id
===
messageId
)
const
content
=
message
?.
arguments
?.[
0
]?.
messages
?.[
0
]?.
text
||
''
if
(
messageIndex
===
-
1
)
{
return
[...
prevMessages
,
{
id
:
messageId
,
role
:
'assistant'
,
content
}]
}
else
{
return
prevMessages
.
map
((
msg
)
=>
(
msg
.
id
===
messageId
?
{
...
msg
,
content
}
:
msg
))
}
})
setIsLoading
(
false
)
},
onerror
(
err
)
{
setIsLoading
(
false
)
throw
err
},
})
}
return
{
ai
,
setAI
,
options
,
post
,
messages
,
isLoading
}
}
src/hooks/useTiangongAI.js
浏览文件 @
1afbfb14
...
@@ -40,7 +40,7 @@ export function useAIChat() {
...
@@ -40,7 +40,7 @@ export function useAIChat() {
// 插入用户消息
// 插入用户消息
addMessage
({
addMessage
({
role_type
:
'user'
,
role_type
:
'user'
,
content
:
data
.
userChatInpu
t
,
content
:
data
.
conten
t
,
conversationId
:
`user-
${
timestamp
}
`
,
// 生成一个唯一的 conversationId
conversationId
:
`user-
${
timestamp
}
`
,
// 生成一个唯一的 conversationId
time
:
Date
.
now
(),
time
:
Date
.
now
(),
})
})
...
...
vite.config.js
浏览文件 @
1afbfb14
...
@@ -12,6 +12,26 @@ export default defineConfig(() => {
...
@@ -12,6 +12,26 @@ export default defineConfig(() => {
open
:
true
,
open
:
true
,
host
:
'dev.ezijing.com'
,
host
:
'dev.ezijing.com'
,
proxy
:
{
proxy
:
{
'/api/tiangong'
:
{
target
:
'https://api.singularity-ai.com'
,
changeOrigin
:
true
,
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api
\/
tiangong/
,
''
),
},
'/api/deepseek'
:
{
target
:
'https://api.deepseek.com'
,
changeOrigin
:
true
,
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api
\/
deepseek/
,
''
),
},
'/api/qwen'
:
{
target
:
'https://dashscope.aliyuncs.com'
,
changeOrigin
:
true
,
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api
\/
qwen/
,
''
),
},
'/api/qianfan'
:
{
target
:
'https://aip.baidubce.com'
,
changeOrigin
:
true
,
rewrite
:
(
path
)
=>
path
.
replace
(
/^
\/
api
\/
qianfan/
,
''
),
},
// '/api/wenku': {
// '/api/wenku': {
// target: 'https://wenchain.baidu.com',
// target: 'https://wenchain.baidu.com',
// changeOrigin: true,
// changeOrigin: true,
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论