This commit is contained in:
2026-04-25 16:36:34 +08:00
commit db90e7579b
1876 changed files with 189777 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

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

View File

@@ -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"
}
}

View File

@@ -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} />
)}
/>
);
},
});

View File

@@ -0,0 +1,178 @@
import React from 'react';
import { Translate } from './translate';
import {
useAsyncRequest,
useConverseMessageContext,
getCachedUserInfo,
getMessageTextDecorators,
} from '@capital/common';
import {
LoadingSpinner,
useChatInputActionContext,
Tag,
Button,
Divider,
} from '@capital/component';
import axios from 'axios';
import styled from 'styled-components';
import {
improveTextPrompt,
longerTextPrompt,
shorterTextPrompt,
summaryMessagesPrompt,
translateTextPrompt,
} from './prompt';
const Root = styled.div`
padding: 0.5rem;
max-width: 300px;
`;
const Tip = styled.div`
margin-bottom: 4px;
`;
const Answer = styled.pre`
white-space: pre-wrap;
max-height: 50vh;
overflow: auto;
`;
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);
}
`;
const ActionTip = styled.div`
font-size: 12px;
opacity: 0.6;
`;
export const AssistantPopover: React.FC<{
onCompleted: () => void;
}> = React.memo((props) => {
const { messages } = useConverseMessageContext();
const { message, setMessage } = useChatInputActionContext();
const [{ loading, value }, handleCallAI] = useAsyncRequest(
async (question: string) => {
// TODO: wait for replace
const { data } = await axios.post('https://yyejoq.laf.dev/chatgpt', {
question,
});
return data;
},
[]
);
if (loading) {
return (
<Root>
<LoadingSpinner />
</Root>
);
}
return (
<Root>
<div>
{typeof value === 'object' && (
<>
{value.result ? (
<div>
<Answer>{value.answer}</Answer>
<div>
<Tag color="green">
{Translate.usage}: {value.usage}ms
</Tag>
<Button
size="small"
type="primary"
onClick={() => {
setMessage(value.answer);
props.onCompleted();
}}
>
{Translate.apply}
</Button>
</div>
</div>
) : (
<div>
<div>{Translate.serviceBusy}</div>
<Tag color="red">{Translate.callError}</Tag>
</div>
)}
<Divider />
</>
)}
</div>
<Tip>{Translate.helpMeTo}</Tip>
<ActionButton
onClick={async () => {
const plainMessages = (
await Promise.all(
[...messages]
.filter((item) => !item.hasRecall) // filter recalled message
.slice(messages.length - 30, messages.length) // get last 30 message, too much will throw error
.map(
async (item) =>
`${
(
await getCachedUserInfo(item.author)
).nickname
}: ${getMessageTextDecorators().serialize(
item.content ?? ''
)}`
)
)
).join('\n');
handleCallAI(summaryMessagesPrompt + '\n' + plainMessages);
}}
>
{Translate.summaryMessages}
</ActionButton>
{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>
</>
) : (
<ActionTip>{Translate.inputTextShowMoreActionTip}</ActionTip>
)}
</Root>
);
});
AssistantPopover.displayName = 'AssistantPopover';

View File

@@ -0,0 +1,11 @@
import { Translate } from './translate';
export const improveTextPrompt = Translate.prompt.improveText;
export const shorterTextPrompt = Translate.prompt.shorterText;
export const longerTextPrompt = Translate.prompt.longerText;
export const translateTextPrompt = Translate.prompt.translateText;
export const summaryMessagesPrompt = Translate.prompt.summaryMessages;

View File

@@ -0,0 +1,84 @@
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',
}),
inputTextShowMoreActionTip: localTrans({
'zh-CN': '或者输入内容后展示更多操作',
'en-US': 'Or input message then show more actions',
}),
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',
}),
apply: localTrans({
'zh-CN': '应用',
'en-US': 'Apply',
}),
prompt: {
improveText: localTrans({
'zh-CN':
'你是一位文字美化师,你只需要美化文字,不需要解读。现在我需要你润色我的内容并保留我的母语:',
'en-US':
"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:",
}),
shorterText: localTrans({
'zh-CN':
'你是一位文字美化师,你只需要简化文字,不需要解读。现在我需要你简化它并保留我的母语:',
'en-US':
"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:",
}),
longerText: localTrans({
'zh-CN':
'你是一位文字美化师,你只需要扩写文字,不需要解读。现在我需要你扩写它并保留我的母语:',
'en-US':
"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:",
}),
translateText: localTrans({
'zh-CN':
'你是一个负责翻译文本的程序。你的任务是根据输入的文本输出指定的目标语言。 请不要输出翻译以外的任何文本。 目标语言是英文,如果你收到的文字是英文,请翻译成中文(不需要拼音),以下是我的内容:',
'en-US':
'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:',
}),
summaryMessages: localTrans({
'zh-CN':
'你将得到一串聊天记录,希望你能够对这些记录进行摘要。要求简明扼要,以包含列表的大纲形式输出。',
'en-US':
'You will receive a chat record and we hope you can summarize it. Please provide a concise outline format that includes a list.',
}),
},
};

View File

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

View File

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