feat: add custom agent #14
This commit is contained in:
parent
a39beb3841
commit
167988927b
@ -55,6 +55,7 @@
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-vite": "^2.0.0",
|
||||
"emittery": "^1.0.3",
|
||||
"emoji-picker-element": "^1.22.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-react": "^7.34.3",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
|
||||
@ -5,7 +5,7 @@ import { PersistGate } from 'redux-persist/integration/react'
|
||||
|
||||
import Sidebar from './components/app/Sidebar'
|
||||
import TopViewContainer from './components/TopView'
|
||||
import AppsPage from './pages/apps/AppsPage'
|
||||
import AgentsPage from './pages/agents/AgentsPage'
|
||||
import HomePage from './pages/home/HomePage'
|
||||
import SettingsPage from './pages/settings/SettingsPage'
|
||||
import TranslatePage from './pages/translate/TranslatePage'
|
||||
@ -23,7 +23,7 @@ function App(): JSX.Element {
|
||||
<Sidebar />
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/apps" element={<AppsPage />} />
|
||||
<Route path="/apps" element={<AgentsPage />} />
|
||||
<Route path="/translate" element={<TranslatePage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
</Routes>
|
||||
|
||||
@ -230,3 +230,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emoji-picker {
|
||||
--border-size: 0;
|
||||
}
|
||||
|
||||
49
src/renderer/src/components/DragableList/index.tsx
Normal file
49
src/renderer/src/components/DragableList/index.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
|
||||
import { droppableReorder } from '@renderer/utils'
|
||||
import { FC } from 'react'
|
||||
|
||||
interface Props<T> {
|
||||
list: T[]
|
||||
children: (item: T, index: number) => React.ReactNode
|
||||
onUpdate: (list: T[]) => void
|
||||
onDragStart?: () => void
|
||||
onDragEnd?: () => void
|
||||
}
|
||||
|
||||
const DragableList: FC<Props<any>> = ({ children, list, onDragStart, onUpdate, onDragEnd }) => {
|
||||
const _onDragEnd = (result: DropResult) => {
|
||||
onDragEnd?.()
|
||||
if (result.destination) {
|
||||
const sourceIndex = result.source.index
|
||||
const destIndex = result.destination.index
|
||||
const reorderAgents = droppableReorder(list, sourceIndex, destIndex)
|
||||
onUpdate(reorderAgents)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DragDropContext onDragStart={onDragStart} onDragEnd={_onDragEnd}>
|
||||
<Droppable droppableId="droppable">
|
||||
{(provided) => (
|
||||
<div {...provided.droppableProps} ref={provided.innerRef}>
|
||||
{list.map((item, index) => (
|
||||
<Draggable key={`draggable_${item.id}_${index}`} draggableId={item.id} index={index}>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={{ ...provided.draggableProps.style, marginBottom: 8 }}>
|
||||
{children(item, index)}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default DragableList
|
||||
25
src/renderer/src/components/EmojiPicker/index.tsx
Normal file
25
src/renderer/src/components/EmojiPicker/index.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { useTheme } from '@renderer/providers/ThemeProvider'
|
||||
import { FC, useEffect, useRef } from 'react'
|
||||
|
||||
interface Props {
|
||||
onEmojiClick: (emoji: string) => void
|
||||
}
|
||||
|
||||
const EmojiPicker: FC<Props> = ({ onEmojiClick }) => {
|
||||
const { theme } = useTheme()
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
ref.current.addEventListener('emoji-click', (event: any) => {
|
||||
event.stopPropagation()
|
||||
onEmojiClick(event.detail.emoji.unicode)
|
||||
})
|
||||
}
|
||||
}, [onEmojiClick])
|
||||
|
||||
// @ts-ignore next-line
|
||||
return <emoji-picker ref={ref} class={theme === 'dark' ? 'dark' : 'light'} style={{ border: 'none' }} />
|
||||
}
|
||||
|
||||
export default EmojiPicker
|
||||
@ -57,18 +57,19 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ assistant, resolve })
|
||||
export default class AssistantSettingPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide(this.topviewId)
|
||||
TopView.hide('AssistantSettingPopup')
|
||||
}
|
||||
static show(props: AssistantSettingPopupShowParams) {
|
||||
return new Promise<Assistant>((resolve) => {
|
||||
this.topviewId = TopView.show(
|
||||
TopView.show(
|
||||
<AssistantSettingPopupContainer
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
}}
|
||||
/>
|
||||
/>,
|
||||
'AssistantSettingPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -58,18 +58,19 @@ const PromptPopupContainer: React.FC<Props> = ({
|
||||
export default class PromptPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide(this.topviewId)
|
||||
TopView.hide('PromptPopup')
|
||||
}
|
||||
static show(props: PromptPopupShowParams) {
|
||||
return new Promise<string>((resolve) => {
|
||||
this.topviewId = TopView.show(
|
||||
TopView.show(
|
||||
<PromptPopupContainer
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
}}
|
||||
/>
|
||||
/>,
|
||||
'PromptPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -37,18 +37,19 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
|
||||
export default class TemplatePopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide(this.topviewId)
|
||||
TopView.hide('TemplatePopup')
|
||||
}
|
||||
static show(props: ShowParams) {
|
||||
return new Promise<any>((resolve) => {
|
||||
this.topviewId = TopView.show(
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
}}
|
||||
/>
|
||||
/>,
|
||||
'TemplatePopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,87 +1,101 @@
|
||||
import { useAppInit } from '@renderer/hooks/useAppInit'
|
||||
import { message, Modal } from 'antd'
|
||||
import { findIndex, pullAt } from 'lodash'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { PropsWithChildren, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { Box } from '../Layout'
|
||||
|
||||
let id = 0
|
||||
let onPop = () => {}
|
||||
|
||||
let onShow = ({ element, key }: { element: React.FC | React.ReactNode; key: number }) => {
|
||||
let onShow = ({ element, id }: { element: React.FC | React.ReactNode; id: string }) => {
|
||||
element
|
||||
key
|
||||
id
|
||||
}
|
||||
|
||||
let onHide = ({ key }: { key: number }) => {
|
||||
key
|
||||
let onHide = (id: string) => {
|
||||
id
|
||||
}
|
||||
let onHideAll = () => {}
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
type ElementItem = {
|
||||
key: number
|
||||
id: string
|
||||
element: React.FC | React.ReactNode
|
||||
}
|
||||
|
||||
const TopViewContainer: React.FC<Props> = ({ children }) => {
|
||||
const [elements, setElements] = useState<ElementItem[]>([])
|
||||
const elementsRef = useRef<ElementItem[]>([])
|
||||
elementsRef.current = elements
|
||||
|
||||
const [messageApi, messageContextHolder] = message.useMessage()
|
||||
const [modal, modalContextHolder] = Modal.useModal()
|
||||
|
||||
useAppInit()
|
||||
|
||||
onPop = () => {
|
||||
const views = [...elements]
|
||||
views.pop()
|
||||
setElements(views)
|
||||
}
|
||||
|
||||
onShow = ({ element, key }: { element: React.FC | React.ReactNode; key: number }) => {
|
||||
setElements(elements.concat([{ element, key }]))
|
||||
}
|
||||
|
||||
onHide = ({ key }: { key: number }) => {
|
||||
const views = [...elements]
|
||||
pullAt(views, findIndex(views, { key }))
|
||||
setElements(views)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.message = messageApi
|
||||
window.modal = modal
|
||||
}, [messageApi, modal])
|
||||
|
||||
onPop = () => {
|
||||
console.debug('[TopView] onPop')
|
||||
const views = [...elementsRef.current]
|
||||
views.pop()
|
||||
elementsRef.current = views
|
||||
setElements(elementsRef.current)
|
||||
}
|
||||
|
||||
onShow = ({ element, id }: ElementItem) => {
|
||||
console.debug('[TopView] onShow', id)
|
||||
|
||||
if (!elementsRef.current.find((el) => el.id === id)) {
|
||||
elementsRef.current = elementsRef.current.concat([{ element, id }])
|
||||
setElements(elementsRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
onHide = (id: string) => {
|
||||
console.debug('[TopView] onHide', id, elementsRef.current)
|
||||
elementsRef.current = elementsRef.current.filter((el) => el.id !== id)
|
||||
setElements(elementsRef.current)
|
||||
}
|
||||
|
||||
onHideAll = () => {
|
||||
console.debug('[TopView] onHideAll')
|
||||
setElements([])
|
||||
elementsRef.current = []
|
||||
}
|
||||
|
||||
const FullScreenContainer: React.FC<PropsWithChildren> = useCallback(({ children }) => {
|
||||
return (
|
||||
<Box flex={1} position="absolute" w="100%" h="100%">
|
||||
<Box position="absolute" w="100%" h="100%" onClick={onPop} />
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}, [])
|
||||
|
||||
console.debug(
|
||||
'[TopView]',
|
||||
elements.map((el) => [el.id, el.element])
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
{messageContextHolder}
|
||||
{modalContextHolder}
|
||||
{elements.length > 0 && (
|
||||
<div style={{ display: 'flex', flex: 1, position: 'absolute', width: '100%', height: '100%' }}>
|
||||
<div style={{ position: 'absolute', width: '100%', height: '100%' }} onClick={onPop} />
|
||||
{elements.map(({ element: Element, key }) =>
|
||||
typeof Element === 'function' ? (
|
||||
<Element key={`TOPVIEW_${key}`} />
|
||||
) : (
|
||||
<div key={`TOPVIEW_${key}`}>{Element}</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{elements.map(({ element: Element, id }) => (
|
||||
<FullScreenContainer key={`TOPVIEW_${id}`}>
|
||||
{typeof Element === 'function' ? <Element /> : Element}
|
||||
</FullScreenContainer>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const TopView = {
|
||||
show: (element: React.FC | React.ReactNode) => {
|
||||
id = id + 1
|
||||
onShow({ element, key: id })
|
||||
return id
|
||||
},
|
||||
hide: (key: number) => {
|
||||
onHide({ key })
|
||||
},
|
||||
show: (element: React.FC | React.ReactNode, id: string) => onShow({ element, id }),
|
||||
hide: (id: string) => onHide(id),
|
||||
clear: () => onHideAll(),
|
||||
pop: onPop
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "🎯 产品经理 - Product Manager",
|
||||
"name": "产品经理 - Product Manager",
|
||||
"emoji": "🎯",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名经验丰富的产品经理,你具有深厚的技术背景,并且对市场和用户需求有敏锐的洞察力。你擅长解决复杂的问题,制定有效的产品策略,并优秀地平衡各种资源以实现产品目标。你具有卓越的项目管理能力和出色的沟通技巧,能够有效地协调团队内部和外部的资源。请在这个角色下为我解答以下问题。",
|
||||
@ -9,15 +9,15 @@
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "🎯 策略产品经理 - Strategy Product Manager",
|
||||
"emoji": "🎯",
|
||||
"name": "策略产品经理 - Strategy Product Manager",
|
||||
"emoji": "🎯 ",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名策略产品经理,你擅长进行市场研究和竞品分析,以制定产品策略。你能把握行业趋势,了解用户需求,并在此基础上优化产品功能和用户体验。请在这个角色下为我解答以下问题。",
|
||||
"description": "你现在是一名策略产品经理,你擅长进行市场研究和竞品分析,以制定产品策略。你能把握行业趋势,了解用户需求,并在此基础上优化产品功能和用户体验。请在这个角色下为我解答以下问题。"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "👥 社群运营 - Community Operations",
|
||||
"name": "社群运营 - Community Operations",
|
||||
"emoji": "👥",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名社群运营专家,你擅长激发社群活力,增强用户的参与度和忠诚度。你了解如何管理和引导社群文化,以及如何解决社群内的问题和冲突。请在这个角色下为我解答以下问题。",
|
||||
@ -25,7 +25,7 @@
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "✍️ 内容运营 - Content Operations",
|
||||
"name": "内容运营 - Content Operations",
|
||||
"emoji": "✍️",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名专业的内容运营人员,你精通内容创作、编辑、发布和优化。你对读者需求有敏锐的感知,擅长通过高质量的内容吸引和保留用户。请在这个角色下为我解答以下问题。",
|
||||
@ -33,7 +33,7 @@
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "🛍️ 商家运营 - Merchant Operations",
|
||||
"name": "商家运营 - Merchant Operations",
|
||||
"emoji": "🛍️",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名经验丰富的商家运营专家,你擅长管理商家关系,优化商家业务流程,提高商家满意度。你对电商行业有深入的了解,并有优秀的商业洞察力。请在这个角色下为我解答以下问题。",
|
||||
@ -41,7 +41,7 @@
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "🚀 产品运营 - Product Operations",
|
||||
"name": "产品运营 - Product Operations",
|
||||
"emoji": "🚀",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名经验丰富的产品运营专家,你擅长分析市场和用户需求,并对产品生命周期各阶段的运营策略有深刻的理解。你有出色的团队协作能力和沟通技巧,能在不同部门间进行有效的协调。请在这个角色下为我解答以下问题。\n",
|
||||
@ -49,15 +49,15 @@
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "💼 销售运营 - Sales Operations",
|
||||
"emoji": "🎓",
|
||||
"name": "销售运营 - Sales Operations",
|
||||
"emoji": "💼",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名销售运营经理,你懂得如何优化销售流程,管理销售数据,提升销售效率。你能制定销售预测和目标,管理销售预算,并提供销售支持。请在这个角色下为我解答以下问题。",
|
||||
"description": "你现在是一名销售运营经理,你懂得如何优化销售流程,管理销售数据,提升销售效率。你能制定销售预测和目标,管理销售预算,并提供销售支持。请在这个角色下为我解答以下问题。"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "👨💻 用户运营 - User Operations",
|
||||
"name": "用户运营 - User Operations",
|
||||
"emoji": "👨💻",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名用户运营专家,你了解用户行为和需求,能够制定并执行针对性的用户运营策略。你有出色的用户服务能力,能有效处理用户反馈和投诉。请在这个角色下为我解答以下问题。\n",
|
||||
@ -65,7 +65,7 @@
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "📢 市场营销 - Marketing",
|
||||
"name": "市场营销 - Marketing",
|
||||
"emoji": "📢",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名专业的市场营销专家,你对营销策略和品牌推广有深入的理解。你熟知如何有效利用不同的渠道和工具来达成营销目标,并对消费者心理有深入的理解。请在这个角色下为我解答以下问题。",
|
||||
@ -73,7 +73,7 @@
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "📈 商业数据分析 - Business Data Analysis",
|
||||
"name": "商业数据分析 - Business Data Analysis",
|
||||
"emoji": "📈",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名商业数据分析师,你精通数据分析方法和工具,能够从大量数据中提取出有价值的商业洞察。你对业务运营有深入的理解,并能提供数据驱动的优化建议。请在这个角色下为我解答以下问题。",
|
||||
@ -81,7 +81,7 @@
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "🗂️ 项目管理 - Project Management",
|
||||
"name": "项目管理 - Project Management",
|
||||
"emoji": "🗂️",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名资深的项目经理,你精通项目管理的各个方面,包括规划、组织、执行和控制。你擅长处理项目风险,解决问题,并有效地协调团队成员以实现项目目标。请在这个角色下为我解答以下问题。",
|
||||
@ -89,7 +89,7 @@
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "🔎 SEO专家 - SEO Expert",
|
||||
"name": "SEO专家 - SEO Expert",
|
||||
"emoji": "🔎",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名知识丰富的SEO专家,你了解搜索引擎的工作原理,熟知如何优化网页以提高其在搜索引擎中的排名。你对关键词研究、内容优化、链接建设等SEO策略有深入的了解。请在这个角色下为我解答以下问题。",
|
||||
@ -97,7 +97,7 @@
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "💻 网站运营数据分析 - Website Operations Data Analysis",
|
||||
"name": "网站运营数据分析 - Website Operations Data Analysis",
|
||||
"emoji": "💻",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名网站运营数据分析师,你擅长收集和分析网站数据,以了解用户行为和网站性能。你可以提供关于网站设计、内容和营销策略的数据支持。请在这个角色下为我解答以下问题。\n",
|
||||
@ -105,7 +105,7 @@
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "📊 数据分析师 - Data Analyst",
|
||||
"name": "数据分析师 - Data Analyst",
|
||||
"emoji": "📊",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名数据分析师,你精通各种统计分析方法,懂得如何清洗、处理和解析数据以获得有价值的洞察。你擅长利用数据驱动的方式来解决问题和提升决策效率。请在这个角色下为我解答以下问题。",
|
||||
@ -113,7 +113,7 @@
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "🖥️ 前端工程师 - Frontend Engineer",
|
||||
"name": "前端工程师 - Frontend Engineer",
|
||||
"emoji": "🖥️",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名专业的前端工程师,你对HTML、CSS、JavaScript等前端技术有深入的了解,能够制作和优化用户界面。你能够解决浏览器兼容性问题,提升网页性能,并实现优秀的用户体验。请在这个角色下为我解答以下问题。\n",
|
||||
@ -121,7 +121,7 @@
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "🛠️ 运维工程师 - Operations Engineer",
|
||||
"name": "运维工程师 - Operations Engineer",
|
||||
"emoji": "🛠️",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名运维工程师,你负责保障系统和服务的正常运行。你熟悉各种监控工具,能够高效地处理故障和进行系统优化。你还懂得如何进行数据备份和恢复,以保证数据安全。请在这个角色下为我解答以下问题。",
|
||||
@ -129,7 +129,7 @@
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "💻 开发工程师 - Software Engineer",
|
||||
"name": "开发工程师 - Software Engineer",
|
||||
"emoji": "💻",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名资深的软件工程师,你熟悉多种编程语言和开发框架,对软件开发的生命周期有深入的理解。你擅长解决技术问题,并具有优秀的逻辑思维能力。请在这个角色下为我解答以下问题。",
|
||||
@ -137,7 +137,7 @@
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "🧪 测试工程师 - Test Engineer",
|
||||
"name": "测试工程师 - Test Engineer",
|
||||
"emoji": "🧪",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名专业的测试工程师,你对软件测试方法论和测试工具有深入的了解。你的主要任务是发现和记录软件的缺陷,并确保软件的质量。你在寻找和解决问题上有出色的技能。请在这个角色下为我解答以下问题。",
|
||||
@ -145,7 +145,7 @@
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "👥 HR人力资源管理 - Human Resources Management",
|
||||
"name": "HR人力资源管理 - Human Resources Management",
|
||||
"emoji": "👥",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名人力资源管理专家,你了解如何招聘、培训、评估和激励员工。你精通劳动法规,擅长处理员工关系,并且在组织发展和变革管理方面有深入的见解。请在这个角色下为我解答以下问题。",
|
||||
@ -153,7 +153,7 @@
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "📋 行政 - Administration",
|
||||
"name": "行政 - Administration",
|
||||
"emoji": "📋",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名行政专员,你擅长组织和管理公司的日常运营事务,包括文件管理、会议安排、办公设施管理等。你有良好的人际沟通和组织能力,能在多任务环境中有效工作。请在这个角色下为我解答以下问题。",
|
||||
@ -161,7 +161,7 @@
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "💰 财务顾问 - Financial Advisor",
|
||||
"name": "财务顾问 - Financial Advisor",
|
||||
"emoji": "💰",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名财务顾问,你对金融市场、投资策略和财务规划有深厚的理解。你能提供财务咨询服务,帮助客户实现其财务目标。你擅长理解和解决复杂的财务问题。请在这个角色下为我解答以下问题。",
|
||||
@ -169,7 +169,7 @@
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "🩺 医生 - Doctor",
|
||||
"name": "医生 - Doctor",
|
||||
"emoji": "🩺",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名医生,具备丰富的医学知识和临床经验。你擅长诊断和治疗各种疾病,能为病人提供专业的医疗建议。你有良好的沟通技巧,能与病人和他们的家人建立信任关系。请在这个角色下为我解答以下问题。",
|
||||
@ -177,7 +177,7 @@
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "✒️ 编辑 - Editor",
|
||||
"name": "编辑 - Editor",
|
||||
"emoji": "✒️",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名编辑,你对文字有敏锐的感觉,擅长审校和修订稿件以确保其质量。你有出色的语言和沟通技巧,能与作者有效地合作以改善他们的作品。你对出版流程有深入的了解。请在这个角色下为我解答以下问题。\n",
|
||||
@ -185,7 +185,7 @@
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"name": "🧠 哲学家 - Philosopher",
|
||||
"name": "哲学家 - Philosopher",
|
||||
"emoji": "🧠",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名哲学家,你对世界的本质和人类存在的意义有深入的思考。你熟悉多种哲学流派,并能从哲学的角度分析和解决问题。你具有深刻的思维和出色的逻辑分析能力。请在这个角色下为我解答以下问题。\n",
|
||||
@ -193,7 +193,7 @@
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"name": "🛒 采购 - Procurement",
|
||||
"name": "采购 - Procurement",
|
||||
"emoji": "🛒",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名采购经理,你熟悉供应链管理,擅长进行供应商评估和价格谈判。你负责制定和执行采购策略,以保证货物的质量和供应的稳定。请在这个角色下为我解答以下问题。\n",
|
||||
@ -201,7 +201,7 @@
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"name": "⚖️ 法务 - Legal Affairs",
|
||||
"name": "法务 - Legal Affairs",
|
||||
"emoji": "⚖️",
|
||||
"group": "职业",
|
||||
"prompt": "你现在是一名法务专家,你了解公司法、合同法等相关法律,能为企业提供法律咨询和风险评估。你还擅长处理法律争端,并能起草和审核合同。请在这个角色下为我解答以下问题。",
|
||||
@ -209,7 +209,7 @@
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "🇨🇳 翻译成中文 - Chinese",
|
||||
"name": "翻译成中文 - Chinese",
|
||||
"emoji": "🇨🇳",
|
||||
"group": "语言",
|
||||
"prompt": "你是一个好用的翻译助手。请将我的英文翻译成中文,将所有非中文的翻译成中文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合中文的语言习惯。",
|
||||
@ -217,7 +217,7 @@
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "🌐 翻译成英文 - English",
|
||||
"name": "翻译成英文 - English",
|
||||
"emoji": "🌐",
|
||||
"group": "语言",
|
||||
"prompt": "你是一个好用的翻译助手。请将我的中文翻译成英文,将所有非中文的翻译成英文。我发给你所有的话都是需要翻译的内容,你只需要回答翻译结果。翻译结果请符合英文的语言习惯。",
|
||||
@ -225,7 +225,7 @@
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"name": "📕 英语单词背诵助手",
|
||||
"name": "英语单词背诵助手",
|
||||
"emoji": "📕",
|
||||
"group": "语言",
|
||||
"prompt": "- 版本:0.1\n- 语言:中文\n- 描述:您是一位语言专家,擅长阐释英语词汇的复杂性。您的角色是将复杂的英语单词分解为简单的概念,提供易懂的英语解释,提供中文翻译,并提供助记设备以帮助记忆。\n\n技能\n1. 分析高级英语单词的拼写、发音和含义。\n2. 使用简单的英语词汇进行解释,然后提供中文翻译。\n3. 使用音标联想、形象联想和词源等记忆技巧。\n4. 创作高质量的句子,以示范单词在语境中的使用。\n\n规则\n1. 总是以使用简单的英语词汇进行解释为开头。\n2. 在适当的时候,保持解释和例句的清晰、准确和幽默。\n3. 确保助记设备与记忆相关且有效。\n\n工作流程\n1. 问候用户并询问他们感兴趣的英语单词。\n2. 分解单词,分析其拼写、发音和复杂含义。\n3. 用简单的英语词汇解释,使含义更易理解。\n4. 提供单词的中文翻译和简单的英语解释。\n5. 针对单词的特点提供个性化的助记策略。\n6. 使用单词构建高质量、信息丰富且引人入胜的句子。\n\n初始化\n作为一名<角色>,您必须遵循<规则>并使用<语言>进行沟通。在问候用户时,确认他们想要理解和记忆的英语单词,然后按照<工作流程>进行操作。",
|
||||
@ -233,7 +233,7 @@
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"name": "📖 文章总结 - Summarize",
|
||||
"name": "文章总结 - Summarize",
|
||||
"emoji": "📖",
|
||||
"group": "阅读",
|
||||
"prompt": "总结下面的文章,给出总结、摘要、观点三个部分内容,其中观点部分要使用列表列出,使用 Markdown 回复",
|
||||
17
src/renderer/src/hooks/useAgents.ts
Normal file
17
src/renderer/src/hooks/useAgents.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { RootState } from '@renderer/store'
|
||||
import { addAgent, removeAgent, updateAgent, updateAgents } from '@renderer/store/agents'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
export function useAgents() {
|
||||
const agents = useSelector((state: RootState) => state.agents.agents)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return {
|
||||
agents,
|
||||
addAgent: (agent: Agent) => dispatch(addAgent(agent)),
|
||||
removeAgent: (agent: Agent) => dispatch(removeAgent(agent)),
|
||||
updateAgent: (agent: Agent) => dispatch(updateAgent(agent)),
|
||||
updateAgents: (agents: Agent[]) => dispatch(updateAgents(agents))
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,8 @@ const resources = {
|
||||
provider: 'Provider',
|
||||
you: 'You',
|
||||
save: 'Save',
|
||||
footnotes: 'References'
|
||||
footnotes: 'References',
|
||||
select: 'Select'
|
||||
},
|
||||
button: {
|
||||
add: 'Add',
|
||||
@ -48,9 +49,7 @@ const resources = {
|
||||
'switch.disabled': 'Switching is disabled while the assistant is generating'
|
||||
},
|
||||
chat: {
|
||||
save: 'Save'
|
||||
},
|
||||
assistant: {
|
||||
save: 'Save',
|
||||
'default.name': '😀 Default Assistant',
|
||||
'default.description': "Hello, I'm Default Assistant. You can start chatting with me right away",
|
||||
'default.topic.name': 'Default Topic',
|
||||
@ -83,8 +82,18 @@ const resources = {
|
||||
'settings.max': 'Max',
|
||||
'suggestions.title': 'Suggested Questions'
|
||||
},
|
||||
apps: {
|
||||
title: 'Agents'
|
||||
agents: {
|
||||
title: 'Agents',
|
||||
my_agents: 'My Agents',
|
||||
'add.title': 'Add Agent',
|
||||
'edit.title': 'Edit Agent',
|
||||
'add.name': 'Name',
|
||||
'add.name.placeholder': 'Enter name',
|
||||
'add.prompt': 'Prompt',
|
||||
'add.prompt.placeholder': 'Enter prompt',
|
||||
'add.button': 'Add',
|
||||
'manage.title': 'Manage Agents',
|
||||
'delete.popup.content': 'Are you sure you want to delete this agent?'
|
||||
},
|
||||
provider: {
|
||||
openai: 'OpenAI',
|
||||
@ -212,7 +221,8 @@ const resources = {
|
||||
regenerate: '重新生成',
|
||||
provider: '提供商',
|
||||
you: '用户',
|
||||
footnote: '引用内容'
|
||||
footnote: '引用内容',
|
||||
select: '选择'
|
||||
},
|
||||
button: {
|
||||
add: '添加',
|
||||
@ -235,9 +245,7 @@ const resources = {
|
||||
'switch.disabled': '模型回复完成后才能切换'
|
||||
},
|
||||
chat: {
|
||||
save: '保存'
|
||||
},
|
||||
assistant: {
|
||||
save: '保存',
|
||||
'default.name': '😃 默认助手 - Assistant',
|
||||
'default.description': '你好,我是默认助手。你可以立刻开始跟我聊天。',
|
||||
'default.topic.name': '默认话题',
|
||||
@ -271,8 +279,18 @@ const resources = {
|
||||
'settings.max': '不限',
|
||||
'suggestions.title': '建议的问题'
|
||||
},
|
||||
apps: {
|
||||
title: '智能体'
|
||||
agents: {
|
||||
title: '智能体',
|
||||
my_agents: '我的智能体',
|
||||
'add.title': '添加智能体',
|
||||
'edit.title': '编辑智能体',
|
||||
'add.name': '名称',
|
||||
'add.name.placeholder': '输入名称',
|
||||
'add.prompt': '提示词',
|
||||
'add.prompt.placeholder': '输入提示词',
|
||||
'add.button': '添加',
|
||||
'manage.title': '管理智能体',
|
||||
'delete.popup.content': '确定要删除此智能体吗?'
|
||||
},
|
||||
provider: {
|
||||
openai: 'OpenAI',
|
||||
|
||||
128
src/renderer/src/pages/agents/AgentsPage.tsx
Normal file
128
src/renderer/src/pages/agents/AgentsPage.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import { UnorderedListOutlined } from '@ant-design/icons'
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import Agents from '@renderer/config/agents.json'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { getDefaultAssistant } from '@renderer/services/assistant'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { Col, Row, Typography } from 'antd'
|
||||
import { find, groupBy } from 'lodash'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AgentCard from './components/AgentCard'
|
||||
import ManageAgentsPopup from './components/ManageAgentsPopup'
|
||||
import UserAgents from './components/UserAgents'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
const AppsPage: FC = () => {
|
||||
const { assistants, addAssistant } = useAssistants()
|
||||
const { agents } = useAgents()
|
||||
const agentGroups = groupBy(
|
||||
Agents.map((a) => ({ ...a, id: String(a.id) })),
|
||||
'group'
|
||||
)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onAddAgentConfirm = (agent: Agent) => {
|
||||
const added = find(assistants, { id: agent.id })
|
||||
|
||||
window.modal.confirm({
|
||||
title: agent.emoji + ' ' + agent.name,
|
||||
content: agent.description || agent.prompt,
|
||||
icon: null,
|
||||
closable: true,
|
||||
maskClosable: true,
|
||||
okButtonProps: { type: 'primary', disabled: Boolean(added) },
|
||||
okText: added ? t('button.added') : t('button.add'),
|
||||
onOk: () => onAddAgent(agent)
|
||||
})
|
||||
}
|
||||
|
||||
const onAddAgent = (agent: Agent) => {
|
||||
addAssistant({
|
||||
...getDefaultAssistant(),
|
||||
...agent,
|
||||
name: agent.emoji ? agent.emoji + ' ' + agent.name : agent.name,
|
||||
id: String(agent.id)
|
||||
})
|
||||
window.message.success({
|
||||
content: t('message.assistant.added.content'),
|
||||
key: 'agent-added',
|
||||
style: { marginTop: '5vh' }
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar>
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('agents.title')}</NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer>
|
||||
<AssistantsContainer>
|
||||
<HStack alignItems="center" style={{ marginBottom: 16 }}>
|
||||
<Title level={3}>{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 }}>
|
||||
{group}
|
||||
</Title>
|
||||
<Row gutter={16}>
|
||||
{agentGroups[group].map((agent, index) => {
|
||||
return (
|
||||
<Col span={8} key={group + index}>
|
||||
<AgentCard onClick={() => onAddAgentConfirm(agent)} agent={agent as any} />
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
</Row>
|
||||
</div>
|
||||
))}
|
||||
<div style={{ minHeight: 20 }} />
|
||||
</AssistantsContainer>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
background-color: var(--color-background);
|
||||
`
|
||||
|
||||
const AssistantsContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
padding: 20px;
|
||||
max-width: 1000px;
|
||||
`
|
||||
|
||||
const ManageIcon = styled(UnorderedListOutlined)`
|
||||
font-size: 18px;
|
||||
color: var(--color-icon);
|
||||
cursor: pointer;
|
||||
margin-bottom: 0.5em;
|
||||
margin-left: 0.5em;
|
||||
`
|
||||
|
||||
export default AppsPage
|
||||
135
src/renderer/src/pages/agents/components/AddAgentPopup.tsx
Normal file
135
src/renderer/src/pages/agents/components/AddAgentPopup.tsx
Normal file
@ -0,0 +1,135 @@
|
||||
import 'emoji-picker-element'
|
||||
|
||||
import EmojiPicker from '@renderer/components/EmojiPicker'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { getLeadingEmoji, uuid } from '@renderer/utils'
|
||||
import { Button, Form, FormInstance, Input, Modal, Popover } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Props {
|
||||
agent?: Agent
|
||||
resolve: (data: Agent | null) => void
|
||||
}
|
||||
|
||||
type FieldType = {
|
||||
id: string
|
||||
name: string
|
||||
prompt: string
|
||||
}
|
||||
|
||||
const PopupContainer: React.FC<Props> = ({ agent, resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const [form] = Form.useForm()
|
||||
const { t } = useTranslation()
|
||||
const { addAgent, updateAgent } = useAgents()
|
||||
const formRef = useRef<FormInstance>(null)
|
||||
const [emoji, setEmoji] = useState(agent?.emoji)
|
||||
|
||||
const onFinish = (values: FieldType) => {
|
||||
const _emoji = emoji || getLeadingEmoji(values.name)
|
||||
|
||||
if (values.name.trim() === '' || values.prompt.trim() === '') {
|
||||
return
|
||||
}
|
||||
|
||||
if (agent) {
|
||||
const _agent = {
|
||||
...agent,
|
||||
name: values.name,
|
||||
emoji: _emoji,
|
||||
prompt: values.prompt
|
||||
}
|
||||
updateAgent(_agent)
|
||||
resolve(_agent)
|
||||
setOpen(false)
|
||||
return
|
||||
}
|
||||
|
||||
const _agent = {
|
||||
id: uuid(),
|
||||
name: values.name,
|
||||
emoji: _emoji,
|
||||
prompt: values.prompt,
|
||||
group: 'user'
|
||||
}
|
||||
|
||||
addAgent(_agent)
|
||||
resolve(_agent)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onClose = () => {
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (agent) {
|
||||
form.setFieldsValue({
|
||||
name: agent.name,
|
||||
prompt: agent.prompt
|
||||
})
|
||||
}
|
||||
}, [agent, form])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
style={{ marginTop: '10vh' }}
|
||||
title={agent ? t('agents.edit.title') : t('agents.add.title')}
|
||||
open={open}
|
||||
onOk={() => formRef.current?.submit()}
|
||||
onCancel={onCancel}
|
||||
maskClosable={false}
|
||||
afterClose={onClose}
|
||||
okText={agent ? t('common.save') : t('agents.add.button')}>
|
||||
<Form
|
||||
ref={formRef}
|
||||
form={form}
|
||||
labelCol={{ flex: '80px' }}
|
||||
labelAlign="left"
|
||||
colon={false}
|
||||
style={{ marginTop: 25 }}
|
||||
onFinish={onFinish}>
|
||||
<Form.Item name="name" label="Emoji">
|
||||
<Popover content={<EmojiPicker onEmojiClick={setEmoji} />} trigger="click" arrow>
|
||||
<Button icon={emoji && <span style={{ fontSize: 20 }}>{emoji}</span>}>{t('common.select')}</Button>
|
||||
</Popover>
|
||||
</Form.Item>
|
||||
<Form.Item name="name" label={t('agents.add.name')} rules={[{ required: true }]}>
|
||||
<Input placeholder={t('agents.add.name.placeholder')} spellCheck={false} allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item name="prompt" label={t('agents.add.prompt')} rules={[{ required: true }]}>
|
||||
<TextArea placeholder={t('agents.add.prompt.placeholder')} spellCheck={false} rows={4} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default class AddAgentPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('AddAgentPopup')
|
||||
}
|
||||
static show(agent?: Agent) {
|
||||
return new Promise<Agent | null>((resolve) => {
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
agent={agent}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
}}
|
||||
/>,
|
||||
'AddAgentPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
81
src/renderer/src/pages/agents/components/AgentCard.tsx
Normal file
81
src/renderer/src/pages/agents/components/AgentCard.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import { Agent } from '@renderer/types'
|
||||
import { Col, Typography } from 'antd'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface Props {
|
||||
agent: Agent
|
||||
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>
|
||||
</AgentHeader>
|
||||
<AgentCardPrompt>{agent.prompt}</AgentCardPrompt>
|
||||
</Col>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 16px;
|
||||
background-color: var(--color-background-soft);
|
||||
border: 0.5px solid var(--color-border);
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
&:hover {
|
||||
background-color: var(--color-background-mute);
|
||||
}
|
||||
`
|
||||
const EmojiHeader = styled.div`
|
||||
width: 25px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 5px;
|
||||
font-size: 25px;
|
||||
line-height: 25px;
|
||||
`
|
||||
|
||||
const AgentHeader = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const AgentName = styled(Title)`
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
color: var(--color-white);
|
||||
font-weight: 900;
|
||||
`
|
||||
|
||||
const AgentCardPrompt = styled.div`
|
||||
color: #666;
|
||||
margin-top: 6px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
export default AgentCard
|
||||
109
src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx
Normal file
109
src/renderer/src/pages/agents/components/ManageAgentsPopup.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import { DeleteOutlined, EditOutlined, MenuOutlined } from '@ant-design/icons'
|
||||
import DragableList from '@renderer/components/DragableList'
|
||||
import { Box, HStack } from '@renderer/components/Layout'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { Empty, Modal, Popconfirm } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AddAgentPopup from './AddAgentPopup'
|
||||
|
||||
const PopupContainer: React.FC = () => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const { t } = useTranslation()
|
||||
const { agents, removeAgent, updateAgents } = useAgents()
|
||||
|
||||
const onOk = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const onClose = async () => {
|
||||
ManageAgentsPopup.hide()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (agents.length === 0) {
|
||||
setOpen(false)
|
||||
}
|
||||
}, [agents])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
style={{ marginTop: '10vh' }}
|
||||
title={t('agents.manage.title')}
|
||||
open={open}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
afterClose={onClose}
|
||||
footer={null}>
|
||||
<Container>
|
||||
{agents.length > 0 && (
|
||||
<DragableList list={agents} onUpdate={updateAgents}>
|
||||
{(item) => (
|
||||
<AgentItem>
|
||||
<Box mr={8}>
|
||||
{item.emoji} {item.name}
|
||||
</Box>
|
||||
<HStack gap="15px">
|
||||
<Popconfirm
|
||||
title={t('agents.delete.popup.content')}
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => removeAgent(item)}>
|
||||
<DeleteOutlined style={{ color: 'var(--color-error)' }} />
|
||||
</Popconfirm>
|
||||
<EditOutlined style={{ cursor: 'pointer' }} onClick={() => AddAgentPopup.show(item)} />
|
||||
<MenuOutlined style={{ cursor: 'move' }} />
|
||||
</HStack>
|
||||
</AgentItem>
|
||||
)}
|
||||
</DragableList>
|
||||
)}
|
||||
{agents.length === 0 && <Empty description="" />}
|
||||
</Container>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 16px;
|
||||
height: 50vh;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
const AgentItem = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
user-select: none;
|
||||
background-color: var(--color-background-soft);
|
||||
margin-bottom: 8px;
|
||||
.anticon {
|
||||
font-size: 16px;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-background-mute);
|
||||
}
|
||||
`
|
||||
|
||||
export default class ManageAgentsPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide('ManageAgentsPopup')
|
||||
}
|
||||
static show() {
|
||||
TopView.show(<PopupContainer />, 'ManageAgentsPopup')
|
||||
}
|
||||
}
|
||||
57
src/renderer/src/pages/agents/components/UserAgents.tsx
Normal file
57
src/renderer/src/pages/agents/components/UserAgents.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { PlusOutlined } from '@ant-design/icons'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { Agent } from '@renderer/types'
|
||||
import { Col, Row } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AddAssistantPopup from './AddAgentPopup'
|
||||
import AgentCard from './AgentCard'
|
||||
|
||||
interface Props {
|
||||
onAdd: (agent: Agent) => void
|
||||
}
|
||||
|
||||
const UserAgents: FC<Props> = ({ onAdd }) => {
|
||||
const { agents } = useAgents()
|
||||
|
||||
const onAddMyAgentClick = () => {
|
||||
AddAssistantPopup.show()
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||
{agents.map((agent) => (
|
||||
<Col span={8} key={agent.id}>
|
||||
<AgentCard agent={agent} onClick={() => onAdd(agent)} />
|
||||
</Col>
|
||||
))}
|
||||
<Col span={8}>
|
||||
<AssistantCardContainer style={{ borderStyle: 'dashed' }} onClick={onAddMyAgentClick}>
|
||||
<PlusOutlined />
|
||||
</AssistantCardContainer>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
const AssistantCardContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
min-height: 84px;
|
||||
.anticon {
|
||||
font-size: 16px;
|
||||
color: var(--color-icon);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
}
|
||||
`
|
||||
|
||||
export default UserAgents
|
||||
@ -1,169 +0,0 @@
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import SYSTEM_ASSISTANTS from '@renderer/config/assistants.json'
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { getDefaultAssistant } from '@renderer/services/assistant'
|
||||
import { SystemAssistant } from '@renderer/types'
|
||||
import { Col, Row, Typography } from 'antd'
|
||||
import { find, groupBy } from 'lodash'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
const AppsPage: FC = () => {
|
||||
const { assistants, addAssistant } = useAssistants()
|
||||
const assistantGroups = groupBy(
|
||||
SYSTEM_ASSISTANTS.map((a) => ({ ...a, id: String(a.id) })),
|
||||
'group'
|
||||
)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onAddAssistantConfirm = (assistant: SystemAssistant) => {
|
||||
const added = find(assistants, { id: assistant.id })
|
||||
|
||||
window.modal.confirm({
|
||||
title: assistant.name,
|
||||
content: assistant.description || assistant.prompt,
|
||||
icon: null,
|
||||
closable: true,
|
||||
maskClosable: true,
|
||||
okButtonProps: { type: 'primary', disabled: Boolean(added) },
|
||||
okText: added ? t('button.added') : t('button.add'),
|
||||
onOk: () => onAddAssistant(assistant)
|
||||
})
|
||||
}
|
||||
|
||||
const onAddAssistant = (assistant: SystemAssistant) => {
|
||||
addAssistant({
|
||||
...getDefaultAssistant(),
|
||||
...assistant,
|
||||
id: String(assistant.id)
|
||||
})
|
||||
window.message.success({
|
||||
content: t('message.assistant.added.content'),
|
||||
key: 'assistant-added',
|
||||
style: { marginTop: '5vh' }
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar>
|
||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('apps.title')}</NavbarCenter>
|
||||
</Navbar>
|
||||
<ContentContainer>
|
||||
<AssistantsContainer>
|
||||
{Object.keys(assistantGroups).map((group) => (
|
||||
<div key={group}>
|
||||
<Title level={3} key={group} style={{ marginBottom: 16 }}>
|
||||
{group}
|
||||
</Title>
|
||||
<Row gutter={16}>
|
||||
{assistantGroups[group].map((assistant, index) => {
|
||||
return (
|
||||
<Col span={8} key={group + index}>
|
||||
<AssistantCard onClick={() => onAddAssistantConfirm(assistant)}>
|
||||
<EmojiHeader>{assistant.emoji}</EmojiHeader>
|
||||
<Col>
|
||||
<AssistantHeader>
|
||||
<AssistantName level={5} style={{ marginBottom: 0 }}>
|
||||
{assistant.name.replace(assistant.emoji + ' ', '')}
|
||||
</AssistantName>
|
||||
</AssistantHeader>
|
||||
<AssistantCardPrompt>{assistant.prompt}</AssistantCardPrompt>
|
||||
</Col>
|
||||
</AssistantCard>
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
</Row>
|
||||
</div>
|
||||
))}
|
||||
<div style={{ minHeight: 20 }} />
|
||||
</AssistantsContainer>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
background-color: var(--color-background);
|
||||
`
|
||||
|
||||
const AssistantsContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
padding: 20px;
|
||||
max-width: 1000px;
|
||||
`
|
||||
|
||||
const AssistantCard = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 16px;
|
||||
background-color: var(--color-background-soft);
|
||||
border: 0.5px solid var(--color-border);
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
&:hover {
|
||||
background-color: var(--color-background-mute);
|
||||
}
|
||||
`
|
||||
const EmojiHeader = styled.div`
|
||||
width: 25px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 5px;
|
||||
font-size: 25px;
|
||||
line-height: 25px;
|
||||
`
|
||||
|
||||
const AssistantHeader = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const AssistantName = styled(Title)`
|
||||
font-size: 18px;
|
||||
line-height: 1.2;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
color: var(--color-white);
|
||||
font-weight: 900;
|
||||
`
|
||||
|
||||
const AssistantCardPrompt = styled.div`
|
||||
color: #666;
|
||||
margin-top: 6px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
export default AppsPage
|
||||
@ -113,9 +113,7 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
|
||||
<AssistantItem
|
||||
onClick={() => onSwitchAssistant(assistant)}
|
||||
className={assistant.id === activeAssistant?.id ? 'active' : ''}>
|
||||
<AssistantName className="name">
|
||||
{assistant.name || t('assistant.default.name')}
|
||||
</AssistantName>
|
||||
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
|
||||
</AssistantItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@ -31,7 +31,7 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
||||
() => ({
|
||||
id: 'assistant',
|
||||
role: 'assistant',
|
||||
content: assistant.description || assistant.prompt || t('assistant.default.description'),
|
||||
content: assistant.description || assistant.prompt || t('chat.default.description'),
|
||||
assistantId: assistant.id,
|
||||
topicId: topic.id,
|
||||
status: 'pending',
|
||||
@ -50,7 +50,7 @@ const Messages: FC<Props> = ({ assistant, topic }) => {
|
||||
)
|
||||
|
||||
const autoRenameTopic = useCallback(async () => {
|
||||
if (topic.name === t('assistant.default.topic.name') && messages.length >= 2) {
|
||||
if (topic.name === t('chat.default.topic.name') && messages.length >= 2) {
|
||||
const summaryText = await fetchMessagesSummary({ messages, assistant })
|
||||
summaryText && updateTopic({ ...topic, name: summaryText })
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ const NavigationCenter: FC<Props> = ({ activeAssistant }) => {
|
||||
<i className="iconfont icon-showsidebarhoriz" />
|
||||
</NewButton>
|
||||
)}
|
||||
<AssistantName>{removeLeadingEmoji(assistant?.name) || t('assistant.default.name')}</AssistantName>
|
||||
<AssistantName>{removeLeadingEmoji(assistant?.name) || t('chat.default.name')}</AssistantName>
|
||||
<SelectModelButton assistant={assistant} />
|
||||
</NavbarCenter>
|
||||
)
|
||||
|
||||
@ -148,45 +148,45 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
<Container id="inputbar" style={{ minHeight: expended ? '100%' : 'var(--input-bar-height)' }}>
|
||||
<Toolbar onDoubleClick={() => setExpend(!expended)}>
|
||||
<ToolbarMenu>
|
||||
<Tooltip placement="top" title={t('assistant.input.new_topic')} arrow>
|
||||
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
||||
<ToolbarButton type="text" onClick={addNewTopic}>
|
||||
<PlusCircleOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={t('assistant.input.topics')} arrow>
|
||||
<Tooltip placement="top" title={t('chat.input.topics')} arrow>
|
||||
<ToolbarButton type="text" onClick={() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)}>
|
||||
<HistoryOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={t('assistant.input.settings')} arrow>
|
||||
<Tooltip placement="top" title={t('chat.input.settings')} arrow>
|
||||
<ToolbarButton type="text" onClick={() => EventEmitter.emit(EVENT_NAMES.SHOW_CHAT_SETTINGS)}>
|
||||
<ControlOutlined />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={t('assistant.input.clear')} arrow>
|
||||
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
||||
<Popconfirm
|
||||
title={t('assistant.input.clear.content')}
|
||||
title={t('chat.input.clear.content')}
|
||||
placement="top"
|
||||
onConfirm={clearTopic}
|
||||
okButtonProps={{ danger: true }}
|
||||
icon={<QuestionCircleOutlined style={{ color: 'red' }} />}
|
||||
okText={t('assistant.input.clear')}>
|
||||
okText={t('chat.input.clear')}>
|
||||
<ToolbarButton type="text">
|
||||
<ClearOutlined />
|
||||
</ToolbarButton>
|
||||
</Popconfirm>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" title={expended ? t('assistant.input.collapse') : t('assistant.input.expand')} arrow>
|
||||
<Tooltip placement="top" title={expended ? t('chat.input.collapse') : t('chat.input.expand')} arrow>
|
||||
<ToolbarButton type="text" onClick={() => setExpend(!expended)}>
|
||||
{expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
{showInputEstimatedTokens && (
|
||||
<TextCount>
|
||||
<Tooltip title={t('assistant.input.context_count.tip')}>
|
||||
<Tooltip title={t('chat.input.context_count.tip')}>
|
||||
<Tag style={{ cursor: 'pointer' }}>{assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT}</Tag>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('assistant.input.estimated_tokens.tip')}>
|
||||
<Tooltip title={t('chat.input.estimated_tokens.tip')}>
|
||||
<Tag style={{ cursor: 'pointer' }}>↑{`${inputTokenCount} / ${estimateTokenCount}`}</Tag>
|
||||
</Tooltip>
|
||||
</TextCount>
|
||||
@ -194,7 +194,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
</ToolbarMenu>
|
||||
<ToolbarMenu>
|
||||
{generating && (
|
||||
<Tooltip placement="top" title={t('assistant.input.pause')} arrow>
|
||||
<Tooltip placement="top" title={t('chat.input.pause')} arrow>
|
||||
<ToolbarButton type="text" onClick={onPause}>
|
||||
<PauseCircleOutlined style={{ color: 'var(--color-error)' }} />
|
||||
</ToolbarButton>
|
||||
@ -207,7 +207,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={t('assistant.input.placeholder')}
|
||||
placeholder={t('chat.input.placeholder')}
|
||||
autoFocus
|
||||
contextMenu="true"
|
||||
variant="borderless"
|
||||
|
||||
@ -15,13 +15,13 @@ const SendMessageButton: FC<Props> = ({ sendMessage }) => {
|
||||
|
||||
const sendSettingItems: MenuProps['items'] = [
|
||||
{
|
||||
label: `Enter ${t('assistant.input.send')}`,
|
||||
label: `Enter ${t('chat.input.send')}`,
|
||||
key: 'Enter',
|
||||
icon: <EnterOutlined />,
|
||||
onClick: () => setSendMessageShortcut('Enter')
|
||||
},
|
||||
{
|
||||
label: `Shift+Enter ${t('assistant.input.send')}`,
|
||||
label: `Shift+Enter ${t('chat.input.send')}`,
|
||||
key: 'Shift+Enter',
|
||||
icon: <ArrowUpOutlined />,
|
||||
onClick: () => setSendMessageShortcut('Shift+Enter')
|
||||
@ -36,7 +36,7 @@ const SendMessageButton: FC<Props> = ({ sendMessage }) => {
|
||||
arrow
|
||||
menu={{ items: sendSettingItems, selectable: true, defaultSelectedKeys: [sendMessageShortcut] }}
|
||||
style={{ width: 'auto' }}>
|
||||
{t('assistant.input.send')}
|
||||
{t('chat.input.send')}
|
||||
<SendOutlined />
|
||||
</Dropdown.Button>
|
||||
)
|
||||
|
||||
@ -80,14 +80,14 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
<Container>
|
||||
<SettingSubtitle>
|
||||
{t('settings.messages.model.title')}{' '}
|
||||
<Tooltip title={t('assistant.settings.reset')}>
|
||||
<Tooltip title={t('chat.settings.reset')}>
|
||||
<ReloadOutlined onClick={onReset} style={{ cursor: 'pointer', fontSize: 12, padding: '0 3px' }} />
|
||||
</Tooltip>
|
||||
</SettingSubtitle>
|
||||
<SettingDivider />
|
||||
<Row align="middle">
|
||||
<Label>{t('assistant.settings.temperature')}</Label>
|
||||
<Tooltip title={t('assistant.settings.temperature.tip')}>
|
||||
<Label>{t('chat.settings.temperature')}</Label>
|
||||
<Tooltip title={t('chat.settings.temperature.tip')}>
|
||||
<QuestionIcon />
|
||||
</Tooltip>
|
||||
</Row>
|
||||
@ -114,8 +114,8 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
</Col>
|
||||
</Row>
|
||||
<Row align="middle">
|
||||
<Label>{t('assistant.settings.conext_count')}</Label>
|
||||
<Tooltip title={t('assistant.settings.conext_count.tip')}>
|
||||
<Label>{t('chat.settings.conext_count')}</Label>
|
||||
<Tooltip title={t('chat.settings.conext_count.tip')}>
|
||||
<QuestionIcon />
|
||||
</Tooltip>
|
||||
</Row>
|
||||
@ -124,7 +124,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
<Slider
|
||||
min={0}
|
||||
max={20}
|
||||
marks={{ 0: '0', 10: '10', 20: t('assistant.settings.max') }}
|
||||
marks={{ 0: '0', 10: '10', 20: t('chat.settings.max') }}
|
||||
onChange={onConextCountChange}
|
||||
value={typeof contextCount === 'number' ? contextCount : 0}
|
||||
step={1}
|
||||
|
||||
@ -27,7 +27,7 @@ const TopicsTab: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTop
|
||||
(topic: Topic) => {
|
||||
const menus: MenuProps['items'] = [
|
||||
{
|
||||
label: t('assistant.topics.auto_rename'),
|
||||
label: t('chat.topics.auto_rename'),
|
||||
key: 'auto-rename',
|
||||
icon: <OpenAIOutlined />,
|
||||
async onClick() {
|
||||
@ -41,12 +41,12 @@ const TopicsTab: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTop
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('assistant.topics.edit.title'),
|
||||
label: t('chat.topics.edit.title'),
|
||||
key: 'rename',
|
||||
icon: <EditOutlined />,
|
||||
async onClick() {
|
||||
const name = await PromptPopup.show({
|
||||
title: t('assistant.topics.edit.title'),
|
||||
title: t('chat.topics.edit.title'),
|
||||
message: '',
|
||||
defaultValue: topic?.name || ''
|
||||
})
|
||||
|
||||
@ -82,8 +82,8 @@ const AssistantSettings: FC = () => {
|
||||
<SettingDivider />
|
||||
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.assistant.model_params')}</SettingSubtitle>
|
||||
<Row align="middle">
|
||||
<Label>{t('assistant.settings.temperature')}</Label>
|
||||
<Tooltip title={t('assistant.settings.temperature.tip')}>
|
||||
<Label>{t('chat.settings.temperature')}</Label>
|
||||
<Tooltip title={t('chat.settings.temperature.tip')}>
|
||||
<QuestionIcon />
|
||||
</Tooltip>
|
||||
</Row>
|
||||
@ -110,8 +110,8 @@ const AssistantSettings: FC = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
<Row align="middle">
|
||||
<Label>{t('assistant.settings.conext_count')}</Label>
|
||||
<Tooltip title={t('assistant.settings.conext_count.tip')}>
|
||||
<Label>{t('chat.settings.conext_count')}</Label>
|
||||
<Tooltip title={t('chat.settings.conext_count.tip')}>
|
||||
<QuestionIcon />
|
||||
</Tooltip>
|
||||
</Row>
|
||||
@ -120,7 +120,7 @@ const AssistantSettings: FC = () => {
|
||||
<Slider
|
||||
min={0}
|
||||
max={20}
|
||||
marks={{ 0: '0', 5: '5', 10: '10', 15: '15', 20: t('assistant.settings.max') }}
|
||||
marks={{ 0: '0', 5: '5', 10: '10', 15: '15', 20: t('chat.settings.max') }}
|
||||
onChange={onConextCountChange}
|
||||
value={typeof contextCount === 'number' ? contextCount : 0}
|
||||
step={1}
|
||||
@ -138,7 +138,7 @@ const AssistantSettings: FC = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
<Button onClick={onReset} style={{ width: 100 }}>
|
||||
{t('assistant.settings.reset')}
|
||||
{t('chat.settings.reset')}
|
||||
</Button>
|
||||
</SettingContainer>
|
||||
)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import i18n from '@renderer/i18n'
|
||||
import LocalStorage from '@renderer/services/storage'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setAvatar } from '@renderer/store/runtime'
|
||||
@ -23,6 +24,7 @@ const GeneralSettings: FC = () => {
|
||||
const onSelectLanguage = (value: string) => {
|
||||
dispatch(setLanguage(value))
|
||||
localStorage.setItem('language', value)
|
||||
i18n.changeLanguage(value)
|
||||
}
|
||||
|
||||
const onSetProxyUrl = () => {
|
||||
|
||||
@ -115,18 +115,19 @@ const PopupContainer: React.FC<Props> = ({ title, provider, resolve }) => {
|
||||
export default class AddModelPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide(this.topviewId)
|
||||
TopView.hide('AddModelPopup')
|
||||
}
|
||||
static show(props: ShowParams) {
|
||||
return new Promise<any>((resolve) => {
|
||||
this.topviewId = TopView.show(
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
}}
|
||||
/>
|
||||
/>,
|
||||
'AddModelPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -54,18 +54,19 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
||||
export default class AddProviderPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide(this.topviewId)
|
||||
TopView.hide('AddProviderPopup')
|
||||
}
|
||||
static show(provider?: Provider) {
|
||||
return new Promise<string>((resolve) => {
|
||||
this.topviewId = TopView.show(
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
provider={provider}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
}}
|
||||
/>
|
||||
/>,
|
||||
'AddProviderPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -224,18 +224,19 @@ const Question = styled(QuestionCircleOutlined)`
|
||||
export default class EditModelsPopup {
|
||||
static topviewId = 0
|
||||
static hide() {
|
||||
TopView.hide(this.topviewId)
|
||||
TopView.hide('EditModelsPopup')
|
||||
}
|
||||
static show(props: ShowParams) {
|
||||
return new Promise<any>((resolve) => {
|
||||
this.topviewId = TopView.show(
|
||||
TopView.show(
|
||||
<PopupContainer
|
||||
{...props}
|
||||
resolve={(v) => {
|
||||
resolve(v)
|
||||
this.hide()
|
||||
}}
|
||||
/>
|
||||
/>,
|
||||
'EditModelsPopup'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { uuid } from '@renderer/utils'
|
||||
export function getDefaultAssistant(): Assistant {
|
||||
return {
|
||||
id: 'default',
|
||||
name: i18n.t('assistant.default.name'),
|
||||
name: i18n.t('chat.default.name'),
|
||||
prompt: '',
|
||||
topics: [getDefaultTopic()]
|
||||
}
|
||||
@ -15,7 +15,7 @@ export function getDefaultAssistant(): Assistant {
|
||||
export function getDefaultTopic(): Topic {
|
||||
return {
|
||||
id: uuid(),
|
||||
name: i18n.t('assistant.default.topic.name'),
|
||||
name: i18n.t('chat.default.topic.name'),
|
||||
messages: []
|
||||
}
|
||||
}
|
||||
|
||||
33
src/renderer/src/store/agents.ts
Normal file
33
src/renderer/src/store/agents.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { Agent } from '@renderer/types'
|
||||
|
||||
export interface AgentsState {
|
||||
agents: Agent[]
|
||||
}
|
||||
|
||||
const initialState: AgentsState = {
|
||||
agents: []
|
||||
}
|
||||
|
||||
const runtimeSlice = createSlice({
|
||||
name: 'agents',
|
||||
initialState,
|
||||
reducers: {
|
||||
addAgent: (state, action: PayloadAction<Agent>) => {
|
||||
state.agents.push(action.payload)
|
||||
},
|
||||
removeAgent: (state, action: PayloadAction<Agent>) => {
|
||||
state.agents = state.agents.filter((a) => a.id !== action.payload.id)
|
||||
},
|
||||
updateAgent: (state, action: PayloadAction<Agent>) => {
|
||||
state.agents = state.agents.map((a) => (a.id === action.payload.id ? action.payload : a))
|
||||
},
|
||||
updateAgents: (state, action: PayloadAction<Agent[]>) => {
|
||||
state.agents = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { addAgent, removeAgent, updateAgent, updateAgents } = runtimeSlice.actions
|
||||
|
||||
export default runtimeSlice.reducer
|
||||
@ -3,6 +3,7 @@ import { useDispatch, useSelector, useStore } from 'react-redux'
|
||||
import { FLUSH, PAUSE, PERSIST, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from 'redux-persist'
|
||||
import storage from 'redux-persist/lib/storage'
|
||||
|
||||
import agents from './agents'
|
||||
import assistants from './assistants'
|
||||
import llm from './llm'
|
||||
import migrate from './migrate'
|
||||
@ -13,6 +14,7 @@ const rootReducer = combineReducers({
|
||||
assistants,
|
||||
settings,
|
||||
llm,
|
||||
agents,
|
||||
runtime
|
||||
})
|
||||
|
||||
@ -20,7 +22,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 17,
|
||||
version: 18,
|
||||
blacklist: ['runtime'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -136,7 +136,7 @@ const initialState: LlmState = {
|
||||
}
|
||||
|
||||
const settingsSlice = createSlice({
|
||||
name: 'settings',
|
||||
name: 'llm',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateProvider: (state, action: PayloadAction<Provider>) => {
|
||||
|
||||
@ -271,6 +271,14 @@ const migrateConfig = {
|
||||
theme: 'auto'
|
||||
}
|
||||
}
|
||||
},
|
||||
'18': (state: RootState) => {
|
||||
return {
|
||||
...state,
|
||||
agents: {
|
||||
agents: []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ const initialState: RuntimeState = {
|
||||
}
|
||||
|
||||
const runtimeSlice = createSlice({
|
||||
name: 'settings',
|
||||
name: 'runtime',
|
||||
initialState,
|
||||
reducers: {
|
||||
setAvatar: (state, action: PayloadAction<string | null>) => {
|
||||
|
||||
@ -3,9 +3,10 @@ import OpenAI from 'openai'
|
||||
export type Assistant = {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
prompt: string
|
||||
topics: Topic[]
|
||||
emoji?: string
|
||||
description?: string
|
||||
model?: Model
|
||||
settings?: AssistantSettings
|
||||
}
|
||||
@ -60,7 +61,7 @@ export type Model = {
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type SystemAssistant = {
|
||||
export type Agent = {
|
||||
id: string
|
||||
name: string
|
||||
emoji: string
|
||||
|
||||
@ -101,6 +101,12 @@ export function removeLeadingEmoji(str: string): string {
|
||||
return str.replace(emojiRegex, '')
|
||||
}
|
||||
|
||||
export function getLeadingEmoji(str: string): string {
|
||||
const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)+/u
|
||||
const match = str.match(emojiRegex)
|
||||
return match ? match[0] : ''
|
||||
}
|
||||
|
||||
export function isFreeModel(model: Model) {
|
||||
return (model.id + model.name).toLocaleLowerCase().includes('free')
|
||||
}
|
||||
|
||||
@ -3469,6 +3469,7 @@ __metadata:
|
||||
electron-vite: "npm:^2.0.0"
|
||||
electron-window-state: "npm:^5.0.3"
|
||||
emittery: "npm:^1.0.3"
|
||||
emoji-picker-element: "npm:^1.22.1"
|
||||
eslint: "npm:^8.56.0"
|
||||
eslint-plugin-react: "npm:^7.34.3"
|
||||
eslint-plugin-react-hooks: "npm:^4.6.2"
|
||||
@ -4251,6 +4252,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"emoji-picker-element@npm:^1.22.1":
|
||||
version: 1.22.1
|
||||
resolution: "emoji-picker-element@npm:1.22.1"
|
||||
checksum: 10c0/3fbd6b5498796b4d46cc641a0276e934c683f2c0a63a00aef0082e7b2acc6711b4acab49c0cf38aaa12bf3bcd126aad1355afbf4580917f7aad5f2fb2ffa0d9c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"emoji-regex@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "emoji-regex@npm:8.0.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user