feat: add user edit modal & add prompt block
This commit is contained in:
parent
be0799a4c6
commit
1866b00265
@ -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;
|
||||
|
||||
108
src/renderer/src/components/Popups/UserPopup.tsx
Normal file
108
src/renderer/src/components/Popups/UserPopup.tsx
Normal 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'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
54
src/renderer/src/pages/home/Messages/Prompt.tsx
Normal file
54
src/renderer/src/pages/home/Messages/Prompt.tsx
Normal 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
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -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;
|
||||
`
|
||||
|
||||
|
||||
@ -23,8 +23,7 @@ const AntdProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: '#00b96b',
|
||||
borderRadius: 8
|
||||
colorPrimary: '#00b96b'
|
||||
}
|
||||
}}>
|
||||
{children}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user