267 lines
7.4 KiB
TypeScript
267 lines
7.4 KiB
TypeScript
import OpenAPIClientAxios from "openapi-client-axios";
|
|
import { StoreKey } from "../constant";
|
|
import { nanoid } from "nanoid";
|
|
import { createPersistStore } from "../utils/store";
|
|
import { getClientConfig } from "../config/client";
|
|
import yaml from "js-yaml";
|
|
import { adapter } from "../utils";
|
|
import { useAccessStore } from "./access";
|
|
|
|
const isApp = getClientConfig()?.buildMode === "export";
|
|
|
|
export type Plugin = {
|
|
id: string;
|
|
createdAt: number;
|
|
title: string;
|
|
version: string;
|
|
content: string;
|
|
builtin: boolean;
|
|
authType?: string;
|
|
authLocation?: string;
|
|
authHeader?: string;
|
|
authToken?: string;
|
|
};
|
|
|
|
export type FunctionToolItem = {
|
|
type: string;
|
|
function: {
|
|
name: string;
|
|
description?: string;
|
|
parameters: Object;
|
|
};
|
|
};
|
|
|
|
type FunctionToolServiceItem = {
|
|
api: OpenAPIClientAxios;
|
|
length: number;
|
|
tools: FunctionToolItem[];
|
|
funcs: Record<string, Function>;
|
|
};
|
|
|
|
export const FunctionToolService = {
|
|
tools: {} as Record<string, FunctionToolServiceItem>,
|
|
add(plugin: Plugin, replace = false) {
|
|
if (!replace && this.tools[plugin.id]) return this.tools[plugin.id];
|
|
const headerName = (
|
|
plugin?.authType == "custom" ? plugin?.authHeader : "Authorization"
|
|
) as string;
|
|
const tokenValue =
|
|
plugin?.authType == "basic"
|
|
? `Basic ${plugin?.authToken}`
|
|
: plugin?.authType == "bearer"
|
|
? `Bearer ${plugin?.authToken}`
|
|
: plugin?.authToken;
|
|
const authLocation = plugin?.authLocation || "header";
|
|
const definition = yaml.load(plugin.content) as any;
|
|
const serverURL = definition?.servers?.[0]?.url;
|
|
const baseURL = !isApp ? "/api/proxy" : serverURL;
|
|
const headers: Record<string, string | undefined> = {
|
|
"X-Base-URL": !isApp ? serverURL : undefined,
|
|
};
|
|
if (authLocation == "header") {
|
|
headers[headerName] = tokenValue;
|
|
}
|
|
// try using openaiApiKey for Dalle3 Plugin.
|
|
if (!tokenValue && plugin.id === "dalle3") {
|
|
const openaiApiKey = useAccessStore.getState().openaiApiKey;
|
|
if (openaiApiKey) {
|
|
headers[headerName] = `Bearer ${openaiApiKey}`;
|
|
}
|
|
}
|
|
const api = new OpenAPIClientAxios({
|
|
definition: yaml.load(plugin.content) as any,
|
|
axiosConfigDefaults: {
|
|
adapter: (window.__TAURI__ ? adapter : ["xhr"]) as any,
|
|
baseURL,
|
|
headers,
|
|
},
|
|
});
|
|
try {
|
|
api.initSync();
|
|
} catch (e) {}
|
|
const operations = api.getOperations();
|
|
return (this.tools[plugin.id] = {
|
|
api,
|
|
length: operations.length,
|
|
tools: operations.map((o) => {
|
|
// @ts-ignore
|
|
const parameters = o?.requestBody?.content["application/json"]
|
|
?.schema || {
|
|
type: "object",
|
|
properties: {},
|
|
};
|
|
if (!parameters["required"]) {
|
|
parameters["required"] = [];
|
|
}
|
|
if (o.parameters instanceof Array) {
|
|
o.parameters.forEach((p) => {
|
|
// @ts-ignore
|
|
if (p?.in == "query" || p?.in == "path") {
|
|
// const name = `${p.in}__${p.name}`
|
|
// @ts-ignore
|
|
const name = p?.name;
|
|
parameters["properties"][name] = {
|
|
// @ts-ignore
|
|
type: p.schema.type,
|
|
// @ts-ignore
|
|
description: p.description,
|
|
};
|
|
// @ts-ignore
|
|
if (p.required) {
|
|
parameters["required"].push(name);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return {
|
|
type: "function",
|
|
function: {
|
|
name: o.operationId,
|
|
description: o.description || o.summary,
|
|
parameters: parameters,
|
|
},
|
|
} as FunctionToolItem;
|
|
}),
|
|
funcs: operations.reduce((s, o) => {
|
|
// @ts-ignore
|
|
s[o.operationId] = function (args) {
|
|
const parameters: Record<string, any> = {};
|
|
if (o.parameters instanceof Array) {
|
|
o.parameters.forEach((p) => {
|
|
// @ts-ignore
|
|
parameters[p?.name] = args[p?.name];
|
|
// @ts-ignore
|
|
delete args[p?.name];
|
|
});
|
|
}
|
|
if (authLocation == "query") {
|
|
parameters[headerName] = tokenValue;
|
|
} else if (authLocation == "body") {
|
|
args[headerName] = tokenValue;
|
|
}
|
|
// @ts-ignore
|
|
return api.client[o.operationId](
|
|
parameters,
|
|
args,
|
|
api.axiosConfigDefaults,
|
|
);
|
|
};
|
|
return s;
|
|
}, {}),
|
|
});
|
|
},
|
|
get(id: string) {
|
|
return this.tools[id];
|
|
},
|
|
};
|
|
|
|
export const createEmptyPlugin = () =>
|
|
({
|
|
id: nanoid(),
|
|
title: "",
|
|
version: "1.0.0",
|
|
content: "",
|
|
builtin: false,
|
|
createdAt: Date.now(),
|
|
}) as Plugin;
|
|
|
|
export const DEFAULT_PLUGIN_STATE = {
|
|
plugins: {} as Record<string, Plugin>,
|
|
};
|
|
|
|
export const usePluginStore = createPersistStore(
|
|
{ ...DEFAULT_PLUGIN_STATE },
|
|
|
|
(set, get) => ({
|
|
create(plugin?: Partial<Plugin>) {
|
|
const plugins = get().plugins;
|
|
const id = plugin?.id || nanoid();
|
|
plugins[id] = {
|
|
...createEmptyPlugin(),
|
|
...plugin,
|
|
id,
|
|
builtin: false,
|
|
};
|
|
|
|
set(() => ({ plugins }));
|
|
get().markUpdate();
|
|
|
|
return plugins[id];
|
|
},
|
|
updatePlugin(id: string, updater: (plugin: Plugin) => void) {
|
|
const plugins = get().plugins;
|
|
const plugin = plugins[id];
|
|
if (!plugin) return;
|
|
const updatePlugin = { ...plugin };
|
|
updater(updatePlugin);
|
|
plugins[id] = updatePlugin;
|
|
FunctionToolService.add(updatePlugin, true);
|
|
set(() => ({ plugins }));
|
|
get().markUpdate();
|
|
},
|
|
delete(id: string) {
|
|
const plugins = get().plugins;
|
|
delete plugins[id];
|
|
set(() => ({ plugins }));
|
|
get().markUpdate();
|
|
},
|
|
|
|
getAsTools(ids: string[]) {
|
|
const plugins = get().plugins;
|
|
const selected = (ids || [])
|
|
.map((id) => plugins[id])
|
|
.filter((i) => i)
|
|
.map((p) => FunctionToolService.add(p));
|
|
return [
|
|
// @ts-ignore
|
|
selected.reduce((s, i) => s.concat(i.tools), []),
|
|
selected.reduce((s, i) => Object.assign(s, i.funcs), {}),
|
|
];
|
|
},
|
|
get(id?: string) {
|
|
return get().plugins[id ?? 1145141919810];
|
|
},
|
|
getAll() {
|
|
return Object.values(get().plugins).sort(
|
|
(a, b) => b.createdAt - a.createdAt,
|
|
);
|
|
},
|
|
}),
|
|
{
|
|
name: StoreKey.Plugin,
|
|
version: 1,
|
|
onRehydrateStorage(state) {
|
|
console.log("onRehydrateStorage", state);
|
|
// Skip store rehydration on server side
|
|
if (typeof window === "undefined") {
|
|
return;
|
|
}
|
|
|
|
fetch("./plugins.json")
|
|
.then((res) => res.json())
|
|
.then((res) => {
|
|
Promise.all(
|
|
res.map((item: any) =>
|
|
fetch(item.schema)
|
|
.then((res) => res.text())
|
|
.then((content) => ({
|
|
...item,
|
|
content,
|
|
})),
|
|
),
|
|
).then((builtinPlugins: any) => {
|
|
builtinPlugins.forEach((item: any) => {
|
|
const plugin = state.create(item);
|
|
state.updatePlugin(plugin.id, (plugin) => {
|
|
const tool = FunctionToolService.add(plugin, true);
|
|
plugin.title = tool.api.definition.info.title;
|
|
plugin.version = tool.api.definition.info.version;
|
|
plugin.builtin = true;
|
|
});
|
|
});
|
|
});
|
|
});
|
|
},
|
|
},
|
|
);
|