Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demo support chatgpt plugins #371

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "chatbox",
"productName": "chatbox",
"version": "0.0.1",
"version": "0.4.0",
"private": true,
"description": "a cross-platform desktop client for ChatGPT API (OpenAI API)",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"package": {
"productName": "chatbox",
"version": "0.0.1"
"version": "0.4.0"
},
"tauri": {
"allowlist": {
Expand Down
60 changes: 58 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as api from './api';
import { ThemeSwitcherProvider } from './theme/ThemeSwitcher';
import { useTranslation } from "react-i18next";
import icon from './icon.png'
import { applyPlugins } from './pluginTasks';

const { useEffect, useState } = React

Expand Down Expand Up @@ -194,6 +195,55 @@ function Main() {
messageScrollRef.current = null
}

const generateWithPlugins = async (session: Session, pluginIDs: string[], promptMsgs: Message[], targetMsg: Message) => {
messageScrollRef.current = { msgId: targetMsg.id, smooth: false }

targetMsg.content = "正在收集信息..."

let pluginMsg = await applyPlugins(store.plugins, pluginIDs, promptMsgs[promptMsgs.length - 1])
if (pluginMsg) {
promptMsgs = promptMsgs.slice(0, promptMsgs.length - 2).concat(pluginMsg, promptMsgs[promptMsgs.length - 1])
}

targetMsg.content = "正在生成结果..."

await client.replay(
store.settings.openaiKey,
store.settings.apiHost,
store.settings.maxContextSize,
store.settings.maxTokens,
session.model,
promptMsgs,
({ text, cancel }) => {
for (let i = 0; i < session.messages.length; i++) {
if (session.messages[i].id === targetMsg.id) {
session.messages[i] = {
...session.messages[i],
content: text,
cancel,
}

break;
}
}
store.updateChatSession(session)
},
(err) => {
for (let i = 0; i < session.messages.length; i++) {
if (session.messages[i].id === targetMsg.id) {
session.messages[i] = {
...session.messages[i],
content: t('api request failed:') + ' \n```\n' + err.message + '\n```',
}
break
}
}
store.updateChatSession(session)
}
)
messageScrollRef.current = null
}

const [messageInput, setMessageInput] = useState('')
useEffect(() => {
document.getElementById('message-input')?.focus() // better way?
Expand Down Expand Up @@ -430,7 +480,13 @@ function Main() {
const newAssistantMsg = createMessage('assistant', '....')
store.currentSession.messages = [...store.currentSession.messages, newUserMsg, newAssistantMsg]
store.updateChatSession(store.currentSession)
generate(store.currentSession, promptsMsgs, newAssistantMsg)

if (store.currentSession.pluginIDs && store.currentSession.pluginIDs.length>0) {
generateWithPlugins(store.currentSession, store.currentSession.pluginIDs, promptsMsgs, newAssistantMsg)
} else {
generate(store.currentSession, promptsMsgs, newAssistantMsg)
}

messageScrollRef.current = { msgId: newAssistantMsg.id, smooth: true }
} else {
store.currentSession.messages = [...store.currentSession.messages, newUserMsg]
Expand All @@ -456,7 +512,7 @@ function Main() {
<ChatConfigWindow open={configureChatConfig !== null}
session={configureChatConfig}
save={(session) => {
store.updateChatSession(session)
store.updateChatSessionPlugins(session)
setConfigureChatConfig(null)
}}
close={() => setConfigureChatConfig(null)}
Expand Down
77 changes: 74 additions & 3 deletions src/ChatConfigWindow.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
import React from 'react';
import {
Button, Dialog, DialogContent, DialogActions, DialogTitle, DialogContentText, TextField,
OutlinedInput,InputLabel,Chip,FormControl,Select,Box,MenuItem
} from '@mui/material';
import { Session } from './types'
import { SelectChangeEvent } from '@mui/material/Select';
import { Theme, useTheme } from '@mui/material/styles';
import useStore from './store'
import { Session, Plugin } from './types'
import { useTranslation } from "react-i18next";

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250,
},
},
};

const { useEffect } = React

function getStyles(name: string, pluginIDs: readonly string[], theme: Theme) {
return {
fontWeight:
pluginIDs.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}

interface Props {
open: boolean
session: Session
Expand All @@ -15,13 +39,29 @@ interface Props {
}

export default function ChatConfigWindow(props: Props) {
const theme = useTheme();
const { t } = useTranslation()
const { plugins } = useStore()

const [dataEdit, setDataEdit] = React.useState<Session>(props.session);

useEffect(() => {
setDataEdit(props.session)
}, [props.session])

const [pluginIDs, setPluginIDs] = React.useState<string[]>(props.session.pluginIDs?props.session.pluginIDs:[]);

const handleChange = (event: SelectChangeEvent<typeof pluginIDs>) => {
const {
target: { value },
} = event;

setPluginIDs(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value,
);
};

const onCancel = () => {
props.close()
setDataEdit(props.session)
Expand All @@ -32,14 +72,16 @@ export default function ChatConfigWindow(props: Props) {
dataEdit.name = props.session.name
}
dataEdit.name = dataEdit.name.trim()
dataEdit.pluginIDs = pluginIDs

props.save(dataEdit)
props.close()
}

return (
<Dialog open={props.open} onClose={onCancel}>
<DialogTitle>{t('rename')}</DialogTitle>
<DialogContent>
<DialogTitle>{t('edit session')}</DialogTitle>
<DialogContent sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<DialogContentText>
</DialogContentText>
<TextField
Expand All @@ -52,6 +94,35 @@ export default function ChatConfigWindow(props: Props) {
value={dataEdit.name}
onChange={(e) => setDataEdit({ ...dataEdit, name: e.target.value })}
/>
<FormControl fullWidth>
<InputLabel id="plugins_label">Plugins</InputLabel>
<Select
labelId="plugins_checkbox_label"
id="plugins_checkbox"
multiple
value={pluginIDs}
onChange={handleChange}
input={<OutlinedInput label="Plugins" />}
renderValue={(selected:string[]) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value: string) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{plugins.map((plugin: Plugin) => (
<MenuItem
key={plugin.id}
value={plugin.id}
style={getStyles(plugin.id, pluginIDs, theme)}
>
{plugin.name_for_human}
</MenuItem>
))}
</Select>
</FormControl>
</DialogContent>
<DialogActions>
<Button onClick={onCancel}>{t('cancel')}</Button>
Expand Down
4 changes: 2 additions & 2 deletions src/SessionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default function SessionItem(props: Props) {
</ListItemIcon>
<ListItemText>
<Typography variant="inherit" noWrap>
{session.name}
{session.name}{(session.pluginIDs && session.pluginIDs.length > 0) ? "^": ""}
</Typography>
</ListItemText>
{
Expand All @@ -86,7 +86,7 @@ export default function SessionItem(props: Props) {
handleClose()
}} disableRipple>
<EditIcon />
{t('rename')}
{t('edit session')}
</MenuItem>

<MenuItem key={session.id + 'copy'} onClick={() => {
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"[Enter] send, [Shift+Enter] line break, [Ctrl+Enter] send without generating": "[Enter] send, [Shift+Enter] line break, [Ctrl+Enter] send without generating",
"version": "Version",
"rename": "Rename",
"edit session": "Edit Session",
"name": "Name",
"clean": "Clean",
"this action will permanently delete all non-system messages in": "This action will permanently delete all non-system messages in",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh-Hans/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"[Enter] send, [Shift+Enter] line break, [Ctrl+Enter] send without generating": "[回车键] 发送,[Shift+回车键] 换行, [Ctrl+回车键] 发送但不生成",
"version": "版本",
"rename": "重命名",
"edit session": "编辑会话",
"name": "名称",
"clean": "清空",
"this action will permanently delete all non-system messages in": "此操作将永久删除",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh-Hant/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"[Enter] send, [Shift+Enter] line break, [Ctrl+Enter] send without generating": "[Enter] 傳送、[Shift+Enter] 換行、[Ctrl+Enter] 傳送但不產生",
"version": "版本",
"rename": "改名",
"edit session": "編輯對話",
"name": "名稱",
"clean": "清除",
"this action will permanently delete all non-system messages in": "此動作將永久刪除",
Expand Down
88 changes: 88 additions & 0 deletions src/pluginTasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Message, createMessage, Plugin } from './types';

export interface QueryResult {
results: Array<{
query: string;
results: Array<{
id: string;
text: string;
metadata: {
source: string;
source_id: string;
url: string;
created_at: string;
author: string;
document_id: string;
};
embedding: number[];
score: number;
}>;
}>;
}

export async function applyPlugins(
plugins: Plugin[],
pluginIDs: string[],
userMsg: Message
): Promise<Message> {
let pluginMsg = createMessage("system", "")
pluginMsg.tags = ["plugin_content"]

console.log("applyPlugins plugins:", plugins, "pluginIDs:", pluginIDs)

let ret = plugins.filter((plugin: Plugin) => {return pluginIDs.indexOf(plugin.id) >= 0})
console.log("applyPlugins return:", ret)

if (ret && ret.length>0) {
let content = await applyPlugin(ret[0], userMsg)
if (content) {
pluginMsg.content = `可以参考插件获取的如下内容回复用户问题,内容如下:${content}`
}
}

console.log("pluginMsg:", pluginMsg)

return pluginMsg
}

export async function applyPlugin(
plugin: Plugin,
userMsg: Message
): Promise<string> {
try {
const response = await fetch(`${plugin.api.url}/sub/query`, {
method: 'POST',
headers: {
'Authorization': `${plugin.auth.authorization_type} ${plugin.auth.authorization_token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"queries": [
{
"query": userMsg.content,
"top_k": 5
}
]
}),
});

if (!response.body) {
throw new Error('No response body')
}

let msgContent = '';

const result = await response.json() as QueryResult
console.log("query result:", result)

if (result && result.results) {
for (let chunk of result.results[0].results) {
msgContent += chunk.text + "\n"
}
}

return msgContent
} catch (error) {
throw error
}
}
Loading