From ebf61b1ce93febdd087a5465f8de7df522dd5e5c Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 30 Dec 2024 23:45:47 +0800 Subject: [PATCH] feat: plugins --- package.json | 2 +- src/main/ipc.ts | 8 ++++++ src/preload/index.d.ts | 3 +++ src/preload/index.ts | 3 +++ src/renderer/src/providers/OpenAIProvider.ts | 26 ++++++++++++++++--- .../tools/DuckDuckGoLiteSearch/function.json | 22 ++++++++++++++++ .../src/tools/DuckDuckGoLiteSearch/index.js | 23 ++++++++++++++++ yarn.lock | 10 +++---- 8 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 src/renderer/src/tools/DuckDuckGoLiteSearch/function.json create mode 100644 src/renderer/src/tools/DuckDuckGoLiteSearch/index.js diff --git a/package.json b/package.json index 11967d32ad..a777f91fea 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "@types/tinycolor2": "^1", "@vitejs/plugin-react": "^4.2.1", "antd": "^5.22.5", - "axios": "^1.7.3", + "axios": "^1.7.9", "browser-image-compression": "^2.0.2", "dayjs": "^1.11.11", "dexie": "^4.0.8", diff --git a/src/main/ipc.ts b/src/main/ipc.ts index cebbd766c2..89d72c8d3b 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -1,7 +1,9 @@ import fs from 'node:fs' import path from 'node:path' +import vm from 'node:vm' import { Shortcut, ThemeMode } from '@types' +import axios from 'axios' import { BrowserWindow, ipcMain, ProxyConfig, session, shell } from 'electron' import log from 'electron-log' @@ -154,4 +156,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle('knowledge-base:add', KnowledgeService.add) ipcMain.handle('knowledge-base:remove', KnowledgeService.remove) ipcMain.handle('knowledge-base:search', KnowledgeService.search) + + // vm + ipcMain.handle('run-js', (_, code: string) => { + const context = vm.createContext(Object.assign({ fetch: fetch, URL: URL, axios: axios }, global)) + return vm.runInContext(code, context) + }) } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index e9c24b411d..947429c3d1 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -76,6 +76,9 @@ declare global { remove: ({ uniqueId, base }: { uniqueId: string; base: KnowledgeBaseParams }) => Promise search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => Promise } + vm: { + run: (code: string) => Promise + } } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index 2e1b8e999a..e099acfb37 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -70,6 +70,9 @@ const api = { ipcRenderer.invoke('knowledge-base:remove', { uniqueId, base }), search: ({ search, base }: { search: string; base: KnowledgeBaseParams }) => ipcRenderer.invoke('knowledge-base:search', { search, base }) + }, + vm: { + run: (code: string) => ipcRenderer.invoke('run-js', code) } } diff --git a/src/renderer/src/providers/OpenAIProvider.ts b/src/renderer/src/providers/OpenAIProvider.ts index 268e2c1dc4..239ad45528 100644 --- a/src/renderer/src/providers/OpenAIProvider.ts +++ b/src/renderer/src/providers/OpenAIProvider.ts @@ -4,6 +4,8 @@ import i18n from '@renderer/i18n' import { getAssistantSettings, getDefaultModel, getTopNamingModel } from '@renderer/services/AssistantService' import { EVENT_NAMES } from '@renderer/services/EventService' import { filterContextMessages } from '@renderer/services/MessagesService' +import DuckDuckGoLiteSearch from '@renderer/tools/DuckDuckGoLiteSearch/function.json' +import DuckDuckGoLiteSearchCode from '@renderer/tools/DuckDuckGoLiteSearch/index.js?raw' import { Assistant, FileTypes, Message, Model, Provider, Suggestion } from '@renderer/types' import { removeQuotes } from '@renderer/utils' import { last, takeRight } from 'lodash' @@ -11,7 +13,8 @@ import OpenAI, { AzureOpenAI } from 'openai' import { ChatCompletionContentPart, ChatCompletionCreateParamsNonStreaming, - ChatCompletionMessageParam + ChatCompletionMessageParam, + ChatCompletionTool } from 'openai/resources' import { CompletionsParams } from '.' @@ -133,7 +136,6 @@ export default class OpenAIProvider extends BaseProvider { } const isOpenAIo1 = model.id.includes('o1-') - const isSupportStreamOutput = streamOutput let time_first_token_millsec = 0 const start_time_millsec = new Date().getTime() @@ -148,12 +150,28 @@ export default class OpenAIProvider extends BaseProvider { top_p: assistant?.settings?.topP, max_tokens: maxTokens, keep_alive: this.keepAliveTime, - stream: isSupportStreamOutput, + stream: streamOutput, + tools: [DuckDuckGoLiteSearch as ChatCompletionTool], ...this.getCustomParameters(assistant) }) - if (!isSupportStreamOutput) { + if (!streamOutput) { const time_completion_millsec = new Date().getTime() - start_time_millsec + + stream.choices[0].message?.tool_calls?.forEach(async (toolCall) => { + const functionName = toolCall.function.name + const params = toolCall.function.arguments + + console.log(functionName, DuckDuckGoLiteSearchCode) + + const result = await window.api.vm.run(` + var params = ${params}; + ${DuckDuckGoLiteSearchCode} + `) + + console.log(result) + }) + return onChunk({ text: stream.choices[0].message?.content || '', usage: stream.usage, diff --git a/src/renderer/src/tools/DuckDuckGoLiteSearch/function.json b/src/renderer/src/tools/DuckDuckGoLiteSearch/function.json new file mode 100644 index 0000000000..542e4437b6 --- /dev/null +++ b/src/renderer/src/tools/DuckDuckGoLiteSearch/function.json @@ -0,0 +1,22 @@ +{ + "type": "function", + "function": { + "name": "DuckDuckGoLiteSearch", + "description": "A search engine useful for answering questions about current events.", + "parameters": { + "type": "object", + "properties": { + "q": { + "type": "string", + "description": "Keywords for query" + }, + "kl": { + "type": "string", + "description": "Language/region code (e.g., wt-wt, us-en, uk-en)", + "default": "wt-wt" + } + }, + "required": ["q"] + } + } +} diff --git a/src/renderer/src/tools/DuckDuckGoLiteSearch/index.js b/src/renderer/src/tools/DuckDuckGoLiteSearch/index.js new file mode 100644 index 0000000000..a2b9bb0478 --- /dev/null +++ b/src/renderer/src/tools/DuckDuckGoLiteSearch/index.js @@ -0,0 +1,23 @@ +new Promise((resolve, reject) => { + async function makeRequest() { + try { + const response = await axios.request({ + method: 'post', + maxBodyLength: Infinity, + url: 'https://google.serper.dev/search', + headers: { + 'X-API-KEY': 'fa70255d0ab3402ee2ddb6455f6b317e73588fc7', + 'Content-Type': 'application/json' + }, + data: params + }) + console.log(JSON.stringify(response.data)) + resolve(response.data) + } catch (error) { + console.log(error) + reject(error.toString()) + } + } + + makeRequest() +}) diff --git a/yarn.lock b/yarn.lock index cfc3903fea..c08e624b75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2867,7 +2867,7 @@ __metadata: adm-zip: "npm:^0.5.16" antd: "npm:^5.22.5" apache-arrow: "npm:^18.1.0" - axios: "npm:^1.7.3" + axios: "npm:^1.7.9" browser-image-compression: "npm:^2.0.2" dayjs: "npm:^1.11.11" dexie: "npm:^4.0.8" @@ -3506,14 +3506,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^1.7.3": - version: 1.7.7 - resolution: "axios@npm:1.7.7" +"axios@npm:^1.7.9": + version: 1.7.9 + resolution: "axios@npm:1.7.9" dependencies: follow-redirects: "npm:^1.15.6" form-data: "npm:^4.0.0" proxy-from-env: "npm:^1.1.0" - checksum: 10c0/4499efc89e86b0b49ffddc018798de05fab26e3bf57913818266be73279a6418c3ce8f9e934c7d2d707ab8c095e837fc6c90608fb7715b94d357720b5f568af7 + checksum: 10c0/b7a41e24b59fee5f0f26c1fc844b45b17442832eb3a0fb42dd4f1430eb4abc571fe168e67913e8a1d91c993232bd1d1ab03e20e4d1fee8c6147649b576fc1b0b languageName: node linkType: hard