From e6af43e07678cdf49cbcd8784b052e6c45cb128c Mon Sep 17 00:00:00 2001 From: Simon Yang Date: Tue, 25 Apr 2023 17:09:11 +1000 Subject: [PATCH] Support Bing without an API key --- src/main.ts | 94 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/src/main.ts b/src/main.ts index e762c82..e607e1f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,7 @@ import { App, Editor, MarkdownView, normalizePath, Notice, Plugin, PluginSettingTab, Setting, loadPdfJs, requestUrl, arrayBufferToBase64, TFolder, RequestUrlParam, TAbstractFile } from 'obsidian'; import { PromptModal } from "./modal"; import { Configuration, OpenAIApi, CreateImageRequestSizeEnum } from "openai"; +import { unescape } from 'querystring'; interface AICommanderPluginSettings { model: string; @@ -30,6 +31,24 @@ const DEFAULT_SETTINGS: AICommanderPluginSettings = { promptsForPdf: '' } +interface TokenLimits { + [key: string]: number; + } + + const TOKEN_LIMITS: TokenLimits = { + 'gpt-3.5-turbo': 4096, + 'gpt-3.5-turbo-0301':4096, + 'text-davinci-003': 4097, + 'text-davinci-002': 4097, + 'code-davinci-002': 8001, + 'code-davinci-001': 8001, + 'gpt-4': 8192, + 'gpt-4-0314': 8192, + 'gpt-4-32k': 32768, + 'gpt-4-32k-0314': 32768 + } + + export default class AICommanderPlugin extends Plugin { settings: AICommanderPluginSettings; writing: boolean; @@ -73,15 +92,14 @@ export default class AICommanderPlugin extends Plugin { if (contextPrompt) { messages.push({ - role: 'user', + role: 'system', content: contextPrompt, }); } else if (this.settings.useSearchEngine) { - if (this.settings.bingSearchKey.length <= 1) throw new Error('Bing Search API Key is not provided.'); const searchResult = await this.searchText(prompt); messages.push({ - role: 'user', - content: 'As an assistant who can learn information from web search results, your task is to incorporate information from a web search result into your answers when responding to questions. Your response should include the relevant information from the web search result and provide the source markdown URL of the information. Please note that you should be able to handle various types of questions and search queries. Your response should also be clear and concise while incorporating all relevant information from the web search results. Here are the web search result: \n\n ' + JSON.stringify(searchResult), + role: 'system', + content: 'As an assistant who can learn information from web search results, your task is to incorporate information from a web search result into your answers when responding to questions. Your response should include the relevant information from your knowledge and the web search result and provide the source markdown URL of the information. Please note that you should be able to handle various types of questions and search queries. Your response should also be clear and concise while incorporating all relevant information from the web search results. Here are the web search result: \n\n ' + JSON.stringify(searchResult), }); } @@ -96,8 +114,6 @@ export default class AICommanderPlugin extends Plugin { stream: true }); - console.log(body); - const response = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', body: body, @@ -285,8 +301,63 @@ export default class AICommanderPlugin extends Plugin { else throw new Error('Error. ' + JSON.stringify(response.json)); } - async searchText(query: string) { + async htmlToMarkdown(html: string) { + const doc = new DOMParser().parseFromString(unescape(html), 'text/html'); + + const body = doc.querySelector('main'); + if (body == null) throw new Error('No search result.'); + + let markdown = body.innerHTML; + markdown = markdown.replace(/

(.*?)<\/h1>/gi, '\n# $1\n'); + markdown = markdown.replace(/

(.*?)<\/h2>/gi, '\n## $1\n'); + markdown = markdown.replace(/

(.*?)<\/h3>/gi, '\n### $1\n'); + markdown = markdown.replace(/

(.*?)<\/h4>/gi, '\n#### $1\n'); + markdown = markdown.replace(/

(.*?)<\/h5>/gi, '\n##### $1\n'); + markdown = markdown.replace(/
(.*?)<\/h6>/gi, '\n###### $1\n'); + markdown = markdown.replace(/(.*?)<\/b>/gi, '**$1**'); + markdown = markdown.replace(/(.*?)<\/i>/gi, '_$1_'); + markdown = markdown.replace(/(.*?)<\/a>/gi, '[$2]($1)'); + markdown = markdown.replace(/
    (.*?)<\/ul>/gis, (match, p1) => { + const listItems = p1.split(''); + listItems.pop(); + const markdownListItems = listItems.map((item: string) => { + const listItem = item.replace('
  • ', '- '); + return listItem.trim(); + }); + return markdownListItems.join('\n') + '\n'; + }); + markdown = markdown.replace(/

    (.*?)<\/p>/gis, '$1\n'); + markdown = markdown.replace(/
    /gi, '\n'); + markdown = markdown.replace(/


    /gi, '---'); + markdown = markdown.replace(/<\/?code>/gi, '`'); + markdown = markdown.replace(/<.*?>/g, '').trim(); + + let tokenLimit = 2048; + if (this.settings.model in TOKEN_LIMITS) tokenLimit = TOKEN_LIMITS[this.settings.model]; + + if (markdown.length > tokenLimit * 2) { + markdown = markdown.substring(0, tokenLimit * 2); + } + + return markdown; + } + + + async searchTextWithoutKey(query: string) { + const params = { + url: 'https://www.bing.com/search?q=' + encodeURIComponent(query), + method: 'GET' + }; + + const response = await requestUrl(params); + return this.htmlToMarkdown(response.text).then((markdown) => { + console.log(markdown) + return markdown; + }); + } + + async searchTextWithKey(query: string) { const params = { url: 'https://api.bing.microsoft.com/v7.0/search?q=' + encodeURIComponent(query), method: 'GET', @@ -305,6 +376,14 @@ export default class AICommanderPlugin extends Plugin { else throw new Error('No web search results: ' + JSON.stringify(response.json)); } + async searchText(query: string) { + if (this.settings.bingSearchKey.length > 1) { + return this.searchTextWithKey(query); + } else { + return this.searchTextWithoutKey(query); + } + } + async getAttachmentDir() { const activeFile = this.app.workspace.getActiveFile(); if (!activeFile) throw new Error('No active file'); @@ -709,6 +788,7 @@ class ApiSettingTab extends PluginSettingTab { })); containerEl.createEl('h2', { text: 'Search Engine' }); + containerEl.createEl('p', { text: 'You may use Bing without an API key. Use an API key to achieve the best performance.' }); new Setting(containerEl) .setName('Use search engine')