From e8bdf9d5fd9f9b2ebda36591106c2063f4dba8b6 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 15 Jul 2024 13:14:15 +0800 Subject: [PATCH] feat: add pause icon to pause chat completion --- src/renderer/src/i18n/index.ts | 12 +++++--- .../src/pages/home/components/Inputbar.tsx | 16 +++++++++- .../src/pages/home/components/Message.tsx | 10 ++++++- src/renderer/src/services/api.ts | 29 +++++++++++-------- src/renderer/src/services/event.ts | 3 +- src/renderer/src/types/index.ts | 2 +- 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts index fdad73fddb..71fe797e76 100644 --- a/src/renderer/src/i18n/index.ts +++ b/src/renderer/src/i18n/index.ts @@ -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: '智能体' diff --git a/src/renderer/src/pages/home/components/Inputbar.tsx b/src/renderer/src/pages/home/components/Inputbar.tsx index e03214eb13..1922bdef24 100644 --- a/src/renderer/src/pages/home/components/Inputbar.tsx +++ b/src/renderer/src/pages/home/components/Inputbar.tsx @@ -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 = ({ 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 = ({ assistant, setActiveTopic }) => { + {generating && ( + + + + + + )} diff --git a/src/renderer/src/pages/home/components/Message.tsx b/src/renderer/src/pages/home/components/Message.tsx index 2e071d4454..47dbd6fcfb 100644 --- a/src/renderer/src/pages/home/components/Message.tsx +++ b/src/renderer/src/pages/home/components/Message.tsx @@ -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 = ({ 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 ( @@ -72,7 +80,7 @@ const MessageItem: FC = ({ message, index, showMenu, onDeleteMessage }) = )} {message.status !== 'sending' && ( - {message.content} + {getMessageContent(message)} )} {showMenu && ( diff --git a/src/renderer/src/services/api.ts b/src/renderer/src/services/api.ts index c0c287a1b3..47aa57b6a2 100644 --- a/src/renderer/src/services/api.ts +++ b/src/renderer/src/services/api.ts @@ -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 { diff --git a/src/renderer/src/services/event.ts b/src/renderer/src/services/event.ts index 34aba9aa66..9750439f62 100644 --- a/src/renderer/src/services/event.ts +++ b/src/renderer/src/services/event.ts @@ -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' } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index b38cbb93c9..36f7aac6a0 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -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 }