feat: @model regenerate message

This commit is contained in:
kangfenmao 2024-08-05 11:45:14 +08:00
parent de41199f7e
commit b0a3d705ff
8 changed files with 64 additions and 20 deletions

View File

@ -144,9 +144,11 @@
height: auto;
}
a {
a,
.link {
color: var(--color-link);
text-decoration: none;
cursor: pointer;
&:hover {
text-decoration: underline;

View File

@ -59,10 +59,9 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
const onRegenerate = useCallback(
(model: Model) => {
setModel(model)
onDeleteMessage?.(message)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.REGENERATE_MESSAGE), 100)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.REGENERATE_MESSAGE, model), 100)
},
[message, onDeleteMessage, setModel]
[setModel]
)
const getUserName = useCallback(() => {
@ -137,6 +136,15 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
{copied && <CheckOutlined style={{ color: 'var(--color-primary)' }} />}
</ActionButton>
</Tooltip>
{canRegenerate && (
<SelectModelDropdown model={model} onSelect={onRegenerate} placement="topRight">
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
<ActionButton>
<SyncOutlined />
</ActionButton>
</Tooltip>
</SelectModelDropdown>
)}
<Popconfirm
title={t('message.message.delete.content')}
okButtonProps={{ danger: true }}
@ -148,15 +156,6 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
</ActionButton>
</Tooltip>
</Popconfirm>
{canRegenerate && (
<SelectModelDropdown model={model} onSelect={onRegenerate} placement="topRight">
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
<ActionButton>
<SyncOutlined />
</ActionButton>
</Tooltip>
</SelectModelDropdown>
)}
{!isUserMessage && (
<Dropdown menu={{ items: dropdownItems }} trigger={['click']} placement="topRight" arrow>
<ActionButton>

View File

@ -2,12 +2,13 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
import { useProviderByAssistant } from '@renderer/hooks/useProvider'
import { fetchChatCompletion, fetchMessagesSummary } from '@renderer/services/api'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { filterAtMessages } from '@renderer/services/message'
import LocalStorage from '@renderer/services/storage'
import { Assistant, Message, Topic } from '@renderer/types'
import { estimateHistoryTokenCount, runAsyncFunction } from '@renderer/utils'
import { Assistant, Message, Model, Topic } from '@renderer/types'
import { estimateHistoryTokenCount, getBriefInfo, runAsyncFunction, uuid } from '@renderer/utils'
import { t } from 'i18next'
import localforage from 'localforage'
import { debounce, reverse } from 'lodash'
import { debounce, last, reverse } from 'lodash'
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import styled from 'styled-components'
@ -75,8 +76,18 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
onSendMessage(msg)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.AI_AUTO_RENAME), 100)
}),
EventEmitter.on(EVENT_NAMES.REGENERATE_MESSAGE, async () => {
fetchChatCompletion({ assistant, messages: messages, topic, onResponse: setLastMessage })
EventEmitter.on(EVENT_NAMES.REGENERATE_MESSAGE, async (model: Model) => {
const lastUserMessage = last(filterAtMessages(messages).filter((m) => m.role === 'user'))
if (lastUserMessage) {
const content = `[@${model.name}](#) ${getBriefInfo(lastUserMessage.content)}`
onSendMessage({ ...lastUserMessage, id: uuid(), type: '@', content })
fetchChatCompletion({
assistant,
topic,
messages: [...messages, lastUserMessage],
onResponse: setLastMessage
})
}
}),
EventEmitter.on(EVENT_NAMES.AI_AUTO_RENAME, autoRenameTopic),
EventEmitter.on(EVENT_NAMES.CLEAR_MESSAGES, () => {

View File

@ -1,7 +1,11 @@
import { omit } from 'lodash'
import React from 'react'
const Link: React.FC = (props) => {
const Link: React.FC = (props: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
if (props.href?.startsWith('#')) {
return <span className="link">{props.children}</span>
}
return <a {...omit(props, 'node')} target="_blank" rel="noreferrer" onClick={(e) => e.stopPropagation()} />
}

View File

@ -14,6 +14,7 @@ import {
getTranslateModel
} from './assistant'
import { EVENT_NAMES, EventEmitter } from './event'
import { filterAtMessages } from './message'
import ProviderSDK from './ProviderSDK'
export async function fetchChatCompletion({
@ -50,7 +51,7 @@ export async function fetchChatCompletion({
onResponse({ ...message })
try {
await providerSdk.completions(messages, assistant, ({ text, usage }) => {
await providerSdk.completions(filterAtMessages(messages), assistant, ({ text, usage }) => {
message.content = message.content + text || ''
message.usage = usage
onResponse({ ...message, status: 'pending' })

View File

@ -0,0 +1,5 @@
import { Message } from '@renderer/types'
export const filterAtMessages = (messages: Message[]) => {
return messages.filter((message) => message.type !== '@')
}

View File

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

View File

@ -230,3 +230,24 @@ export function convertMathFormula(input) {
// 使用正则表达式匹配并替换公式格式
return input.replaceAll(/\\\[/g, '$$$$').replaceAll(/\\\]/g, '$$$$')
}
export function getBriefInfo(text: string, maxLength: number = 50): string {
// 去除空行
const noEmptyLinesText = text.replace(/\n\s*\n/g, '\n')
// 检查文本是否超过最大长度
if (noEmptyLinesText.length <= maxLength) {
return noEmptyLinesText
}
// 找到最近的单词边界
let truncatedText = noEmptyLinesText.slice(0, maxLength)
const lastSpaceIndex = truncatedText.lastIndexOf(' ')
if (lastSpaceIndex !== -1) {
truncatedText = truncatedText.slice(0, lastSpaceIndex)
}
// 截取前面的内容,并在末尾添加 "..."
return truncatedText + '...'
}