Spaces:
Paused
Paused
| 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, getOperationId } from "../utils"; | |
| import { useAccessStore } from "./access"; | |
| const isApp = getClientConfig()?.isApp !== false; | |
| 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: getOperationId(o), | |
| description: o.description || o.summary, | |
| parameters: parameters, | |
| }, | |
| } as FunctionToolItem; | |
| }), | |
| funcs: operations.reduce((s, o) => { | |
| // @ts-ignore | |
| s[getOperationId(o)] = 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 if o.operationId is null, then using o.path and o.method | |
| return api.client.paths[o.path][o.method]( | |
| 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) { | |
| // 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) => | |
| // skip get schema | |
| state.get(item.id) | |
| ? item | |
| : fetch(item.schema) | |
| .then((res) => res.text()) | |
| .then((content) => ({ | |
| ...item, | |
| content, | |
| })) | |
| .catch((e) => item), | |
| ), | |
| ).then((builtinPlugins: any) => { | |
| builtinPlugins | |
| .filter((item: any) => item?.content) | |
| .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; | |
| }); | |
| }); | |
| }); | |
| }); | |
| }, | |
| }, | |
| ); | |