You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			272 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
		
		
			
		
	
	
			272 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
| 
											9 months ago
										 | 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; | ||
|  |                 }); | ||
|  |               }); | ||
|  |           }); | ||
|  |         }); | ||
|  |     }, | ||
|  |   }, | ||
|  | ); |