From 8589650f555189d4cb8b3ac37753867d73508d24 Mon Sep 17 00:00:00 2001 From: an-lee Date: Sun, 13 Oct 2024 10:10:25 +0800 Subject: [PATCH] Feat: Mention agent in chat (#1118) * mention UI * fix agent update * create message with mentions * mention agent to reply * fix chat hooks * refactor group prompt * improve UI * refactor Sentence widget --- enjoy/src/constants/index.ts | 7 +- enjoy/src/i18n/en.json | 7 +- enjoy/src/i18n/zh-CN.json | 7 +- .../main/db/handlers/chat-messages-handler.ts | 5 +- enjoy/src/main/db/models/chat-member.ts | 13 +- enjoy/src/main/db/models/chat-message.ts | 1 + .../components/chats/chat-agent-card.tsx | 2 +- .../components/chats/chat-agent-form.tsx | 27 +- .../renderer/components/chats/chat-input.tsx | 172 +++++--- .../components/chats/chat-member-form.tsx | 27 +- .../components/chats/chat-mentioning.tsx | 88 ++++ .../components/chats/chat-session.tsx | 2 +- .../chats/chat-suggestion-button.tsx | 4 +- enjoy/src/renderer/components/chats/index.ts | 1 + .../renderer/components/widgets/sentence.tsx | 6 +- .../context/app-settings-provider.tsx | 7 +- .../context/chat-session-provider.tsx | 68 ++- .../src/renderer/context/copilot-provider.tsx | 3 +- enjoy/src/renderer/hooks/use-chat-message.tsx | 386 ------------------ enjoy/src/renderer/hooks/use-chat-session.tsx | 86 ++-- enjoy/src/types/chat.d.ts | 6 +- 21 files changed, 405 insertions(+), 520 deletions(-) create mode 100644 enjoy/src/renderer/components/chats/chat-mentioning.tsx delete mode 100644 enjoy/src/renderer/hooks/use-chat-message.tsx diff --git a/enjoy/src/constants/index.ts b/enjoy/src/constants/index.ts index e47e0b64..a211f206 100644 --- a/enjoy/src/constants/index.ts +++ b/enjoy/src/constants/index.ts @@ -60,11 +60,8 @@ export const NOT_SUPPORT_JSON_FORMAT_MODELS = [ export const CHAT_GROUP_PROMPT_TEMPLATE = `You are {name} in this chat. You should reply to everyone in this chat and always stay in character. -[Chat History] -{history} - -Return reply as {name}. -`; +[Recent Chat History] +{history}`; export const DEFAULT_GPT_CONFIG = { model: "gpt-4o", diff --git a/enjoy/src/i18n/en.json b/enjoy/src/i18n/en.json index c039890a..5556aa1f 100644 --- a/enjoy/src/i18n/en.json +++ b/enjoy/src/i18n/en.json @@ -866,5 +866,10 @@ "migrateToChat": "Migrate to chat", "memberJoined": "{{name}} has joined the chat.", "memberLeft": "{{name}} has left the chat.", - "templates": "templates" + "templates": "templates", + "askAgentToReply": "Ask @{{name}} to reply", + "inviteAgentInChatAndReply": "Invite @{{name}} into the chat and reply", + "chatInputPlaceholder": "Type @ to mention agents, press Enter to send", + "replyOnlyWhenMentioned": "Reply only when mentioned", + "replyOnlyWhenMentionedDescription": "Will only reply when you mention this agent using @" } diff --git a/enjoy/src/i18n/zh-CN.json b/enjoy/src/i18n/zh-CN.json index 79dc3fc6..d4e571e3 100644 --- a/enjoy/src/i18n/zh-CN.json +++ b/enjoy/src/i18n/zh-CN.json @@ -866,5 +866,10 @@ "migrateToChat": "迁移到聊天", "memberJoined": "{{name}} 已加入聊天", "memberLeft": "{{name}} 已离开聊天", - "templates": "模板" + "templates": "模板", + "askAgentToReply": "请 @{{name}} 回复", + "inviteAgentInChatAndReply": "邀请 @{{name}} 加入聊天并回复", + "chatInputPlaceholder": "输入 @ 指定智能体回答,按回车发送", + "replyOnlyWhenMentioned": "仅在提及时回复", + "replyOnlyWhenMentionedDescription": "通过 @ 指定智能体时才会回复" } diff --git a/enjoy/src/main/db/handlers/chat-messages-handler.ts b/enjoy/src/main/db/handlers/chat-messages-handler.ts index 5320535e..16f8527d 100644 --- a/enjoy/src/main/db/handlers/chat-messages-handler.ts +++ b/enjoy/src/main/db/handlers/chat-messages-handler.ts @@ -23,7 +23,6 @@ class ChatMessagesHandler { }; } const chatMessages = await ChatMessage.findAll({ - order: [["createdAt", "DESC"]], where, ...options, }); @@ -47,7 +46,9 @@ class ChatMessagesHandler { private async create( _event: IpcMainEvent, - data: Partial> & { recordingUrl?: string } + data: Partial> & { + recordingUrl?: string; + } ) { const { recordingUrl } = data; delete data.recordingUrl; diff --git a/enjoy/src/main/db/models/chat-member.ts b/enjoy/src/main/db/models/chat-member.ts index c7be4c82..2eb45a16 100644 --- a/enjoy/src/main/db/models/chat-member.ts +++ b/enjoy/src/main/db/models/chat-member.ts @@ -1,7 +1,6 @@ import { AfterUpdate, AfterDestroy, - BeforeDestroy, BelongsTo, Table, Column, @@ -74,22 +73,20 @@ export class ChatMember extends Model { @AfterCreate static async updateChats(member: ChatMember) { - const agent = await ChatAgent.findByPk(member.userId); - if (agent) { - agent.changed("updatedAt", true); - agent.update({ updatedAt: new Date() }, { hooks: false }); - } - const chat = await Chat.findByPk(member.chatId); if (chat) { chat.changed("updatedAt", true); - chat.update({ updatedAt: new Date() }, { hooks: false }); + chat.update({ updatedAt: new Date() }); } } @AfterCreate static async chatSystemAddedMessage(member: ChatMember) { const chatAgent = await ChatAgent.findByPk(member.userId); + if (!chatAgent) return; + chatAgent.changed("updatedAt", true); + chatAgent.update({ updatedAt: new Date() }); + ChatMessage.create({ chatId: member.chatId, content: `${chatAgent.name} has joined the chat.`, diff --git a/enjoy/src/main/db/models/chat-message.ts b/enjoy/src/main/db/models/chat-message.ts index 38b483dc..30ad3837 100644 --- a/enjoy/src/main/db/models/chat-message.ts +++ b/enjoy/src/main/db/models/chat-message.ts @@ -88,6 +88,7 @@ export class ChatMessage extends Model { @Column(DataType.UUID) agentId: string | null; + @Default([]) @Column(DataType.JSON) mentions: string[]; diff --git a/enjoy/src/renderer/components/chats/chat-agent-card.tsx b/enjoy/src/renderer/components/chats/chat-agent-card.tsx index 5b088cc7..1c7fb8d3 100644 --- a/enjoy/src/renderer/components/chats/chat-agent-card.tsx +++ b/enjoy/src/renderer/components/chats/chat-agent-card.tsx @@ -13,7 +13,7 @@ import { EllipsisIcon } from "lucide-react"; export const ChatAgentCard = (props: { chatAgent: ChatAgentType; - selected: boolean; + selected?: boolean; onSelect: (chatAgent: ChatAgentType) => void; onEdit?: (chatAgent: ChatAgentType) => void; onDelete?: (chatAgent: ChatAgentType) => void; diff --git a/enjoy/src/renderer/components/chats/chat-agent-form.tsx b/enjoy/src/renderer/components/chats/chat-agent-form.tsx index e4f3eabf..c7245c3d 100644 --- a/enjoy/src/renderer/components/chats/chat-agent-form.tsx +++ b/enjoy/src/renderer/components/chats/chat-agent-form.tsx @@ -86,11 +86,18 @@ export const ChatAgentForm = (props: { const form = useForm>({ resolver: zodResolver(agentFormSchema), - values: agent || { - type: ChatAgentTypeEnum.GPT, - name: "", - description: "", - }, + values: agent + ? { + type: agent.type, + name: agent.name, + description: agent.description, + config: agent.config, + } + : { + type: ChatAgentTypeEnum.GPT, + name: "", + description: "", + }, }); const onSubmit = form.handleSubmit((data) => { @@ -155,7 +162,7 @@ export const ChatAgentForm = (props: { ...templates, ]; - useEffect(() => { + const applyTemplate = () => { if (form.watch("type") !== ChatAgentTypeEnum.GPT) { form.setValue("name", ""); form.setValue("config.prompt", ""); @@ -172,6 +179,12 @@ export const ChatAgentForm = (props: { } form.setValue("config.prompt", template.prompt || ""); } + }; + + useEffect(() => { + if (agent && selectedTemplate === "custom") return; + + applyTemplate(); }, [selectedTemplate, form.watch("type")]); useEffect(() => { @@ -257,7 +270,7 @@ export const ChatAgentForm = (props: { - + { askAgent, createMessage, shadowing, + chatAgents, } = useContext(ChatSessionProviderContext); const { EnjoyApp } = useContext(AppSettingsProviderContext); const inputRef = useRef(null); @@ -46,6 +47,7 @@ export const ChatInput = () => { const [inputMode, setInputMode] = useState<"text" | "audio">("text"); const [content, setContent] = useState(""); const { currentHotkeys } = useContext(HotKeysSettingsProviderContext); + const [mentioned, setMentioned] = useState([]); useEffect(() => { if (!inputRef.current) return; @@ -183,75 +185,121 @@ export const ChatInput = () => { if (inputMode === "text") { return ( -
- -