From 08357a281d4ab875bce1393354dd08c3603fff10 Mon Sep 17 00:00:00 2001 From: arvinxx Date: Wed, 16 Aug 2023 23:40:58 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=A5=20refactor:=20remove=20/api/pl?= =?UTF-8?q?ugins=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/api/plugins.api.ts | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/pages/api/plugins.api.ts diff --git a/src/pages/api/plugins.api.ts b/src/pages/api/plugins.api.ts deleted file mode 100644 index d10fa4d8b1fe2..0000000000000 --- a/src/pages/api/plugins.api.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { OpenAIPluginPayload } from '@/types/plugin'; - -import { PluginsMap } from '../../plugins'; - -export const runtime = 'edge'; - -export default async function handler(req: Request) { - const { name, arguments: args } = (await req.json()) as OpenAIPluginPayload; - - console.log(`检测到 functionCall: ${name}`); - - const func = PluginsMap[name]; - - if (func) { - const data = JSON.parse(args); - const result = await func.runner(data); - - console.log(`[${name}]`, args, `result:`, JSON.stringify(result, null, 2).slice(0, 3600)); - - return new Response(JSON.stringify(result)); - } -} From 33fecc701550b016ea625fb20cb5a8c57248842d Mon Sep 17 00:00:00 2001 From: arvinxx Date: Wed, 16 Aug 2023 23:41:56 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20refactor?= =?UTF-8?q?=20the=20plugin=20implement=20to=20separate=20the=20server=20ru?= =?UTF-8?q?nner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.mjs | 9 +++- package.json | 2 - .../ChatList/Plugins/PluginMessage.tsx | 4 +- src/plugins/Render.ts | 11 ----- src/plugins/searchEngine/index.ts | 7 ++- src/plugins/searchEngine/runner.ts | 41 ----------------- src/plugins/type.ts | 18 +------- src/plugins/weather/index.ts | 7 ++- src/plugins/weather/runner.ts | 35 -------------- src/plugins/webCrawler/index.ts | 5 +- src/plugins/webCrawler/runner.ts | 46 ------------------- src/services/url.ts | 2 +- 12 files changed, 19 insertions(+), 168 deletions(-) delete mode 100644 src/plugins/Render.ts delete mode 100644 src/plugins/searchEngine/runner.ts delete mode 100644 src/plugins/weather/runner.ts delete mode 100644 src/plugins/webCrawler/runner.ts diff --git a/next.config.mjs b/next.config.mjs index 6aece5e24ba7f..2e33e837942be 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -3,6 +3,10 @@ import nextPWA from 'next-pwa'; const isProd = process.env.NODE_ENV === 'production'; const API_END_PORT_URL = process.env.API_END_PORT_URL || ''; +// chat plugin market +const PLUGIN_RUNNER_BASE_URL = + process.env.PLUGIN_RUNNER_BASE_URL || 'https://lobe-chat-plugin-market.vercel.app'; + const withPWA = nextPWA({ dest: 'public', register: true, @@ -31,8 +35,9 @@ const nextConfig = { destination: `${API_END_PORT_URL}/api/openai`, }, { - source: '/api/plugins-dev', - destination: `${API_END_PORT_URL}/api/plugins`, + source: '/api/plugins', + // refs to: https://github.com/lobehub/chat-plugin-market + destination: `${PLUGIN_RUNNER_BASE_URL}/api/v1/runner`, }, ]; }, diff --git a/package.json b/package.json index 957ce4c3767d5..399e4114d89be 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "dependencies": { "@ant-design/colors": "^7", "@ant-design/icons": "^5", - "@commitlint/cli": "^17", "@emoji-mart/data": "^1", "@emoji-mart/react": "^1", "@icons-pack/react-simple-icons": "^8", @@ -86,7 +85,6 @@ "next": "13.4.7", "openai-edge": "^1", "polished": "^4", - "query-string": "^8", "react": "^18", "react-dom": "^18", "react-hotkeys-hook": "^4", diff --git a/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx b/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx index a22cd91c25deb..571e001472b4e 100644 --- a/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx +++ b/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx @@ -3,7 +3,7 @@ import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { Flexbox } from 'react-layout-kit'; -import { PluginsRender } from '@/plugins/Render'; +import { PluginsMap } from '@/plugins'; import { ChatMessage } from '@/types/chatMessage'; export interface FunctionMessageProps extends ChatMessage { @@ -13,7 +13,7 @@ export interface FunctionMessageProps extends ChatMessage { const PluginMessage = memo(({ content, name }) => { const { t } = useTranslation('plugin'); - const Render = PluginsRender[name || '']; + const Render = PluginsMap[name || '']?.render; let isJSON = true; try { diff --git a/src/plugins/Render.ts b/src/plugins/Render.ts deleted file mode 100644 index a97f4b88a57ce..0000000000000 --- a/src/plugins/Render.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PluginRender } from '@/plugins/type'; - -import searchEngine from './searchEngine'; -import SearchEngineRender from './searchEngine/Render'; -import getWeather from './weather'; -import WeatherRender from './weather/Render'; - -export const PluginsRender: Record = { - [getWeather.name]: WeatherRender, - [searchEngine.name]: SearchEngineRender, -}; diff --git a/src/plugins/searchEngine/index.ts b/src/plugins/searchEngine/index.ts index 5a7f2979a9593..7ec9087de2afa 100644 --- a/src/plugins/searchEngine/index.ts +++ b/src/plugins/searchEngine/index.ts @@ -2,8 +2,7 @@ import { ChatCompletionFunctions } from 'openai-edge/types/api'; import { PluginItem } from '@/plugins/type'; -import runner from './runner'; -import { Result } from './type'; +import render from './Render'; const schema: ChatCompletionFunctions = { description: '查询搜索引擎获取信息', @@ -20,10 +19,10 @@ const schema: ChatCompletionFunctions = { }, }; -const searchEngine: PluginItem = { +const searchEngine: PluginItem = { avatar: '🔍', name: 'searchEngine', - runner, + render, schema, }; diff --git a/src/plugins/searchEngine/runner.ts b/src/plugins/searchEngine/runner.ts deleted file mode 100644 index eb2a23105787a..0000000000000 --- a/src/plugins/searchEngine/runner.ts +++ /dev/null @@ -1,41 +0,0 @@ -import querystring from 'query-string'; - -import { PluginRunner } from '@/plugins/type'; - -import { OrganicResults, Result } from './type'; - -const BASE_URL = 'https://serpapi.com/search'; - -const API_KEY = process.env.SERPAI_API_KEY; - -const fetchResult: PluginRunner<{ keywords: string }, Result> = async ({ keywords }) => { - const params = { - api_key: API_KEY, - engine: 'google', - gl: 'cn', - google_domain: 'google.com', - hl: 'zh-cn', - location: 'China', - q: keywords, - }; - - const query = querystring.stringify(params); - - const res = await fetch(`${BASE_URL}?${query}`); - - const data = await res.json(); - - const results = data.organic_results as OrganicResults; - - return results.map((r) => ({ - content: r.snippet, - date: r.date, - displayed_link: r.displayed_link, - favicon: r.favicon, - link: r.link, - source: r.source, - title: r.title, - })); -}; - -export default fetchResult; diff --git a/src/plugins/type.ts b/src/plugins/type.ts index 375b0f86cce59..f96747d561bc0 100644 --- a/src/plugins/type.ts +++ b/src/plugins/type.ts @@ -6,7 +6,7 @@ import { ReactNode } from 'react'; * @template Result - 结果类型,默认为 any * @template RunnerParams - 运行参数类型,默认为 any */ -export interface PluginItem { +export interface PluginItem { /** * 头像 */ @@ -15,12 +15,7 @@ export interface PluginItem { * 名称 */ name: string; - /** - * 运行器 - * @param params - 运行参数 - * @returns 运行结果的 Promise - */ - runner: PluginRunner; + render?: PluginRender; /** * 聊天完成函数的模式 */ @@ -34,15 +29,6 @@ export interface PluginItem { */ export type PluginRender = (props: PluginRenderProps) => ReactNode; -/** - * 插件运行器 - * @template Params - 参数类型,默认为 object - * @template Result - 结果类型,默认为 any - * @param params - 运行参数 - * @returns 运行结果的 Promise - */ -export type PluginRunner = (params: Params) => Promise; - /** * 插件渲染属性 * @template Result - 结果类型,默认为 any diff --git a/src/plugins/weather/index.ts b/src/plugins/weather/index.ts index 1098725eceeff..4505f6d02188d 100644 --- a/src/plugins/weather/index.ts +++ b/src/plugins/weather/index.ts @@ -1,7 +1,6 @@ import { PluginItem } from '@/plugins/type'; -import { WeatherResult } from '@/plugins/weather/type'; -import runner from './runner'; +import render from './Render'; const schema = { description: '获取当前天气情况', @@ -18,10 +17,10 @@ const schema = { }, }; -const getWeather: PluginItem = { +const getWeather: PluginItem = { avatar: '☂️', name: 'realtimeWeather', - runner, + render, schema, }; diff --git a/src/plugins/weather/runner.ts b/src/plugins/weather/runner.ts deleted file mode 100644 index eea9c82993f22..0000000000000 --- a/src/plugins/weather/runner.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { PluginRunner } from '@/plugins/type'; - -import { Response, WeatherParams, WeatherResult } from './type'; - -const weatherBaseURL = 'https://restapi.amap.com/v3/weather/weatherInfo'; - -const citySearchURL = 'https://restapi.amap.com/v3/config/district'; - -const KEY = process.env.GAODE_WEATHER_KEY; - -const fetchCityCode = async (keywords: string): Promise => { - const URL = `${citySearchURL}?keywords=${keywords}&subdistrict=0&extensions=base&key=${KEY}`; - const res = await fetch(URL); - - const data = await res.json(); - console.log(data); - - return data.districts[0].adcode; -}; - -const fetchWeather: PluginRunner = async ({ - city, - extensions = 'all', -}) => { - const cityCode = await fetchCityCode(city); - - const URL = `${weatherBaseURL}?city=${cityCode}&extensions=${extensions}&key=${KEY}`; - const res = await fetch(URL); - - const data: Response = await res.json(); - - return data.forecasts; -}; - -export default fetchWeather; diff --git a/src/plugins/webCrawler/index.ts b/src/plugins/webCrawler/index.ts index 02024f68619a0..df0bd0f031de8 100644 --- a/src/plugins/webCrawler/index.ts +++ b/src/plugins/webCrawler/index.ts @@ -1,8 +1,5 @@ import { PluginItem } from '@/plugins/type'; -import runner from './runner'; -import { Result } from './type'; - const schema = { description: '提取网页内容并总结', name: 'websiteCrawler', @@ -18,6 +15,6 @@ const schema = { }, }; -const getWeather: PluginItem = { avatar: '🕸', name: 'websiteCrawler', runner, schema }; +const getWeather: PluginItem = { avatar: '🕸', name: 'websiteCrawler', schema }; export default getWeather; diff --git a/src/plugins/webCrawler/runner.ts b/src/plugins/webCrawler/runner.ts deleted file mode 100644 index 25bbbabaf9951..0000000000000 --- a/src/plugins/webCrawler/runner.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { PluginRunner } from '@/plugins/type'; - -import { ParserResponse, Result } from './type'; - -const BASE_URL = process.env.BROWSERLESS_URL ?? 'https://chrome.browserless.io'; -const BROWSERLESS_TOKEN = process.env.BROWSERLESS_TOKEN; - -// service from: https://github.com/lobehub/html-parser/tree/master -const HTML_PARSER_URL = process.env.HTML_PARSER_URL; - -const runner: PluginRunner<{ url: string }, Result> = async ({ url }) => { - const input = { - gotoOptions: { waitUntil: 'networkidle2' }, - url, - }; - - try { - const res = await fetch(`${BASE_URL}/content?token=${BROWSERLESS_TOKEN}`, { - body: JSON.stringify(input), - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - }); - const html = await res.text(); - - const parserBody = { html, url }; - - const parseRes = await fetch(`${HTML_PARSER_URL}`, { - body: JSON.stringify(parserBody), - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - }); - - const { title, textContent, siteName } = (await parseRes.json()) as ParserResponse; - - return { content: textContent, title, url, website: siteName }; - } catch (error) { - console.error(error); - return { content: '抓取失败', errorMessage: (error as any).message, url }; - } -}; - -export default runner; diff --git a/src/services/url.ts b/src/services/url.ts index f18b774bd92ca..b813c13598820 100644 --- a/src/services/url.ts +++ b/src/services/url.ts @@ -4,5 +4,5 @@ const prefix = isDev ? '-dev' : ''; export const URLS = { openai: '/api/openai' + prefix, - plugins: '/api/plugins' + prefix, + plugins: '/api/plugins', }; From df369c8f1997086427546346a6df654fb4c477e3 Mon Sep 17 00:00:00 2001 From: arvinxx Date: Wed, 16 Aug 2023 23:47:32 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=90=9B=20fix:=20fix=20serverless=20co?= =?UTF-8?q?mpiler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Conversation/ChatList/Plugins/PluginMessage.tsx | 4 ++-- src/plugins/Render.ts | 11 +++++++++++ src/plugins/searchEngine/index.ts | 3 --- src/plugins/weather/index.ts | 3 --- 4 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 src/plugins/Render.ts diff --git a/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx b/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx index 571e001472b4e..a22cd91c25deb 100644 --- a/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx +++ b/src/pages/chat/features/Conversation/ChatList/Plugins/PluginMessage.tsx @@ -3,7 +3,7 @@ import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { Flexbox } from 'react-layout-kit'; -import { PluginsMap } from '@/plugins'; +import { PluginsRender } from '@/plugins/Render'; import { ChatMessage } from '@/types/chatMessage'; export interface FunctionMessageProps extends ChatMessage { @@ -13,7 +13,7 @@ export interface FunctionMessageProps extends ChatMessage { const PluginMessage = memo(({ content, name }) => { const { t } = useTranslation('plugin'); - const Render = PluginsMap[name || '']?.render; + const Render = PluginsRender[name || '']; let isJSON = true; try { diff --git a/src/plugins/Render.ts b/src/plugins/Render.ts new file mode 100644 index 0000000000000..a97f4b88a57ce --- /dev/null +++ b/src/plugins/Render.ts @@ -0,0 +1,11 @@ +import { PluginRender } from '@/plugins/type'; + +import searchEngine from './searchEngine'; +import SearchEngineRender from './searchEngine/Render'; +import getWeather from './weather'; +import WeatherRender from './weather/Render'; + +export const PluginsRender: Record = { + [getWeather.name]: WeatherRender, + [searchEngine.name]: SearchEngineRender, +}; diff --git a/src/plugins/searchEngine/index.ts b/src/plugins/searchEngine/index.ts index 7ec9087de2afa..87d8843fa57c0 100644 --- a/src/plugins/searchEngine/index.ts +++ b/src/plugins/searchEngine/index.ts @@ -2,8 +2,6 @@ import { ChatCompletionFunctions } from 'openai-edge/types/api'; import { PluginItem } from '@/plugins/type'; -import render from './Render'; - const schema: ChatCompletionFunctions = { description: '查询搜索引擎获取信息', name: 'searchEngine', @@ -22,7 +20,6 @@ const schema: ChatCompletionFunctions = { const searchEngine: PluginItem = { avatar: '🔍', name: 'searchEngine', - render, schema, }; diff --git a/src/plugins/weather/index.ts b/src/plugins/weather/index.ts index 4505f6d02188d..a8164f9ca63d6 100644 --- a/src/plugins/weather/index.ts +++ b/src/plugins/weather/index.ts @@ -1,7 +1,5 @@ import { PluginItem } from '@/plugins/type'; -import render from './Render'; - const schema = { description: '获取当前天气情况', name: 'realtimeWeather', @@ -20,7 +18,6 @@ const schema = { const getWeather: PluginItem = { avatar: '☂️', name: 'realtimeWeather', - render, schema, };