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

chore: update

上级 cf325497
import { useState, useEffect } from 'react'
import { SendOutlined } from '@ant-design/icons'
import { Drawer, Input, Button, message } from 'antd'
import dayjs from 'dayjs'
import { useAIChat } from '@/hooks/useTiangongAI'
import { Drawer, Input, Button, message, Select } from 'antd'
import MarkdownRender from '@/components/markdownRender'
import { useAI } from '@/hooks/useAI'
import normalAvatar from '@/assets/images/icon-normal-avatar.png'
// import normalAvatar from '@/assets/images/icon-normal-avatar.png'
import './AIChatDrawer.less'
......@@ -12,7 +12,7 @@ const AIChatDrawer = (props) => {
const selectText = props.editor.getSelectionText()
const [open, setOpen] = useState(true)
const { messages, post, isLoading } = useAIChat()
const { ai, setAI, options, messages, post, isLoading } = useAI()
const [value, setValue] = useState(selectText) // 输入值
......@@ -35,7 +35,7 @@ const AIChatDrawer = (props) => {
// 提交对话
const targetChat = async () => {
if (value) {
post({ userChatInput: value })
post({ content: value })
setValue('')
} else {
message.error('请输入对话内容!')
......@@ -58,26 +58,19 @@ const AIChatDrawer = (props) => {
{messages.map((item, index) => {
return (
<div className={`chat-content-item`} key={index}>
{item.role_type === 'user' && (
<div className="time">{dayjs(item.time).format('YYYY-MM-DD HH:mm')}</div>
)}
{item.role_type === 'ai' && (
{item.role === 'assistant' && (
<div className="inside">
<div className="ai-in-content">
<div className="img">
<img src={item.role?.avatar} />
<div className="ask-content">
<MarkdownRender>{item.content}</MarkdownRender>
</div>
<div className="ask-content">{item.content}</div>
</div>
</div>
)}
{item.role_type === 'user' && (
{item.role === 'user' && (
<div className="inside inside-user">
<div className="user-in-content">
<div className="ask-content">{item.content}</div>
<div className="img">
<img src={normalAvatar} />
</div>
</div>
</div>
)}
......@@ -87,6 +80,7 @@ const AIChatDrawer = (props) => {
</div>
</div>
</div>
<Select value={ai} options={options} onChange={setAI} style={{ marginTop: '10px' }}></Select>
<div className="ai-chat-ask">
<div className="text">
<Input.TextArea
......
......@@ -12,13 +12,12 @@
flex-direction: column;
.ai-chat-container {
flex: 1;
height: calc(100% - 100px);
height: calc(100% - 100px - 32px);
.chat-content {
height: 100%;
overflow-x: hidden;
overflow-y: auto;
box-sizing: border-box;
padding-right: 17px;
border-radius: 5px;
border: 1px solid #e5e5e5;
.chat-content-padd {
......@@ -50,7 +49,6 @@
}
.ask-content {
background-color: #ab1941;
margin-left: 10px;
padding: 5px 8px;
color: #fff;
font-size: 14px;
......@@ -80,7 +78,6 @@
}
.ask-content {
background-color: #e5e5e5;
margin-right: 10px;
padding: 5px 8px;
color: #666;
font-size: 14px;
......
import { useEffect, useState } from 'react'
import { Modal, Input, Button, Flex, Spin } from 'antd'
import { useAIEdit } from '@/hooks/useBaiduAI'
import { Modal, Input, Button, Flex, Spin, Select } from 'antd'
import { useAI } from '@/hooks/useAI'
import { SlateTransforms } from '@wangeditor/editor'
const { TextArea } = Input
......@@ -17,7 +18,7 @@ const actionMap = {
(1)带有错误标点符号的句子:
(2)纠正标点符号之后正确的句子:
(3)更新符号之后的完整内容:
具体要校对的内容如下:`
具体要校对的内容如下:`,
},
contentInspect: {
name: '内容检查',
......@@ -27,39 +28,40 @@ const actionMap = {
(1)带有敏感词或错别字的句子:
(2)纠正敏感词或错别字之后正确的句子:
(3)更新之后的完整内容:
具体要校对的内容如下:`
具体要校对的内容如下:`,
},
translate: {
name: '翻译',
prompt: `请将以下文本翻译成英文。具体要求如下:
1、请确保翻译准确、流畅,并尽量保留原文的语义和风格。
2、翻译之后的文本以“翻译结果:”作为开头
待翻译文本:`
}
待翻译文本:`,
},
}
export default function AIModal({ editor, action }) {
const [isModalOpen, setIsModalOpen] = useState(true)
const [content, setContent] = useState('')
const { text, fetch, isLoading } = useAIEdit()
const { ai, setAI, options, messages, post, isLoading } = useAI()
const actionText = actionMap[action]?.name
const text = messages.findLast((item) => item.role === 'assistant')?.content || ''
const [selectionText, setSelectionText] = useState('')
useEffect(() => {
const selection = editor.getSelectionText()
if (selection) {
setSelectionText(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(() => {
setContent(text)
}, [text])
const handleFetch = () => {
fetch({ messages: [{ role: 'user', content: actionMap[action].prompt + selectionText }] })
post({ content: actionMap[action].prompt + selectionText })
}
const handlePrimary = () => {
......@@ -97,8 +99,8 @@ export default function AIModal({ editor, action }) {
editor.restoreSelection()
// 删除当前选中的节点
SlateTransforms.removeNodes(editor)
const resultArr = result.split('\n').filter(item => item)
const nodeList = resultArr.map(item => {
const resultArr = result.split('\n').filter((item) => item)
const nodeList = resultArr.map((item) => {
return { type: 'paragraph', indent: '2em', children: [{ text: item }] }
})
// 插入节点
......@@ -114,12 +116,13 @@ export default function AIModal({ editor, action }) {
onOk={handlePrimary}
onCancel={() => setIsModalOpen(false)}>
<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>
<br />
<Flex gap="small" justify="center">
<Button onClick={() => setIsModalOpen(false)}>取消</Button>
<Button type="primary" onClick={handleFetch}>
<Button type="primary" onClick={handleFetch} loading={isLoading}>
重新{actionText}
</Button>
<Button type="primary" onClick={handlePrimary}>
......
import MarkdownIt from 'markdown-it'
import hljs from 'highlight.js'
export default function MarkdownRenderer({ children }) {
export default function MarkdownRender({ children }) {
const md = new MarkdownIt({
html: true,
highlight: function (str, lang) {
......
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 }
}
......@@ -40,7 +40,7 @@ export function useAIChat() {
// 插入用户消息
addMessage({
role_type: 'user',
content: data.userChatInput,
content: data.content,
conversationId: `user-${timestamp}`, // 生成一个唯一的 conversationId
time: Date.now(),
})
......
......@@ -12,6 +12,26 @@ export default defineConfig(() => {
open: true,
host: 'dev.ezijing.com',
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': {
// target: 'https://wenchain.baidu.com',
// changeOrigin: true,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论