feat: add pause icon to pause chat completion

This commit is contained in:
kangfenmao 2024-07-15 13:14:15 +08:00
parent a97d6f024b
commit e8bdf9d5fd
6 changed files with 52 additions and 20 deletions

View File

@ -39,7 +39,8 @@ const resources = {
'error.enter.api.host': 'Please enter your API host first',
'error.enter.model': 'Please select a model first',
'api.connection.failed': 'Connection failed',
'api.connection.success': 'Connection successful'
'api.connection.success': 'Connection successful',
'chat.completion.paused': 'Chat completion paused'
},
assistant: {
'default.name': 'Default Assistant',
@ -61,7 +62,8 @@ const resources = {
'input.clear.title': 'Clear all messages?',
'input.clear.content': 'Are you sure to clear all messages?',
'input.placeholder': 'Type your message here...',
'input.send': 'Send'
'input.send': 'Send',
'input.pause': 'Pause'
},
apps: {
title: 'Agents'
@ -146,7 +148,8 @@ const resources = {
'error.enter.api.host': '请输入您的 API 地址',
'error.enter.model': '请选择一个模型',
'api.connection.failed': '连接失败',
'api.connection.successful': '连接成功'
'api.connection.successful': '连接成功',
'chat.completion.paused': '会话已停止'
},
assistant: {
'default.name': '默认助手',
@ -168,7 +171,8 @@ const resources = {
'input.clear.title': '清除所有消息?',
'input.clear.content': '确定要清除所有消息吗?',
'input.placeholder': '在这里输入消息...',
'input.send': '发送'
'input.send': '发送',
'input.pause': '暂停'
},
apps: {
title: '智能体'

View File

@ -12,6 +12,7 @@ import {
FullscreenExitOutlined,
FullscreenOutlined,
HistoryOutlined,
PauseCircleOutlined,
PlusCircleOutlined
} from '@ant-design/icons'
import TextArea, { TextAreaRef } from 'antd/es/input/TextArea'
@ -19,9 +20,10 @@ import { isEmpty } from 'lodash'
import SendMessageSetting from './SendMessageSetting'
import { useSettings } from '@renderer/hooks/useSettings'
import dayjs from 'dayjs'
import { useAppSelector } from '@renderer/store'
import store, { useAppSelector } from '@renderer/store'
import { getDefaultTopic } from '@renderer/services/assistant'
import { useTranslation } from 'react-i18next'
import { setGenerating } from '@renderer/store/runtime'
interface Props {
assistant: Assistant
@ -86,6 +88,11 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
const clearTopic = () => EventEmitter.emit(EVENT_NAMES.CLEAR_MESSAGES)
const onPause = () => {
window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, true)
store.dispatch(setGenerating(false))
}
// Command or Ctrl + N create new topic
useEffect(() => {
const onKeydown = (e) => {
@ -148,6 +155,13 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
</Tooltip>
</ToolbarMenu>
<ToolbarMenu>
{generating && (
<Tooltip placement="top" title={t('assistant.input.pause')} arrow>
<ToolbarButton type="text" onClick={onPause}>
<PauseCircleOutlined />
</ToolbarButton>
</Tooltip>
)}
<SendMessageSetting>
<ToolbarButton type="text" style={{ marginRight: 0 }}>
<MoreOutlined />

View File

@ -12,6 +12,7 @@ import Logo from '@renderer/assets/images/logo.png'
import { SyncOutlined } from '@ant-design/icons'
import { firstLetter } from '@renderer/utils'
import { useTranslation } from 'react-i18next'
import { isEmpty } from 'lodash'
interface Props {
message: Message
@ -53,6 +54,13 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
setTimeout(() => EventEmitter.emit(EVENT_NAMES.REGENERATE_MESSAGE), 100)
}
const getMessageContent = (message: Message) => {
if (isEmpty(message.content) && message.status === 'paused') {
return t('message.chat.completion.paused')
}
return message.content
}
return (
<MessageContainer key={message.id}>
<AvatarWrapper>
@ -72,7 +80,7 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
)}
{message.status !== 'sending' && (
<Markdown className="markdown" components={{ code: CodeBlock as any }}>
{message.content}
{getMessageContent(message)}
</Markdown>
)}
{showMenu && (

View File

@ -27,6 +27,8 @@ const getOpenAiProvider = (provider: Provider) => {
}
export async function fetchChatCompletion({ messages, topic, assistant, onResponse }: FetchChatCompletionParams) {
window.keyv.set(EVENT_NAMES.CHAT_COMPLETION_PAUSED, false)
const provider = getAssistantProvider(assistant)
const openaiProvider = getOpenAiProvider(provider)
const defaultModel = getDefaultModel()
@ -34,7 +36,7 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
store.dispatch(setGenerating(true))
const _message: Message = {
const message: Message = {
id: uuid(),
role: 'assistant',
content: '',
@ -45,7 +47,7 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
status: 'sending'
}
onResponse({ ..._message })
onResponse({ ...message })
const systemMessage = assistant.prompt ? { role: 'system', content: assistant.prompt } : undefined
@ -54,12 +56,10 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
content: message.content
}))
const _messages = [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[]
try {
const stream = await openaiProvider.chat.completions.create({
model: model.id,
messages: _messages,
messages: [systemMessage, ...userMessages].filter(Boolean) as ChatCompletionMessageParam[],
stream: true
})
@ -67,22 +67,27 @@ export async function fetchChatCompletion({ messages, topic, assistant, onRespon
let usage: OpenAI.Completions.CompletionUsage | undefined = undefined
for await (const chunk of stream) {
if (window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)) {
break
}
content = content + (chunk.choices[0]?.delta?.content || '')
chunk.usage && (usage = chunk.usage)
onResponse({ ..._message, content, status: 'pending' })
onResponse({ ...message, content, status: 'pending' })
}
_message.content = content
_message.usage = usage
message.content = content
message.usage = usage
} catch (error: any) {
_message.content = `Error: ${error.message}`
message.content = `Error: ${error.message}`
}
_message.status = 'success'
EventEmitter.emit(EVENT_NAMES.AI_CHAT_COMPLETION, _message)
const paused = window.keyv.get(EVENT_NAMES.CHAT_COMPLETION_PAUSED)
message.status = paused ? 'paused' : 'success'
EventEmitter.emit(EVENT_NAMES.AI_CHAT_COMPLETION, message)
store.dispatch(setGenerating(false))
return _message
return message
}
interface FetchMessagesSummaryParams {

View File

@ -9,5 +9,6 @@ export const EVENT_NAMES = {
CLEAR_MESSAGES: 'CLEAR_MESSAGES',
ADD_ASSISTANT: 'ADD_ASSISTANT',
EDIT_MESSAGE: 'EDIT_MESSAGE',
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE'
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED'
}

View File

@ -16,7 +16,7 @@ export type Message = {
topicId: string
modelId?: string
createdAt: string
status: 'sending' | 'pending' | 'success' | 'error'
status: 'sending' | 'pending' | 'success' | 'paused' | 'error'
usage?: OpenAI.Completions.CompletionUsage
}