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

chore: update

上级 ec63f18b
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -13,6 +13,7 @@
"dependencies": {
"@ant-design/icons": "^5.4.0",
"@chuangkit/chuangkit-design": "^2.0.8",
"@ezijing/ai-react": "^1.0.37",
"@fortaine/fetch-event-source": "^3.0.6",
"@reduxjs/toolkit": "^1.9.7",
"@wangeditor/editor": "^5.1.23",
......
import BaseModalMenu from './common/BaseModalMenu'
import AISearchModal from './common/AISearchModal'
class AIBaiduSearch extends BaseModalMenu {
constructor() {
super()
this.title = '学术搜索'
this.iconSvg = `<svg style="fill:none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-file-search"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 3v4a1 1 0 0 0 1 1h4" /><path d="M12 21h-5a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v4.5" /><path d="M16.5 17.5m-2.5 0a2.5 2.5 0 1 0 5 0a2.5 2.5 0 1 0 -5 0" /><path d="M18.5 19.5l2.5 2.5" /></svg>`
}
getValue(editor) {
return <AISearchModal key={Date.now()} editor={editor}></AISearchModal>
}
}
export default {
key: 'AIBaiduSearch',
factory() {
return new AIBaiduSearch()
},
}
import BaseModalMenu from './common/BaseModalMenu'
import AIImageModal from './common/AIImageModal'
class AIImage extends BaseModalMenu {
constructor() {
super()
this.title = 'AI图片'
this.iconSvg = `<svg style="fill:none" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pen"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/></svg>`
}
getValue(editor) {
return <AIImageModal key={Date.now()} editor={editor}></AIImageModal>
}
}
export default {
key: 'AIImage',
factory() {
return new AIImage()
},
}
import { useState, useEffect, useRef } from 'react'
import { SendOutlined, UndoOutlined, CopyOutlined, LinkOutlined } from '@ant-design/icons'
import { ConfigProvider, Modal, Input, Button } from 'antd'
const { TextArea } = Input
import './AISearchModal.less'
import { useSearch } from '@/hooks/useWenku'
import { useCopyToClipboard } from 'react-use'
export default function AIModal() {
const [isModalOpen, setIsModalOpen] = useState(true)
const [content, setContent] = useState('')
const [textIndent, setTextIndent] = useState(0)
const prePromptRef = useRef(null)
const messageScrollRef = useRef(null)
const { messages, isLoading, query } = useSearch()
useEffect(() => {
if (prePromptRef.current) {
const width = prePromptRef.current.offsetWidth + 10
setTextIndent(width)
}
}, [isModalOpen])
useEffect(() => {
if (messageScrollRef.current) {
const scrollContainer = messageScrollRef.current
scrollContainer.scrollTop = scrollContainer.scrollHeight
}
}, [messages])
const prePrompt = '帮我找一些文献资料,主题是:'
const handleEnterSearch = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
!isLoading && handleSearch()
}
}
const handleSearch = () => {
query(prePrompt + content)
setContent('')
}
const [, copyToClipboard] = useCopyToClipboard()
const handleCopy = (content) => {
copyToClipboard(content)
}
const MessageRender = ({ msg }) => {
if (msg.role === 'ai') {
const copyMessageContent = msg.searchReferList
?.map((refer, index) => `${index + 1}.《${refer.title}》\n${refer.abstract}\n`)
.join('\n')
return (
msg.searchReferList?.length > 0 && (
<div className={`message-item ${msg.role}`}>
<div className="message-box">
<div className="message-content">
{msg.searchReferList.map((refer, index) => {
return (
<div key={index} className="result-item">
<p>
<strong>
{index + 1}.《{refer.title}
</strong>
</p>
<p>
{refer.abstract}
<a href={refer.orgUrl} target="_blank" rel="noreferrer">
<LinkOutlined />
</a>
</p>
</div>
)
})}
</div>
<div className="message-tools">
<Button
type="text"
size="small"
icon={<UndoOutlined />}
disabled={isLoading}
onClick={() => query(msg.userQuery)}>
重新生成
</Button>
<Button type="text" size="small" icon={<CopyOutlined />} onClick={() => handleCopy(copyMessageContent)}>
复制内容
</Button>
</div>
</div>
</div>
)
)
} else {
return (
<div className={`message-item ${msg.role}`}>
<div className="message-box">
<div className="message-content">{msg.content}</div>
</div>
</div>
)
}
}
return (
<ConfigProvider theme={{ components: { Modal: { headerBg: '#f7f8fa', contentBg: '#f7f8fa' } } }}>
<Modal
title="学术/文献搜索:"
open={isModalOpen}
footer={null}
onCancel={() => setIsModalOpen(false)}
width={1000}>
<div className="message-scroll" ref={messageScrollRef}>
{messages.map((msg, index) => {
return <MessageRender msg={msg} key={index}></MessageRender>
})}
</div>
<div className="input-container">
<div className="input-box">
<div className="edit-area">
{prePrompt && (
<span className="pre-prompt" ref={prePromptRef}>
{prePrompt}
</span>
)}
<TextArea
className="content"
autoSize
value={content}
placeholder="请输入关键词"
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleEnterSearch}
style={{ textIndent }}
/>
</div>
<div className="input-tools">
<Button type="primary" size="large" icon={<SendOutlined />} onClick={handleSearch} loading={isLoading} />
</div>
</div>
</div>
</Modal>
</ConfigProvider>
)
}
import { useState, useEffect, useRef } from 'react'
import {
SendOutlined,
UndoOutlined,
CopyOutlined,
FileTextOutlined,
FileWordOutlined,
DownloadOutlined,
PlusCircleOutlined,
DeleteOutlined,
} from '@ant-design/icons'
import { ConfigProvider, Modal, Input, Button, Dropdown } from 'antd'
const { TextArea } = Input
import './AISearchModal.less'
import { usePaper } from '@/hooks/useWenku'
import { useCopyToClipboard } from 'react-use'
import { CircleEllipsis } from 'lucide-react'
export default function AIModal() {
const [isModalOpen, setIsModalOpen] = useState(true)
const [content, setContent] = useState('')
const [textIndent, setTextIndent] = useState(0)
const prePromptRef = useRef(null)
const messageScrollRef = useRef(null)
const {
messages,
setMessages,
isLoading,
generateOutline,
generatePaper,
chapterTagRender,
getChaptersMarkdown,
addChapter,
updateChapter,
removeChapter,
} = usePaper()
useEffect(() => {
if (prePromptRef.current) {
const width = prePromptRef.current.offsetWidth + 10
setTextIndent(width)
}
}, [isModalOpen])
const [isEditMessage, setIsEditMessage] = useState(false)
useEffect(() => {
if (messageScrollRef.current && !isEditMessage) {
const scrollContainer = messageScrollRef.current
scrollContainer.scrollTop = scrollContainer.scrollHeight
}
}, [messages, isEditMessage])
const prePrompt = '帮我生成一篇课题报告,主题是:'
const handleEnterSearch = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
!isLoading && handleSearch()
}
}
const handleSearch = () => {
generateOutline(prePrompt + content)
setContent('')
setIsEditMessage(false)
}
const [, copyToClipboard] = useCopyToClipboard()
// 复制内容
const handleCopy = (msg) => {
copyToClipboard(getChaptersMarkdown(msg.chapters))
}
// 生成论文
const handleGeneratePaper = async (msg) => {
setMessages((prevMessages) => {
return [...prevMessages, { content: '正在生成长文...', role: 'ai', tips: '预计10分钟', queryID: msg.queryID }]
})
const paper = await generatePaper({
userQuery: msg.userQuery,
queryID: msg.queryID,
outline: getChaptersMarkdown(msg.chapters),
})
setMessages((prevMessages) => {
prevMessages.pop()
return [...prevMessages, { content: '已为您生成初稿,请点击下载', role: 'ai', queryID: msg.queryID, paper }]
})
}
let chapterMenuItems = [
{ key: 'add', label: '新增', icon: <PlusCircleOutlined style={{ fontSize: 14 }} /> },
{ key: 'remove', label: '删除', icon: <DeleteOutlined style={{ fontSize: 14 }} /> },
]
const handleMenuClick = async (e, chapter, message) => {
setIsEditMessage(true)
if (e.key === 'add') addChapter(chapter, message)
if (e.key === 'remove') removeChapter(chapter, message)
}
const MessageRender = ({ msg }) => {
if (msg.role === 'ai') {
if (msg.chapters && msg.chapters.length) {
return (
<div className={`message-item ${msg.role}`}>
<div className="message-box">
<div className="message-content">
{msg.chapters.map((item) => {
return (
<div className="chapter-item" key={item.id}>
<div className="chapter-left">
<div className="chapter-left-title">{chapterTagRender(item, msg.chapters)}</div>
<div className="line-dot">
<div className="dot"></div>
<div className="line"></div>
</div>
</div>
<div className="chapter-right">
<div className="chapter-right-content">
<Input
className="chapter-title"
placeholder="请输入标题"
defaultValue={item.title}
variant="borderless"
onBlur={(e) => updateChapter(item, msg, { ...item, title: e.target.value })}
/>
{item.level !== 1 && (
<Input
className="chapter-desc"
placeholder="请输入你的备注,如这个章节必须包含正反观点等。"
defaultValue={item.desc}
variant="borderless"
onBlur={(e) => updateChapter(item, msg, { ...item, desc: e.target.value })}
/>
)}
{/* <h3>{item.title}</h3> */}
{/* <p>{item.desc}</p> */}
</div>
<div className="chapter-right-tools">
{item.level !== 1 && (
<Dropdown
menu={{ items: chapterMenuItems, onClick: (e) => handleMenuClick(e, item, msg) }}
getPopupContainer={(triggerNode) => triggerNode.parentNode}>
<Button type="text" icon={<CircleEllipsis color="#222" size={20} />}></Button>
</Dropdown>
)}
</div>
</div>
</div>
)
})}
</div>
<div className="message-tools">
<Button
type="text"
size="small"
icon={<UndoOutlined />}
disabled={isLoading}
onClick={() => generateOutline(msg.userQuery)}>
换个大纲
</Button>
<Button
type="text"
size="small"
icon={<FileTextOutlined />}
disabled={isLoading}
onClick={() => handleGeneratePaper(msg)}>
生成长文
</Button>
<Button type="text" size="small" icon={<CopyOutlined />} onClick={() => handleCopy(msg)}>
复制内容
</Button>
</div>
</div>
</div>
)
} else {
return (
<div className={`message-item ${msg.role}`}>
<div className="message-box">
<div className="message-content">
{msg.content}
{msg.paper?.downloadLink && (
<div className="message-file">
<div className="message-file-content">
<a href={msg.paper.downloadLink} target="_blank" rel="noreferrer">
<FileWordOutlined />
商业数据分析研究
</a>
</div>
<div className="message-file-tools">
<a href={msg.paper.downloadLink} target="_blank" rel="noreferrer">
<DownloadOutlined />
</a>
</div>
</div>
)}
</div>
</div>
</div>
)
}
} else {
return (
<div className={`message-item ${msg.role}`}>
<div className="message-box">
<div className="message-content">{msg.content}</div>
</div>
</div>
)
}
}
return (
<ConfigProvider theme={{ components: { Modal: { headerBg: '#f7f8fa', contentBg: '#f7f8fa' } } }}>
<Modal title="AI图片" open={isModalOpen} footer={null} onCancel={() => setIsModalOpen(false)} width={1000}>
<div className="message-scroll" ref={messageScrollRef}>
{messages.map((msg) => {
return <MessageRender msg={msg} key={msg.id}></MessageRender>
})}
</div>
<div className="input-container">
<div className="input-box">
<div className="edit-area">
{prePrompt && (
<span className="pre-prompt" ref={prePromptRef}>
{prePrompt}
</span>
)}
<TextArea
className="content"
autoSize
value={content}
placeholder="今天需要我做些什么?shift+enter换行"
onChange={(e) => setContent(e.target.value)}
onKeyDown={handleEnterSearch}
style={{ textIndent }}
/>
</div>
<div className="input-tools">
{/* <Button type="text" icon={<UploadOutlined />} /> */}
<Button type="primary" size="large" icon={<SendOutlined />} onClick={handleSearch} loading={isLoading} />
</div>
</div>
<div className="upload-list"></div>
</div>
</Modal>
</ConfigProvider>
)
}
......@@ -12,26 +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/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,
......@@ -40,6 +40,7 @@ export default defineConfig(() => {
'/api': {
target: 'https://zijingebook.ezijing.com',
changeOrigin: true,
secure: false,
},
},
},
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论