Skip to content

Commit

Permalink
✨ feat: 先使用最简单的方式迁移插件服务端
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Aug 16, 2023
1 parent d8bb922 commit eaf26bd
Show file tree
Hide file tree
Showing 16 changed files with 422 additions and 3 deletions.
3 changes: 2 additions & 1 deletion api/v1/runner.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PluginsMap } from '../../plugins';
import { OpenAIPluginPayload } from '../../types/plugins';

export const runtime = 'edge';
Expand All @@ -9,7 +10,7 @@ export default async (req: Request) => {

console.log(`检测到 functionCall: ${name}`);

const func = { runner: (params: any) => params };
const func = PluginsMap[name];

if (func) {
const data = JSON.parse(args);
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"last 2 versions",
"not ie <= 10"
],
"dependencies": {
"query-string": "^8"
},
"devDependencies": {
"@lobehub/lint": "latest",
"@vercel/node": "^2",
Expand Down
10 changes: 10 additions & 0 deletions plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { PluginItem } from '../types/pluginItem';
import searchEngine from './searchEngine';
import getWeather from './weather';
import webCrawler from './webCrawler';

export const PluginsMap: Record<string, PluginItem> = {
[getWeather.name]: getWeather,
[searchEngine.name]: searchEngine,
[webCrawler.name]: webCrawler,
};
27 changes: 27 additions & 0 deletions plugins/searchEngine/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PluginItem } from '../../types/pluginItem';
import runner from './runner';
import { Result } from './type';

const schema: PluginItem['schema'] = {
description: '查询搜索引擎获取信息',
name: 'searchEngine',
parameters: {
properties: {
keywords: {
description: '关键词',
type: 'string',
},
},
required: ['keywords'],
type: 'object',
},
};

const searchEngine: PluginItem<Result> = {
avatar: '🔍',
name: 'searchEngine',
runner,
schema,
};

export default searchEngine;
40 changes: 40 additions & 0 deletions plugins/searchEngine/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import querystring from 'query-string';

import { PluginRunner } from '../../types/pluginItem';
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;
82 changes: 82 additions & 0 deletions plugins/searchEngine/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
export type OrganicResults = OrganicResult[];

export interface SearchItem {
content: string;
date?: string;
displayed_link?: string;
favicon?: string;
link: string;
source?: string;
title: string;
}
export type Result = SearchItem[];

interface OrganicResult {
about_page_link: string;
about_page_serpapi_link: string;
about_this_result: AboutThisResult;
cached_page_link?: string;
date?: string;
displayed_link: string;
favicon?: string;
link: string;
position: number;
related_results?: RelatedResult[];
rich_snippet?: RichSnippet;
snippet: string;
snippet_highlighted_words?: string[];
source: string;
thumbnail?: string;
title: string;
}

interface AboutThisResult {
languages: string[];
regions: string[];
source: Source;
}

interface Source {
description: string;
icon: string;
security?: string;
source_info_link?: string;
}

interface RelatedResult {
about_page_link: string;
about_page_serpapi_link: string;
about_this_result: AboutThisResult2;
cached_page_link: string;
date: string;
displayed_link: string;
link: string;
position: number;
snippet: string;
snippet_highlighted_words: string[];
title: string;
}

interface AboutThisResult2 {
languages: string[];
regions: string[];
source: Source2;
}

interface Source2 {
description: string;
icon: string;
}

interface RichSnippet {
top: Top;
}

interface Top {
detected_extensions: DetectedExtensions;
extensions: string[];
}

interface DetectedExtensions {
month_ago: number;
}
27 changes: 27 additions & 0 deletions plugins/weather/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { PluginItem } from '../../types/pluginItem';
import runner from './runner';
import { WeatherResult } from './type';

const schema: PluginItem['schema'] = {
description: '获取当前天气情况',
name: 'realtimeWeather',
parameters: {
properties: {
city: {
description: '城市名称',
type: 'string',
},
},
required: ['city'],
type: 'object',
},
};

const getWeather: PluginItem<WeatherResult> = {
avatar: '☂️',
name: 'realtimeWeather',
runner,
schema,
};

export default getWeather;
34 changes: 34 additions & 0 deletions plugins/weather/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { PluginRunner } from '../../types/pluginItem';
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<string> => {
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<WeatherParams, WeatherResult> = 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;
36 changes: 36 additions & 0 deletions plugins/weather/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export interface WeatherParams {
city: string;
extensions?: 'base' | 'all';
}
export type WeatherResult = Forecast[];

export interface Response {
count: string;
forecasts: Forecast[];
info: string;
infocode: string;
status: string;
}

export interface Forecast {
adcode: string;
casts: Cast[];
city: string;
province: string;
reporttime: string;
}

export interface Cast {
date: string;
daypower: string;
daytemp: string;
daytemp_float: string;
dayweather: string;
daywind: string;
nightpower: string;
nighttemp: string;
nighttemp_float: string;
nightweather: string;
nightwind: string;
week: string;
}
22 changes: 22 additions & 0 deletions plugins/webCrawler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { PluginItem } from '../../types/pluginItem';
import runner from './runner';
import { Result } from './type';

const schema = {
description: '提取网页内容并总结',
name: 'websiteCrawler',
parameters: {
properties: {
url: {
description: '网页内容',
type: 'string',
},
},
required: ['url'],
type: 'object',
},
};

const getWeather: PluginItem<Result> = { avatar: '🕸', name: 'websiteCrawler', runner, schema };

export default getWeather;
45 changes: 45 additions & 0 deletions plugins/webCrawler/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { PluginRunner } from '../../types/pluginItem';
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;
35 changes: 35 additions & 0 deletions plugins/webCrawler/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export type Result = {
content: string;
title?: string;
url: string;
website?: string;
};

export interface ParserResponse {
/** author metadata */
byline: string;

/** HTML string of processed article content */
content: string;

/** content direction */
dir: string;

/** article description, or short excerpt from the content */
excerpt: string;

/** content language */
lang: string;

/** length of an article, in characters */
length: number;

/** name of the site */
siteName: string;

/** text content of the article, with all the HTML tags removed */
textContent: string;

/** article title */
title: string;
}
Loading

0 comments on commit eaf26bd

Please sign in to comment.