feat: Added translations, new column, and UI improvements.

- Added translations for a new field.
- Added new column for file count in the FilesPage view.
- Improved handling of message tokens in the UI.
- Added functionality to display message tokens for messages with specific roles.
- Added window style selection and styling adjustments to the General Settings page.
- Added support for vision models in OpenAIProvider.
This commit is contained in:
kangfenmao 2024-09-19 16:56:44 +08:00
parent f1c8922752
commit 33ac0937df
6 changed files with 77 additions and 55 deletions

View File

@ -113,6 +113,7 @@ const resources = {
file: 'File', file: 'File',
name: 'Name', name: 'Name',
size: 'Size', size: 'Size',
count: 'Count',
created_at: 'Created At' created_at: 'Created At'
}, },
agents: { agents: {
@ -385,6 +386,7 @@ const resources = {
file: '文件', file: '文件',
name: '文件名', name: '文件名',
size: '大小', size: '大小',
count: '文件数',
created_at: '创建时间' created_at: '创建时间'
}, },
agents: { agents: {

View File

@ -22,6 +22,7 @@ const FilesPage: FC = () => {
file: isImage ? ImageView : <FileNameText className="text-nowrap">{file.origin_name}</FileNameText>, file: isImage ? ImageView : <FileNameText className="text-nowrap">{file.origin_name}</FileNameText>,
name: <a href={'file://' + getFileDirectory(file.path)}>{file.origin_name}</a>, name: <a href={'file://' + getFileDirectory(file.path)}>{file.origin_name}</a>,
size: `${(file.size / 1024 / 1024).toFixed(2)} MB`, size: `${(file.size / 1024 / 1024).toFixed(2)} MB`,
count: file.count,
created_at: dayjs(file.created_at).format('MM-DD HH:mm') created_at: dayjs(file.created_at).format('MM-DD HH:mm')
} }
}) })
@ -44,6 +45,12 @@ const FilesPage: FC = () => {
key: 'size', key: 'size',
width: '100px' width: '100px'
}, },
{
title: t('files.count'),
dataIndex: 'count',
key: 'count',
width: '100px'
},
{ {
title: t('files.created_at'), title: t('files.created_at'),
dataIndex: 'created_at', dataIndex: 'created_at',

View File

@ -17,7 +17,6 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
import useAvatar from '@renderer/hooks/useAvatar' import useAvatar from '@renderer/hooks/useAvatar'
import { useModel } from '@renderer/hooks/useModel' import { useModel } from '@renderer/hooks/useModel'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { useRuntime } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { Message, Model } from '@renderer/types' import { Message, Model } from '@renderer/types'
import { firstLetter, removeLeadingEmoji, removeTrailingDoubleSpaces } from '@renderer/utils' import { firstLetter, removeLeadingEmoji, removeTrailingDoubleSpaces } from '@renderer/utils'
@ -31,6 +30,7 @@ import styled from 'styled-components'
import SelectModelDropdown from '../components/SelectModelDropdown' import SelectModelDropdown from '../components/SelectModelDropdown'
import Markdown from '../Markdown/Markdown' import Markdown from '../Markdown/Markdown'
import MessageAttachments from './MessageAttachments' import MessageAttachments from './MessageAttachments'
import MessgeTokens from './MessageTokens'
interface Props { interface Props {
message: Message message: Message
@ -152,9 +152,10 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
</MessageHeader> </MessageHeader>
<MessageContentContainer style={{ fontFamily, fontSize }}> <MessageContentContainer style={{ fontFamily, fontSize }}>
<MessageContent message={message} /> <MessageContent message={message} />
<MessageFooter style={{ border: messageBorder }}> <MessageFooter style={{ border: messageBorder, flexDirection: isLastMessage ? 'row-reverse' : undefined }}>
<MessgeTokens message={message} />
{showMenu && ( {showMenu && (
<MenusBar className={`menubar ${isLastMessage && 'show'} ${(!isLastMessage || isUserMessage) && 'user'}`}> <MenusBar className={`menubar ${isLastMessage && 'show'}`}>
{message.role === 'user' && ( {message.role === 'user' && (
<Tooltip title="Edit" mouseEnterDelay={0.8}> <Tooltip title="Edit" mouseEnterDelay={0.8}>
<ActionButton onClick={onEdit}> <ActionButton onClick={onEdit}>
@ -204,39 +205,12 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
)} )}
</MenusBar> </MenusBar>
)} )}
<MessgeTokens message={message} />
</MessageFooter> </MessageFooter>
</MessageContentContainer> </MessageContentContainer>
</MessageContainer> </MessageContainer>
) )
} }
const MessgeTokens: React.FC<{ message: Message }> = ({ message }) => {
const { generating } = useRuntime()
if (!message.usage) {
return null
}
if (message.role === 'user') {
return <MessageMetadata>Tokens: {message?.usage?.total_tokens}</MessageMetadata>
}
if (generating) {
return null
}
if (message.role === 'assistant') {
return (
<MessageMetadata>
Tokens: {message?.usage?.total_tokens} | {message?.usage?.prompt_tokens} | {message?.usage?.completion_tokens}
</MessageMetadata>
)
}
return null
}
const MessageContent: React.FC<{ message: Message }> = ({ message }) => { const MessageContent: React.FC<{ message: Message }> = ({ message }) => {
const { t } = useTranslation() const { t } = useTranslation()
@ -358,13 +332,6 @@ const MenusBar = styled.div`
margin-left: -5px; margin-left: -5px;
` `
const MessageMetadata = styled.div`
font-size: 12px;
color: var(--color-text-2);
user-select: text;
margin: 2px 0;
`
const ActionButton = styled.div` const ActionButton = styled.div`
cursor: pointer; cursor: pointer;
border-radius: 8px; border-radius: 8px;

View File

@ -0,0 +1,38 @@
import { useRuntime } from '@renderer/hooks/useStore'
import { Message } from '@renderer/types'
import styled from 'styled-components'
const MessgeTokens: React.FC<{ message: Message }> = ({ message }) => {
const { generating } = useRuntime()
if (!message.usage) {
return null
}
if (message.role === 'user') {
return <MessageMetadata>Tokens: {message?.usage?.total_tokens}</MessageMetadata>
}
if (generating) {
return null
}
if (message.role === 'assistant') {
return (
<MessageMetadata>
Tokens: {message?.usage?.total_tokens} | {message?.usage?.prompt_tokens} | {message?.usage?.completion_tokens}
</MessageMetadata>
)
}
return null
}
const MessageMetadata = styled.div`
font-size: 12px;
color: var(--color-text-2);
user-select: text;
margin: 2px 0;
`
export default MessgeTokens

View File

@ -77,8 +77,9 @@ const GeneralSettings: FC = () => {
]} ]}
/> />
</SettingRow> </SettingRow>
<SettingDivider />
{isMac && ( {isMac && (
<>
<SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.theme.window.style.title')}</SettingRowTitle> <SettingRowTitle>{t('settings.theme.window.style.title')}</SettingRowTitle>
<Select <Select
@ -91,6 +92,7 @@ const GeneralSettings: FC = () => {
]} ]}
/> />
</SettingRow> </SettingRow>
</>
)} )}
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>
@ -108,7 +110,7 @@ const GeneralSettings: FC = () => {
<SettingDivider /> <SettingDivider />
{topicPosition === 'left' && ( {topicPosition === 'left' && (
<> <>
<SettingRow> <SettingRow style={{ minHeight: 32 }}>
<SettingRowTitle>{t('settings.advanced.click_assistant_switch_to_topics')}</SettingRowTitle> <SettingRowTitle>{t('settings.advanced.click_assistant_switch_to_topics')}</SettingRowTitle>
<Switch <Switch
checked={clickAssistantToShowTopic} checked={clickAssistantToShowTopic}

View File

@ -1,8 +1,9 @@
import { isLocalAi } from '@renderer/config/env' import { isLocalAi } from '@renderer/config/env'
import { isVisionModel } from '@renderer/config/models'
import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/assistant' import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/assistant'
import { EVENT_NAMES } from '@renderer/services/event' import { EVENT_NAMES } from '@renderer/services/event'
import { filterContextMessages, filterMessages } from '@renderer/services/messages' import { filterContextMessages, filterMessages } from '@renderer/services/messages'
import { Assistant, FileTypes, Message, Provider, Suggestion } from '@renderer/types' import { Assistant, FileTypes, Message, Model, Provider, Suggestion } from '@renderer/types'
import { removeQuotes } from '@renderer/utils' import { removeQuotes } from '@renderer/utils'
import { first, takeRight } from 'lodash' import { first, takeRight } from 'lodash'
import OpenAI from 'openai' import OpenAI from 'openai'
@ -33,7 +34,12 @@ export default class OpenAIProvider extends BaseProvider {
return true return true
} }
private async getMessageParam(message: Message): Promise<OpenAI.Chat.Completions.ChatCompletionMessageParam> { private async getMessageParam(
message: Message,
model: Model
): Promise<OpenAI.Chat.Completions.ChatCompletionMessageParam> {
const isVision = isVisionModel(model)
if (message.role !== 'user') { if (message.role !== 'user') {
return { return {
role: message.role, role: message.role,
@ -49,7 +55,7 @@ export default class OpenAIProvider extends BaseProvider {
] ]
for (const file of message.files || []) { for (const file of message.files || []) {
if (file.type === FileTypes.IMAGE) { if (file.type === FileTypes.IMAGE && isVision) {
const image = await window.api.file.base64Image(file.id + file.ext) const image = await window.api.file.base64Image(file.id + file.ext)
parts.push({ parts.push({
type: 'image_url', type: 'image_url',
@ -83,7 +89,7 @@ export default class OpenAIProvider extends BaseProvider {
onFilterMessages(_messages) onFilterMessages(_messages)
for (const message of _messages) { for (const message of _messages) {
userMessages.push(await this.getMessageParam(message)) userMessages.push(await this.getMessageParam(message, model))
} }
// @ts-ignore key is not typed // @ts-ignore key is not typed