feat: add AI Assistant plugin for Tailchat with ChatGPT

pull/90/head
moonrailgun 2 years ago
parent eb00379a75
commit 060d07ae8e

@ -0,0 +1,9 @@
{
"label": "AI Assistant",
"name": "com.msgbyte.ai-assistant",
"url": "/plugins/com.msgbyte.ai-assistant/index.js",
"version": "0.0.0",
"author": "moonrailgun",
"description": "Add chatgpt into Tailchat",
"requireRestart": true
}

@ -0,0 +1,16 @@
{
"name": "@plugins/com.msgbyte.ai-assistant",
"main": "src/index.tsx",
"version": "0.0.0",
"description": "Add chatgpt into Tailchat",
"private": true,
"scripts": {
"sync:declaration": "tailchat declaration github"
},
"dependencies": {},
"devDependencies": {
"@types/styled-components": "^5.1.26",
"react": "18.2.0",
"styled-components": "^5.3.6"
}
}

@ -0,0 +1,22 @@
import { regChatInputButton } from '@capital/common';
import { BaseChatInputButton } from '@capital/component';
import React from 'react';
import { AssistantPopover } from './popover';
const PLUGIN_ID = 'com.msgbyte.ai-assistant';
const PLUGIN_NAME = 'AI Assistant';
console.log(`Plugin ${PLUGIN_NAME}(${PLUGIN_ID}) is loaded`);
regChatInputButton({
render: () => {
return (
<BaseChatInputButton
icon="eos-icons:ai"
popoverContent={({ hidePopover }) => (
<AssistantPopover onCompleted={hidePopover} />
)}
/>
);
},
});

@ -0,0 +1,116 @@
import React from 'react';
import { Translate } from './translate';
import { useAsyncRequest } from '@capital/common';
import {
LoadingSpinner,
useChatInputActionContext,
Tag,
} from '@capital/component';
import axios from 'axios';
import styled from 'styled-components';
import {
improveTextPrompt,
longerTextPrompt,
shorterTextPrompt,
translateTextPrompt,
} from './prompt';
const Root = styled.div`
padding: 0.5rem;
max-width: 300px;
`;
const Tip = styled.div`
margin-bottom: 4px;
`;
const ActionButton = styled.div`
min-width: 180px;
padding: 4px 6px;
border-radius: 3px;
background-color: rgba(0, 0, 0, 0.1);
cursor: pointer;
margin-bottom: 4px;
&:hover {
background-color: rgba(0, 0, 0, 0.2);
}
`;
export const AssistantPopover: React.FC<{
onCompleted: () => void;
}> = React.memo((props) => {
const { message } = useChatInputActionContext();
const [{ loading, value }, handleCallAI] = useAsyncRequest(
async (question: string) => {
// TODO: wait for replace
const { data } = await axios.post('https://uui1ik.laf.dev/chatgpt', {
question,
});
return data;
},
[]
);
if (loading) {
return (
<Root>
<LoadingSpinner />
</Root>
);
}
return (
<Root>
<Tip>{Translate.helpMeTo}</Tip>
<div>
{typeof value === 'object' &&
(value.result ? (
<div>
<div>{value.answer}</div>
<Tag color="green">
{Translate.usage}: {value.usage}ms
</Tag>
</div>
) : (
<div>
<div>{Translate.serviceBusy}</div>
<Tag color="red">{Translate.callError}</Tag>
</div>
))}
</div>
{typeof message === 'string' && message.length > 0 && (
<>
<ActionButton
onClick={() => handleCallAI(improveTextPrompt + message)}
>
{Translate.improveText}
</ActionButton>
<ActionButton
onClick={() => handleCallAI(shorterTextPrompt + message)}
>
{Translate.makeShorter}
</ActionButton>
<ActionButton
onClick={() => handleCallAI(longerTextPrompt + message)}
>
{Translate.makeLonger}
</ActionButton>
<ActionButton
onClick={() => handleCallAI(translateTextPrompt + message)}
>
{Translate.translateInputText}
</ActionButton>
</>
)}
{/* <ActionButton onClick={() => handleCallAI('')}>
{Translate.summaryMessages}
</ActionButton> */}
</Root>
);
});
AssistantPopover.displayName = 'AssistantPopover';

@ -0,0 +1,8 @@
export const improveTextPrompt =
"You are a text embellisher, you can only embellish the text, don't interpret it. Now i need you embellish it and keep my origin language:";
export const shorterTextPrompt =
"You are a text embellisher, you can only shorter the text, don't interpret it. Now i need you shorter it and keep my origin language:";
export const longerTextPrompt =
"You are a text embellisher, you can only longer the text, don't interpret it. Now i need you longer it and keep my origin language:";
export const translateTextPrompt =
'You are a program responsible for translating text. Your task is to output the specified target language based on the input text. Please do not output any text other than the translation. Target language is english, and if you receive text is english, please translate to chinese(no need pinyin), then its my text:';

@ -0,0 +1,44 @@
import { localTrans } from '@capital/common';
export const Translate = {
name: localTrans({
'zh-CN': 'AI Assistant',
'en-US': 'AI Assistant',
}),
helpMeTo: localTrans({
'zh-CN': '帮我:',
'en-US': 'Help me to:',
}),
improveText: localTrans({
'zh-CN': '改进文本',
'en-US': 'Improve Text',
}),
makeShorter: localTrans({
'zh-CN': '精简内容',
'en-US': 'Make Shorter',
}),
makeLonger: localTrans({
'zh-CN': '扩写内容',
'en-US': 'Make Longer',
}),
summaryMessages: localTrans({
'zh-CN': '总结内容',
'en-US': 'Summary Messages',
}),
translateInputText: localTrans({
'zh-CN': '翻译输入内容',
'en-US': 'Translate Input',
}),
usage: localTrans({
'zh-CN': '用时',
'en-US': 'Usage',
}),
serviceBusy: localTrans({
'zh-CN': '服务器忙,请稍后再试',
'en-US': 'Server is busy, please try again later',
}),
callError: localTrans({
'zh-CN': '调用失败',
'en-US': 'Call Error',
}),
};

@ -0,0 +1,7 @@
{
"compilerOptions": {
"esModuleInterop": true,
"jsx": "react",
"importsNotUsedAsValues": "error"
}
}

@ -0,0 +1,2 @@
declare module '@capital/common';
declare module '@capital/component';

@ -6,6 +6,8 @@ import { useShallowObject } from 'tailchat-shared';
* Input Actions * Input Actions
*/ */
export interface ChatInputActionContextProps { export interface ChatInputActionContextProps {
message: string;
setMessage: (msg: string) => void;
sendMsg: (message: string) => void; sendMsg: (message: string) => void;
appendMsg: (message: string) => void; appendMsg: (message: string) => void;
} }

@ -1,4 +1,7 @@
import { getMessageTextDecorators } from '@/plugin/common'; import {
getMessageTextDecorators,
pluginChatInputButtons,
} from '@/plugin/common';
import { isEnterHotkey } from '@/utils/hot-key'; import { isEnterHotkey } from '@/utils/hot-key';
import React, { useCallback, useRef, useState } from 'react'; import React, { useCallback, useRef, useState } from 'react';
import { ChatInputAddon } from './Addon'; import { ChatInputAddon } from './Addon';
@ -89,6 +92,8 @@ export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
return ( return (
<ChatInputActionContext.Provider <ChatInputActionContext.Provider
value={{ value={{
message,
setMessage,
sendMsg: props.onSendMsg, sendMsg: props.onSendMsg,
appendMsg: handleAppendMsg, appendMsg: handleAppendMsg,
}} }}
@ -110,6 +115,12 @@ export const ChatInputBox: React.FC<ChatInputBoxProps> = React.memo((props) => {
</div> </div>
<div className="px-2 flex space-x-1"> <div className="px-2 flex space-x-1">
{pluginChatInputButtons.map((item, i) =>
React.cloneElement(item.render(), {
key: `plugin-chatinput-btn#${i}`,
})
)}
<ChatInputEmotion /> <ChatInputEmotion />
{message ? ( {message ? (

@ -157,6 +157,12 @@ export type { ChatInputActionContextProps };
export const [pluginChatInputActions, regChatInputAction] = export const [pluginChatInputActions, regChatInputAction] =
buildRegList<ChatInputAction>(); buildRegList<ChatInputAction>();
interface ChatInputButton {
render: () => React.ReactElement;
}
export const [pluginChatInputButtons, regChatInputButton] =
buildRegList<ChatInputButton>();
export { regSocketEventListener }; export { regSocketEventListener };
/** /**

@ -13,6 +13,7 @@ export {
notification, notification,
Empty, Empty,
Popover, Popover,
Tag,
} from 'antd'; } from 'antd';
export const TextArea = Input.TextArea; export const TextArea = Input.TextArea;
export { export {
@ -31,6 +32,8 @@ export {
export { Link } from 'react-router-dom'; export { Link } from 'react-router-dom';
export { MessageAckContainer } from '@/components/ChatBox/ChatMessageList/MessageAckContainer'; export { MessageAckContainer } from '@/components/ChatBox/ChatMessageList/MessageAckContainer';
export { BaseChatInputButton } from '@/components/ChatBox/ChatInputBox/BaseChatInputButton';
export { useChatInputActionContext } from '@/components/ChatBox/ChatInputBox/context';
export { GroupExtraDataPanel } from '@/components/Panel/group/GroupExtraDataPanel'; export { GroupExtraDataPanel } from '@/components/Panel/group/GroupExtraDataPanel';
export { Image } from '@/components/Image'; export { Image } from '@/components/Image';
export { IconBtn } from '@/components/IconBtn'; export { IconBtn } from '@/components/IconBtn';

@ -951,6 +951,18 @@ importers:
specifier: ^6.5.4 specifier: ^6.5.4
version: 6.5.4(webpack@5.75.0) version: 6.5.4(webpack@5.75.0)
client/web/plugins/com.msgbyte.ai-assistant:
devDependencies:
'@types/styled-components':
specifier: ^5.1.26
version: 5.1.26
react:
specifier: 18.2.0
version: 18.2.0
styled-components:
specifier: ^5.3.6
version: 5.3.6(react-dom@18.2.0)(react-is@18.2.0)(react@18.2.0)
client/web/plugins/com.msgbyte.bbcode: client/web/plugins/com.msgbyte.bbcode:
dependencies: dependencies:
'@bbob/parser': '@bbob/parser':

Loading…
Cancel
Save