feat: add user edit modal & add prompt block

This commit is contained in:
kangfenmao 2024-08-16 17:19:18 +08:00
parent be0799a4c6
commit 1866b00265
19 changed files with 203 additions and 53 deletions

View File

@ -31,7 +31,8 @@
--color-text: var(--color-text-1);
--color-icon: #ffffff99;
--color-icon-white: #ffffff;
--color-border: #ffffff20;
--color-border: #000;
--color-border-soft: #ffffff20;
--color-error: #f44336;
--color-link: #1677ff;
--color-code-background: #323232;
@ -80,6 +81,7 @@ body[theme-mode='light'] {
--color-icon: #00000099;
--color-icon-white: #000000;
--color-border: #00000028;
--color-border-soft: #00000028;
--color-error: #f44336;
--color-link: #1677ff;
--color-code-background: #e3e3e3;

View File

@ -0,0 +1,108 @@
import useAvatar from '@renderer/hooks/useAvatar'
import { useSettings } from '@renderer/hooks/useSettings'
import LocalStorage from '@renderer/services/storage'
import { useAppDispatch } from '@renderer/store'
import { setAvatar } from '@renderer/store/runtime'
import { setUserName } from '@renderer/store/settings'
import { compressImage } from '@renderer/utils'
import { Avatar, Input, Modal, Upload } from 'antd'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { Center, HStack } from '../Layout'
import { TopView } from '../TopView'
interface Props {
resolve: (data: any) => void
}
const PopupContainer: React.FC<Props> = ({ resolve }) => {
const [open, setOpen] = useState(true)
const { t } = useTranslation()
const { userName } = useSettings()
const dispatch = useAppDispatch()
const avatar = useAvatar()
const onOk = () => {
setOpen(false)
}
const onCancel = () => {
setOpen(false)
}
const onClose = () => {
resolve({})
}
return (
<Modal
width="300px"
open={open}
footer={null}
onOk={onOk}
onCancel={onCancel}
afterClose={onClose}
transitionName="ant-move-down">
<Center mt="30px">
<Upload
customRequest={() => {}}
accept="image/png, image/jpeg"
itemRender={() => null}
maxCount={1}
onChange={async ({ file }) => {
try {
const _file = file.originFileObj as File
const compressedFile = await compressImage(_file)
await LocalStorage.storeImage('avatar', compressedFile)
dispatch(setAvatar(await LocalStorage.getImage('avatar')))
} catch (error: any) {
window.message.error(error.message)
}
}}>
<UserAvatar src={avatar} />
</Upload>
</Center>
<HStack alignItems="center" gap="10px" p="20px">
<Input
placeholder={t('settings.general.user_name.placeholder')}
value={userName}
onChange={(e) => dispatch(setUserName(e.target.value))}
style={{ flex: 1, textAlign: 'center', width: '100%' }}
maxLength={30}
/>
</HStack>
</Modal>
)
}
const UserAvatar = styled(Avatar)`
cursor: pointer;
width: 80px;
height: 80px;
transition: opacity 0.3s ease;
&:hover {
opacity: 0.8;
}
`
export default class UserPopup {
static topviewId = 0
static hide() {
TopView.hide('UserPopup')
}
static show() {
return new Promise<any>((resolve) => {
TopView.show(
<PopupContainer
resolve={(v) => {
resolve(v)
this.hide()
}}
/>,
'UserPopup'
)
})
}
}

View File

@ -8,6 +8,8 @@ import { FC } from 'react'
import { Link, useLocation } from 'react-router-dom'
import styled from 'styled-components'
import UserPopup from '../Popups/UserPopup'
const Sidebar: FC = () => {
const { pathname } = useLocation()
const avatar = useAvatar()
@ -15,11 +17,13 @@ const Sidebar: FC = () => {
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
const onEditUser = () => {
UserPopup.show()
}
return (
<Container style={{ backgroundColor: minappShow ? 'var(--color-background)' : 'var(--sidebar-background)' }}>
<StyledLink to="/">
<AvatarImg src={avatar || Logo} draggable={false} />
</StyledLink>
<AvatarImg src={avatar || Logo} draggable={false} className="dragdisable" onClick={onEditUser} />
<MainMenus>
<Menus>
<StyledLink to="/">
@ -71,6 +75,7 @@ const AvatarImg = styled(Avatar)`
margin-bottom: ${isMac ? '12px' : '12px'};
margin-top: ${isMac ? '5px' : '2px'};
border: none;
cursor: pointer;
`
const MainMenus = styled.div`
display: flex;

View File

@ -56,13 +56,13 @@ const AppsPage: FC = () => {
<ContentContainer>
<AssistantsContainer>
<HStack alignItems="center" style={{ marginBottom: 16 }}>
<Title level={3}>{t('agents.my_agents')}</Title>
<Title level={4}>{t('agents.my_agents')}</Title>
{agents.length > 0 && <ManageIcon onClick={ManageAgentsPopup.show} />}
</HStack>
<UserAgents onAdd={onAddAgentConfirm} />
{Object.keys(agentGroups).map((group) => (
<div key={group}>
<Title level={3} key={group} style={{ marginBottom: 16 }}>
<Title level={4} key={group} style={{ marginBottom: 16 }}>
{group}
</Title>
<Row gutter={16}>

View File

@ -1,5 +1,5 @@
import { Agent } from '@renderer/types'
import { Col, Typography } from 'antd'
import { Col } from 'antd'
import styled from 'styled-components'
interface Props {
@ -7,17 +7,13 @@ interface Props {
onClick?: () => void
}
const { Title } = Typography
const AgentCard: React.FC<Props> = ({ agent, onClick }) => {
return (
<Container onClick={onClick}>
{agent.emoji && <EmojiHeader>{agent.emoji}</EmojiHeader>}
<Col>
<AgentHeader>
<AgentName level={5} style={{ marginBottom: 0 }}>
{agent.name}
</AgentName>
<AgentName style={{ marginBottom: 0 }}>{agent.name}</AgentName>
</AgentHeader>
<AgentCardPrompt>{agent.prompt}</AgentCardPrompt>
</Col>
@ -41,14 +37,14 @@ const Container = styled.div`
}
`
const EmojiHeader = styled.div`
width: 25px;
width: 20px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-right: 5px;
font-size: 25px;
line-height: 25px;
font-size: 20px;
line-height: 20px;
`
const AgentHeader = styled.div`
@ -58,15 +54,13 @@ const AgentHeader = styled.div`
align-items: center;
`
const AgentName = styled(Title)`
font-size: 18px;
const AgentName = styled.div`
line-height: 1.2;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
color: var(--color-white);
font-weight: 900;
color: var(--color-text-1);
`
const AgentCardPrompt = styled.div`
@ -76,6 +70,7 @@ const AgentCardPrompt = styled.div`
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
font-size: 12px;
`
export default AgentCard

View File

@ -41,10 +41,10 @@ const AssistantCardContainer = styled.div`
align-items: center;
justify-content: center;
padding: 20px;
border: 1px dashed var(--color-border);
border: 1px dashed var(--color-border-soft);
border-radius: 10px;
cursor: pointer;
min-height: 84px;
min-height: 72px;
.anticon {
font-size: 16px;
color: var(--color-icon);

View File

@ -145,7 +145,7 @@ const AssistantItem = styled.div`
flex-direction: column;
padding: 7px 10px;
position: relative;
border-radius: 8px;
border-radius: 4px;
cursor: pointer;
font-family: Ubuntu;
.anticon {

View File

@ -6,7 +6,7 @@ import { FC } from 'react'
import styled from 'styled-components'
import Inputbar from './Inputbar/Inputbar'
import Messages from './Messages'
import Messages from './Messages/Messages'
import RightSidebar from './RightSidebar'
interface Props {

View File

@ -9,9 +9,9 @@ import { Switch } from 'antd'
import { FC, useState } from 'react'
import styled from 'styled-components'
import AddAssistantPopup from '../../components/Popups/AddAssistantPopup'
import Assistants from './Assistants'
import Chat from './Chat'
import AddAssistantPopup from './components/AddAssistantPopup'
import Navigation from './Header'
let _activeAssistant: Assistant

View File

@ -240,7 +240,7 @@ const Container = styled.div`
display: flex;
flex-direction: column;
height: var(--input-bar-height);
border: 1px solid var(--color-border);
border: 1px solid var(--color-border-soft);
transition: all 0.3s ease;
position: relative;
margin: 0 20px 15px 20px;

View File

@ -23,8 +23,8 @@ import { FC, memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import SelectModelDropdown from './components/SelectModelDropdown'
import Markdown from './Markdown/Markdown'
import SelectModelDropdown from '../components/SelectModelDropdown'
import Markdown from '../Markdown/Markdown'
interface Props {
message: Message
@ -208,7 +208,7 @@ const MessageContainer = styled.div`
&.user {
position: absolute;
top: 10px;
right: 10px;
right: 15px;
}
}
&:hover {

View File

@ -10,11 +10,12 @@ import { getBriefInfo, runAsyncFunction, uuid } from '@renderer/utils'
import { t } from 'i18next'
import localforage from 'localforage'
import { last, reverse } from 'lodash'
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { FC, useCallback, useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
import Suggestions from './components/Suggestions'
import Suggestions from '../components/Suggestions'
import MessageItem from './Message'
import Prompt from './Prompt'
interface Props {
assistant: Assistant
@ -28,19 +29,6 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
const containerRef = useRef<HTMLDivElement>(null)
const { updateTopic } = useAssistant(assistant.id)
const assistantDefaultMessage: Message = useMemo(
() => ({
id: 'assistant',
role: 'assistant',
content: assistant.description || assistant.prompt || t('chat.default.description'),
assistantId: assistant.id,
topicId: topic.id,
status: 'pending',
createdAt: new Date().toISOString()
}),
[assistant.description, assistant.id, assistant.prompt, topic.id]
)
const onSendMessage = useCallback(
(message: Message) => {
const _messages = [...messages, message]
@ -123,7 +111,7 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
{reverse([...messages]).map((message, index) => (
<MessageItem key={message.id} message={message} showMenu index={index} onDeleteMessage={onDeleteMessage} />
))}
<MessageItem message={assistantDefaultMessage} />
<Prompt assistant={assistant} key={assistant.prompt} />
</Container>
)
}

View File

@ -0,0 +1,54 @@
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { syncAsistantToAgent } from '@renderer/services/assistant'
import { Assistant } from '@renderer/types'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
interface Props {
assistant: Assistant
}
const Prompt: FC<Props> = ({ assistant }) => {
const { t } = useTranslation()
const { updateAssistant } = useAssistant(assistant.id)
const prompt = assistant.prompt || t('chat.default.description')
const onEdit = async () => {
const _assistant = await AssistantSettingPopup.show({ assistant })
updateAssistant(_assistant)
syncAsistantToAgent(_assistant)
}
if (!prompt) {
return null
}
return (
<Container onClick={onEdit}>
<Text>{prompt}</Text>
</Container>
)
}
const Container = styled.div`
padding: 10px 20px;
background-color: var(--color-background-soft);
margin-bottom: 20px;
margin: 0 20px 20px 20px;
border-radius: 6px;
cursor: pointer;
`
const Text = styled.div`
color: var(--color-text-3);
font-size: 12px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
`
export default Prompt

View File

@ -142,12 +142,11 @@ const Container = styled.div`
const TopicListItem = styled.div`
padding: 7px 10px;
cursor: pointer;
border-radius: 8px;
border-radius: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: Ubuntu;
transition: all 0.3s;
&:hover {
background-color: var(--color-background-soft);
}

View File

@ -95,7 +95,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
</SettingTitle>
<Divider style={{ width: '100%', margin: '10px 0' }} />
<SettingSubtitle style={{ marginTop: 5 }}>{t('settings.provider.api_key')}</SettingSubtitle>
<Space.Compact style={{ width: '100%' }}>
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
<Input.Password
value={apiKey}
placeholder={t('settings.provider.api_key')}
@ -117,7 +117,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
</HelpTextRow>
)}
<SettingSubtitle>{t('settings.provider.api_host')}</SettingSubtitle>
<Space.Compact style={{ width: '100%' }}>
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
<Input
value={apiHost}
placeholder={t('settings.provider.api_host')}
@ -128,7 +128,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
{apiEditable && <Button onClick={onReset}>{t('settings.provider.api.url.reset')}</Button>}
</Space.Compact>
{provider.id === 'ollama' && <OllamSettings />}
<SettingSubtitle>{t('common.models')}</SettingSubtitle>
<SettingSubtitle style={{ marginBottom: 5 }}>{t('common.models')}</SettingSubtitle>
{Object.keys(modelGroups).map((group) => (
<Card key={group} type="inner" title={group} style={{ marginBottom: '10px' }} size="small">
{modelGroups[group].map((model) => (

View File

@ -257,7 +257,7 @@ const InputContainer = styled.div`
flex: 1;
flex-direction: column;
height: 100%;
border: 1px solid var(--color-border);
border: 1px solid var(--color-border-soft);
border-radius: 10px;
`

View File

@ -23,8 +23,7 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
}
},
token: {
colorPrimary: '#00b96b',
borderRadius: 8
colorPrimary: '#00b96b'
}
}}>
{children}

View File

@ -123,7 +123,7 @@ export async function fetchMessagesSummary({ messages, assistant }: { messages:
const providerSdk = new ProviderSDK(provider)
try {
return await providerSdk.summaries(messages, assistant)
return await providerSdk.summaries(filterMessages(messages), assistant)
} catch (error: any) {
return null
}