refactor: markdown render
This commit is contained in:
parent
88aefb6ad1
commit
232892b71c
40
package.json
40
package.json
@ -30,43 +30,45 @@
|
|||||||
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
|
||||||
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
"@electron-toolkit/eslint-config-ts": "^1.0.1",
|
||||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||||
|
"@fontsource/inter": "^5.0.18",
|
||||||
|
"@reduxjs/toolkit": "^2.2.5",
|
||||||
"@types/lodash": "^4.17.5",
|
"@types/lodash": "^4.17.5",
|
||||||
"@types/node": "^18.19.9",
|
"@types/node": "^18.19.9",
|
||||||
"@types/react": "^18.2.48",
|
"@types/react": "^18.2.48",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.2.18",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"electron": "^28.2.0",
|
|
||||||
"electron-builder": "^24.9.1",
|
|
||||||
"electron-devtools-installer": "^3.2.0",
|
|
||||||
"electron-vite": "^2.0.0",
|
|
||||||
"eslint": "^8.56.0",
|
|
||||||
"eslint-plugin-react": "^7.34.3",
|
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
|
||||||
"eslint-plugin-unused-imports": "^4.0.0",
|
|
||||||
"prettier": "^3.2.4",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"sass": "^1.77.2",
|
|
||||||
"typescript": "^5.3.3",
|
|
||||||
"vite": "^5.0.12",
|
|
||||||
"@fontsource/inter": "^5.0.18",
|
|
||||||
"@reduxjs/toolkit": "^2.2.5",
|
|
||||||
"ahooks": "^3.8.0",
|
"ahooks": "^3.8.0",
|
||||||
"antd": "^5.18.3",
|
"antd": "^5.18.3",
|
||||||
"browser-image-compression": "^2.0.2",
|
"browser-image-compression": "^2.0.2",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
|
"electron": "^28.2.0",
|
||||||
|
"electron-builder": "^24.9.1",
|
||||||
|
"electron-devtools-installer": "^3.2.0",
|
||||||
|
"electron-vite": "^2.0.0",
|
||||||
"emittery": "^1.0.3",
|
"emittery": "^1.0.3",
|
||||||
"highlight.js": "^11.9.0",
|
"eslint": "^8.56.0",
|
||||||
|
"eslint-plugin-react": "^7.34.3",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
|
"eslint-plugin-unused-imports": "^4.0.0",
|
||||||
|
"i18next": "^23.11.5",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"marked": "^13.0.1",
|
|
||||||
"openai": "^4.52.1",
|
"openai": "^4.52.1",
|
||||||
|
"prettier": "^3.2.4",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-i18next": "^14.1.2",
|
||||||
|
"react-markdown": "^9.0.1",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-router": "6",
|
"react-router": "6",
|
||||||
"react-router-dom": "6",
|
"react-router-dom": "6",
|
||||||
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
|
"sass": "^1.77.2",
|
||||||
"styled-components": "^6.1.11",
|
"styled-components": "^6.1.11",
|
||||||
"uuid": "^10.0.0"
|
"typescript": "^5.3.3",
|
||||||
|
"uuid": "^10.0.0",
|
||||||
|
"vite": "^5.0.12"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^17.0.0 || ^18.0.0",
|
"react": "^17.0.0 || ^18.0.0",
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import SettingsPage from './pages/settings/SettingsPage'
|
|||||||
import { ConfigProvider } from 'antd'
|
import { ConfigProvider } from 'antd'
|
||||||
import TopViewContainer from './components/TopView'
|
import TopViewContainer from './components/TopView'
|
||||||
import { AntdThemeConfig } from './config/antd'
|
import { AntdThemeConfig } from './config/antd'
|
||||||
|
import './i18n'
|
||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -5,12 +5,6 @@
|
|||||||
user-select: text;
|
user-select: text;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
|
|
||||||
.hljs {
|
|
||||||
background-color: transparent;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p:first-of-type {
|
p:first-of-type {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
@ -78,27 +72,7 @@
|
|||||||
background-color: #555;
|
background-color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
span {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 3px 5px;
|
|
||||||
border-radius: 2px;
|
|
||||||
font-size: 90%;
|
|
||||||
display: inline-block;
|
|
||||||
font-family:
|
|
||||||
ui-monospace,
|
|
||||||
SFMono-Regular,
|
|
||||||
SF Mono,
|
|
||||||
Menlo,
|
|
||||||
Consolas,
|
|
||||||
Liberation Mono,
|
|
||||||
monospace;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/renderer/src/i18n/index.ts
Normal file
40
src/renderer/src/i18n/index.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import i18n from 'i18next'
|
||||||
|
import { initReactI18next } from 'react-i18next'
|
||||||
|
|
||||||
|
const resources = {
|
||||||
|
'en-US': {
|
||||||
|
translation: {
|
||||||
|
settings: {
|
||||||
|
title: 'Settings',
|
||||||
|
general: 'General',
|
||||||
|
provider: 'Model Provider',
|
||||||
|
model: 'Model Settings',
|
||||||
|
assistant: 'Default Assistant',
|
||||||
|
about: 'About'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'zh-CN': {
|
||||||
|
translation: {
|
||||||
|
settings: {
|
||||||
|
title: '设置',
|
||||||
|
general: '常规',
|
||||||
|
provider: '模型提供商',
|
||||||
|
model: '模型设置',
|
||||||
|
assistant: '默认助手',
|
||||||
|
about: '关于'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i18n.use(initReactI18next).init({
|
||||||
|
resources,
|
||||||
|
lng: 'en-US',
|
||||||
|
fallbackLng: 'en-US',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n
|
||||||
@ -4,7 +4,7 @@ import styled from 'styled-components'
|
|||||||
import Inputbar from './Inputbar'
|
import Inputbar from './Inputbar'
|
||||||
import Messages from './Messages'
|
import Messages from './Messages'
|
||||||
import { Flex } from 'antd'
|
import { Flex } from 'antd'
|
||||||
import TopicList from './TopicList'
|
import Topics from './Topics'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
import { useActiveTopic } from '@renderer/hooks/useTopic'
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ const Chat: FC<Props> = (props) => {
|
|||||||
<Messages assistant={assistant} topic={activeTopic} />
|
<Messages assistant={assistant} topic={activeTopic} />
|
||||||
<Inputbar assistant={assistant} setActiveTopic={setActiveTopic} />
|
<Inputbar assistant={assistant} setActiveTopic={setActiveTopic} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<TopicList assistant={assistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
|
<Topics assistant={assistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
68
src/renderer/src/pages/home/components/CodeBlock.tsx
Normal file
68
src/renderer/src/pages/home/components/CodeBlock.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import SyntaxHighlighter from 'react-syntax-highlighter'
|
||||||
|
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import { CopyOutlined } from '@ant-design/icons'
|
||||||
|
|
||||||
|
interface CodeBlockProps {
|
||||||
|
children: string
|
||||||
|
className?: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeBlock: React.FC<CodeBlockProps> = ({ children, className, ...rest }) => {
|
||||||
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
|
|
||||||
|
const onCopy = () => {
|
||||||
|
navigator.clipboard.writeText(children)
|
||||||
|
window.message.success({ content: 'Copied!', key: 'copy-code' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return match ? (
|
||||||
|
<div>
|
||||||
|
<CodeHeader>
|
||||||
|
<CodeLanguage>{match[1]}</CodeLanguage>
|
||||||
|
<CopyOutlined className="copy" onClick={onCopy} />
|
||||||
|
</CodeHeader>
|
||||||
|
<SyntaxHighlighter
|
||||||
|
{...rest}
|
||||||
|
language={match[1]}
|
||||||
|
style={atomDark}
|
||||||
|
customStyle={{ borderTopLeftRadius: 0, borderTopRightRadius: 0, marginTop: 0 }}>
|
||||||
|
{String(children).replace(/\n$/, '')}
|
||||||
|
</SyntaxHighlighter>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<code {...rest} className={className}>
|
||||||
|
{children}
|
||||||
|
</code>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeHeader = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #323232;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-top-left-radius: 8px;
|
||||||
|
border-top-right-radius: 8px;
|
||||||
|
.copy {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
.copy:hover {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const CodeLanguage = styled.div`
|
||||||
|
font-weight: bold;
|
||||||
|
`
|
||||||
|
|
||||||
|
export default CodeBlock
|
||||||
@ -37,7 +37,8 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
content: text,
|
content: text,
|
||||||
assistantId: assistant.id,
|
assistantId: assistant.id,
|
||||||
topicId: assistant.topics[0].id || uuid(),
|
topicId: assistant.topics[0].id || uuid(),
|
||||||
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
status: 'success'
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE, message)
|
EventEmitter.emit(EVENT_NAMES.SEND_MESSAGE, message)
|
||||||
@ -70,9 +71,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
setActiveTopic(topic)
|
setActiveTopic(topic)
|
||||||
}, [addTopic, setActiveTopic])
|
}, [addTopic, setActiveTopic])
|
||||||
|
|
||||||
const clearTopic = () => {
|
const clearTopic = () => EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES)
|
||||||
EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command or Ctrl + N create new topic
|
// Command or Ctrl + N create new topic
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { Message } from '@renderer/types'
|
import { Message } from '@renderer/types'
|
||||||
import { Avatar } from 'antd'
|
import { Avatar } from 'antd'
|
||||||
import { marked } from 'marked'
|
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import Logo from '@renderer/assets/images/logo.png'
|
import Logo from '@renderer/assets/images/logo.png'
|
||||||
import useAvatar from '@renderer/hooks/useAvatar'
|
import useAvatar from '@renderer/hooks/useAvatar'
|
||||||
import { CopyOutlined, DeleteOutlined } from '@ant-design/icons'
|
import { CopyOutlined, DeleteOutlined } from '@ant-design/icons'
|
||||||
|
import Markdown from 'react-markdown'
|
||||||
|
import CodeBlock from './CodeBlock'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message: Message
|
message: Message
|
||||||
@ -36,7 +37,10 @@ const MessageItem: FC<Props> = ({ message, showMenu, onDeleteMessage }) => {
|
|||||||
<MessageContainer key={message.id}>
|
<MessageContainer key={message.id}>
|
||||||
<AvatarWrapper>{message.role === 'assistant' ? <Avatar src={Logo} /> : <Avatar src={avatar} />}</AvatarWrapper>
|
<AvatarWrapper>{message.role === 'assistant' ? <Avatar src={Logo} /> : <Avatar src={avatar} />}</AvatarWrapper>
|
||||||
<MessageContent>
|
<MessageContent>
|
||||||
<div className="markdown" dangerouslySetInnerHTML={{ __html: marked(message.content) }} />
|
<Markdown className="markdown" components={{ code: CodeBlock as any }}>
|
||||||
|
{message.content}
|
||||||
|
</Markdown>
|
||||||
|
{/* <Markdown className="markdown" dangerouslySetInnerHTML={{ __html: marked(message.content) }} /> */}
|
||||||
{showMenu && (
|
{showMenu && (
|
||||||
<MenusBar className="menubar">
|
<MenusBar className="menubar">
|
||||||
<CopyOutlined onClick={onCopy} />
|
<CopyOutlined onClick={onCopy} />
|
||||||
@ -60,6 +64,8 @@ const AvatarWrapper = styled.div`
|
|||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// const Markdown = styled.div``
|
||||||
|
|
||||||
const MessageContent = styled.div`
|
const MessageContent = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { FC, useCallback, useEffect, useRef, useState } from 'react'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import MessageItem from './Message'
|
import MessageItem from './Message'
|
||||||
import { reverse } from 'lodash'
|
import { reverse } from 'lodash'
|
||||||
import hljs from 'highlight.js'
|
|
||||||
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { DEFAULT_TOPIC_NAME } from '@renderer/config/constant'
|
import { DEFAULT_TOPIC_NAME } from '@renderer/config/constant'
|
||||||
@ -91,8 +90,6 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
|||||||
})
|
})
|
||||||
}, [topic.id])
|
}, [topic.id])
|
||||||
|
|
||||||
useEffect(() => hljs.highlightAll(), [messages, lastMessage])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
messagesRef.current?.scrollTo({ top: 100000, behavior: 'auto' })
|
messagesRef.current?.scrollTo({ top: 100000, behavior: 'auto' })
|
||||||
}, [messages])
|
}, [messages])
|
||||||
|
|||||||
@ -15,7 +15,7 @@ interface Props {
|
|||||||
setActiveTopic: (topic: Topic) => void
|
setActiveTopic: (topic: Topic) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopicList: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
const Topics: FC<Props> = ({ assistant, activeTopic, setActiveTopic }) => {
|
||||||
const { showRightSidebar } = useShowRightSidebar()
|
const { showRightSidebar } = useShowRightSidebar()
|
||||||
const { removeTopic, updateTopic, removeAllTopics } = useAssistant(assistant.id)
|
const { removeTopic, updateTopic, removeAllTopics } = useAssistant(assistant.id)
|
||||||
const currentTopic = useRef<Topic | null>(null)
|
const currentTopic = useRef<Topic | null>(null)
|
||||||
@ -162,4 +162,4 @@ const DeleteIcon = styled(DeleteOutlined)`
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default TopicList
|
export default Topics
|
||||||
@ -7,33 +7,35 @@ import AboutSettings from './AboutSettings'
|
|||||||
import AssistantSettings from './AssistantSettings'
|
import AssistantSettings from './AssistantSettings'
|
||||||
import ModelSettings from './ModelSettings'
|
import ModelSettings from './ModelSettings'
|
||||||
import ProviderSettings from './ProviderSettings'
|
import ProviderSettings from './ProviderSettings'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const SettingsPage: FC = () => {
|
const SettingsPage: FC = () => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<NavbarCenter>Settings</NavbarCenter>
|
<NavbarCenter>{t('settings.title')}</NavbarCenter>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
<SettingMenus>
|
<SettingMenus>
|
||||||
<MenuItemLink to="/settings/general">
|
<MenuItemLink to="/settings/general">
|
||||||
<MenuItem className={isRoute('/settings/general')}>General</MenuItem>
|
<MenuItem className={isRoute('/settings/general')}>{t('settings.general')}</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/provider">
|
<MenuItemLink to="/settings/provider">
|
||||||
<MenuItem className={isRoute('/settings/provider')}>Model Provider</MenuItem>
|
<MenuItem className={isRoute('/settings/provider')}>{t('settings.provider')}</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/model">
|
<MenuItemLink to="/settings/model">
|
||||||
<MenuItem className={isRoute('/settings/model')}>Model Settings</MenuItem>
|
<MenuItem className={isRoute('/settings/model')}>{t('settings.model')}</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/assistant">
|
<MenuItemLink to="/settings/assistant">
|
||||||
<MenuItem className={isRoute('/settings/assistant')}>Default Assistant</MenuItem>
|
<MenuItem className={isRoute('/settings/assistant')}>{t('settings.assistant')}</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
<MenuItemLink to="/settings/about">
|
<MenuItemLink to="/settings/about">
|
||||||
<MenuItem className={isRoute('/settings/about')}>About</MenuItem>
|
<MenuItem className={isRoute('/settings/about')}>{t('settings.about')}</MenuItem>
|
||||||
</MenuItemLink>
|
</MenuItemLink>
|
||||||
</SettingMenus>
|
</SettingMenus>
|
||||||
<SettingContent>
|
<SettingContent>
|
||||||
|
|||||||
@ -35,7 +35,8 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
|
|||||||
assistantId: assistant.id,
|
assistantId: assistant.id,
|
||||||
topicId: topic.id,
|
topicId: topic.id,
|
||||||
modelId: model.id,
|
modelId: model.id,
|
||||||
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
createdAt: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
status: 'pending'
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -60,6 +61,8 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
|
|||||||
_message.content = `Error: ${error.message}`
|
_message.content = `Error: ${error.message}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_message.status = 'success'
|
||||||
|
|
||||||
EventEmitter.emit(EVENT_NAMES.AI_CHAT_COMPLETION, _message)
|
EventEmitter.emit(EVENT_NAMES.AI_CHAT_COMPLETION, _message)
|
||||||
|
|
||||||
return _message
|
return _message
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export type Message = {
|
|||||||
topicId: string
|
topicId: string
|
||||||
modelId?: string
|
modelId?: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
|
status: 'pending' | 'success' | 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Topic = {
|
export type Topic = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user