diff --git a/example.env b/example.env index a5e06cd..3c436f2 100644 --- a/example.env +++ b/example.env @@ -2,7 +2,8 @@ GLADDIS_DATA_PATH="__DATA__" GLADDIS_NAME_LABEL="Gladdis" #GLADDIS_CONFIG_FILE="Gladdis" GLADDIS_DEFAULT_USER="User" -GLADDIS_DEFAULT_MODEL="gpt-3.5-turbo" +GLADDIS_DEFAULT_MODEL="gpt-4o-mini" +#GLADDIS_DEFAULT_SERVER="http://localhost:8080/v1" GLADDIS_TEMPERATURE=0 GLADDIS_TOP_P_PARAM=100 @@ -13,6 +14,7 @@ GLADDIS_SERVER_PORT=3000 GLADDIS_WHISPER_INPUT="Gladdis" #GLADDIS_WHISPER_CONFIG="Whisper" GLADDIS_WHISPER_MODEL="whisper-1" +#GLADDIS_WHISPER_SERVER="http://localhost:8080/v1" GLADDIS_WHISPER_LIVE_SUFFIX="dictated, but not read" GLADDIS_WHISPER_READ_SUFFIX="transcribed and read" GLADDIS_WHISPER_TEMPERATURE=0 diff --git a/src/gladdis.ts b/src/gladdis.ts index e5f6cbd..da4ec52 100644 --- a/src/gladdis.ts +++ b/src/gladdis.ts @@ -71,6 +71,7 @@ export async function callGladdis(context: Context): Promise { const openai = new OpenAI({ apiKey: context.user.env.OPENAI_API_KEY, + baseURL: context.gladdis.server, dangerouslyAllowBrowser: true, }) @@ -85,14 +86,12 @@ export async function callGladdis(context: Context): Promise { presence_penalty: context.gladdis.pres_penalty / 100, }) - for await (const data of stream) { - if (data.choices[0].delta?.role === 'assistant') { - await disk.appendFile(context.file.path, `\n\n__${context.gladdis.label}:__ `) - } + await disk.appendFile(context.file.path, `\n\n__${context.gladdis.label}:__ `) - if ((data.choices[0].delta?.content ?? '') !== '') { - response.push(data.choices[0].delta?.content ?? '') - await disk.appendFile(context.file.path, data.choices[0].delta?.content ?? '') + for await (const data of stream) { + if ((data.choices[0].delta.content ?? '') !== '') { + response.push(data.choices[0].delta.content ?? '') + await disk.appendFile(context.file.path, data.choices[0].delta.content ?? '') } } } catch (error: unknown) { diff --git a/src/obsidian.ts b/src/obsidian.ts index 3362ba6..935d9b2 100755 --- a/src/obsidian.ts +++ b/src/obsidian.ts @@ -46,7 +46,9 @@ export async function getHtml(url: string): Promise { let iteration = 0 while (equalLoop < 3 && iteration < 33) { - await new Promise((resolve) => setTimeout(resolve, 33)) // Max 33 ms * 34 loops = 1.1 s + page.webContents.scrollToBottom() + + await new Promise((resolve) => setTimeout(resolve, 33)) // Max: 33 ms * 34 loops = 1.1 s const size = await page.webContents.executeJavaScript('document.body.innerText.length;') if (size === prevSize) equalLoop++ @@ -70,11 +72,15 @@ interface GladdisSettings { GLADDIS_NAME_LABEL: string GLADDIS_DEFAULT_USER: string GLADDIS_DEFAULT_MODEL: string + GLADDIS_DEFAULT_SERVER?: string GLADDIS_TEMPERATURE: string GLADDIS_TOP_P_PARAM: string GLADDIS_WHISPER_CONFIG?: string GLADDIS_WHISPER_INPUT: string GLADDIS_WHISPER_MODEL: string + GLADDIS_WHISPER_SERVER?: string + GLADDIS_WHISPER_LIVE_SUFFIX: string + GLADDIS_WHISPER_READ_SUFFIX: string GLADDIS_WHISPER_TEMPERATURE: string GLADDIS_WHISPER_ECHO_OUTPUT: string GLADDIS_WHISPER_DELETE_FILE: string @@ -84,11 +90,13 @@ const DEFAULT_SETTINGS: GladdisSettings = { GLADDIS_DATA_PATH: 'Gladdis', GLADDIS_NAME_LABEL: 'Gladdis', GLADDIS_DEFAULT_USER: 'User', - GLADDIS_DEFAULT_MODEL: 'gpt-3.5-turbo', + GLADDIS_DEFAULT_MODEL: 'gpt-4o-mini', GLADDIS_TEMPERATURE: '42', GLADDIS_TOP_P_PARAM: '100', - GLADDIS_WHISPER_INPUT: 'Hi Gladdis, please transcribe this file.', + GLADDIS_WHISPER_INPUT: 'Hi Gladdis, please transcribe this.', GLADDIS_WHISPER_MODEL: 'whisper-1', + GLADDIS_WHISPER_LIVE_SUFFIX: 'dictated, but not read', + GLADDIS_WHISPER_READ_SUFFIX: 'transcribed and read', GLADDIS_WHISPER_TEMPERATURE: '24', GLADDIS_WHISPER_ECHO_OUTPUT: 'true', GLADDIS_WHISPER_DELETE_FILE: 'false', @@ -308,7 +316,7 @@ class VaultInterface implements DiskInterface { await this.vault.createFolder(normalizePath(path)) } - baseName(path: string, ext?: string | undefined): string { + baseName(path: string, ext?: string): string { const fileName = normalizePath(path).split('/').at(-1) ?? '' const fileExt = this.extName(path).toLowerCase() @@ -365,12 +373,13 @@ class GladdisSettingTab extends PluginSettingTab { fragment.appendText('" subfolder.') }), ) - .addText((text) => + .addText((text) => { + text.inputEl.style.width = '24ch' text.setValue(this.plugin.settings.GLADDIS_DATA_PATH).onChange(async (value) => { this.plugin.settings.GLADDIS_DATA_PATH = value await this.plugin.saveSettings() - }), - ) + }) + }) new Setting(this.containerEl) .setName('OpenAI secret API key') @@ -387,7 +396,8 @@ class GladdisSettingTab extends PluginSettingTab { ) .addText((text) => { text.inputEl.type = 'password' - text.setPlaceholder('sk-***************') + text.inputEl.style.width = '24ch' + text.setPlaceholder('--OPENAI API KEY--') .setValue(this.plugin.secrets.OPENAI_API_KEY ?? '') .onChange(async (value) => { this.plugin.secrets.OPENAI_API_KEY = value @@ -401,7 +411,7 @@ class GladdisSettingTab extends PluginSettingTab { .setName('Default config file') .setDesc( createFragment((fragment) => { - fragment.appendText('The path to the default config file ("') + fragment.appendText('The path to the Gladdis config file ("') fragment.createEl('code', { text: '.md' }) fragment.appendText('" extension optional).') }), @@ -421,29 +431,44 @@ class GladdisSettingTab extends PluginSettingTab { .setName('Default LLM model') .setDesc( createFragment((fragment) => { - fragment.appendText('Only OpenAI models at the moment ("') - fragment.createEl('code', { text: 'GPT-3.5' }) - fragment.appendText('" or "') - fragment.createEl('code', { text: 'GPT-4' }) - fragment.appendText('").') + fragment.appendText('Choose an ') + fragment.createEl('a', { + href: 'https://platform.openai.com/docs/models', + text: 'OpenAI', + }) + fragment.appendText(' or ') + fragment.createEl('a', { + href: 'https://ollama.com/library', + text: 'local model', + }) + fragment.appendText(' from the ') + fragment.createEl('a', { + href: 'https://github.com/AurelienStebe/Gladdis#available-models', + text: 'documentation', + }) + fragment.appendText('.') }), ) - .addDropdown((dropdown) => - dropdown - .addOptions({ - 'gpt-4-turbo-preview': 'GPT-4 Preview (128k)', - 'gpt-4-32k': 'GPT-4 (32k)', - 'gpt-4': 'GPT-4 (8k)', - 'gpt-3.5-turbo': 'GPT-3.5 Updated (16k)', - 'gpt-3.5-turbo-16k': 'GPT-3.5 (16k)', - 'gpt-3.5-turbo-0613': 'GPT-3.5 (4k)', - }) - .setValue(this.plugin.settings.GLADDIS_DEFAULT_MODEL) + .addText((text) => + text.setValue(this.plugin.settings.GLADDIS_DEFAULT_MODEL).onChange(async (value) => { + this.plugin.settings.GLADDIS_DEFAULT_MODEL = value + await this.plugin.saveSettings() + }), + ) + + new Setting(this.containerEl) + .setName('Default LLM server') + .setDesc('The default LLM server, use it to point to a local LLM model.') + .addText((text) => { + text.inputEl.style.width = '24ch' + text.setPlaceholder('http://localhost:8080/v1') + .setValue(this.plugin.settings.GLADDIS_DEFAULT_SERVER ?? '') .onChange(async (value) => { - this.plugin.settings.GLADDIS_DEFAULT_MODEL = value + if (value === '') delete this.plugin.settings.GLADDIS_DEFAULT_SERVER + else this.plugin.settings.GLADDIS_DEFAULT_SERVER = value await this.plugin.saveSettings() - }), - ) + }) + }) new Setting(this.containerEl) .setName('Default AI label') @@ -481,7 +506,7 @@ class GladdisSettingTab extends PluginSettingTab { .setName('Default temperature') .setDesc('Value in Celsius, from 0 (freezing), 100 (boiling), to 200 (steaming).') .addSlider((slider) => { - slider.sliderEl.style.width = '100%' + slider.sliderEl.style.width = '80%' slider .setDynamicTooltip() .setLimits(0, 200, 1) @@ -496,7 +521,7 @@ class GladdisSettingTab extends PluginSettingTab { .setName('Default top P param') .setDesc('Value in literal percentage of probability mass, from 0 % to 100 %.') .addSlider((slider) => { - slider.sliderEl.style.width = '100%' + slider.sliderEl.style.width = '80%' slider .setDynamicTooltip() .setLimits(0, 100, 1) @@ -513,7 +538,7 @@ class GladdisSettingTab extends PluginSettingTab { .setName('Default config file') .setDesc( createFragment((fragment) => { - fragment.appendText('The path to the default config file ("') + fragment.appendText('The path to the Whisper config file ("') fragment.createEl('code', { text: '.md' }) fragment.appendText('" extension optional).') }), @@ -531,9 +556,10 @@ class GladdisSettingTab extends PluginSettingTab { new Setting(this.containerEl) .setName('Default audio prompt') - .setDesc('The default audio prompt, use it to spell out specific nouns or words.') + .setDesc('The default audio prompt, use it to spell out any specific words.') .addTextArea((textArea) => { - textArea.inputEl.style.width = '100%' + textArea.inputEl.style.width = '24ch' + textArea.inputEl.style.resize = 'none' textArea.setValue(this.plugin.settings.GLADDIS_WHISPER_INPUT).onChange(async (value) => { this.plugin.settings.GLADDIS_WHISPER_INPUT = value await this.plugin.saveSettings() @@ -544,26 +570,59 @@ class GladdisSettingTab extends PluginSettingTab { .setName('Default audio model') .setDesc( createFragment((fragment) => { - fragment.appendText('Only one OpenAI model is supported at the moment ("') + fragment.appendText('Only one OpenAI model available ("') fragment.createEl('code', { text: 'whisper-1' }) - fragment.appendText('").') + fragment.appendText('") or a local model.') }), ) .addText((text) => - text - .setDisabled(true) - .setValue(this.plugin.settings.GLADDIS_WHISPER_MODEL) + text.setValue(this.plugin.settings.GLADDIS_WHISPER_MODEL).onChange(async (value) => { + this.plugin.settings.GLADDIS_WHISPER_MODEL = value + await this.plugin.saveSettings() + }), + ) + + new Setting(this.containerEl) + .setName('Default audio server') + .setDesc('The default audio server, use it to point to a local Whisper model.') + .addText((text) => { + text.inputEl.style.width = '24ch' + text.setPlaceholder('http://localhost:8080/v1') + .setValue(this.plugin.settings.GLADDIS_WHISPER_SERVER ?? '') .onChange(async (value) => { - this.plugin.settings.GLADDIS_WHISPER_MODEL = value + if (value === '') delete this.plugin.settings.GLADDIS_WHISPER_SERVER + else this.plugin.settings.GLADDIS_WHISPER_SERVER = value await this.plugin.saveSettings() - }), - ) + }) + }) + + new Setting(this.containerEl) + .setName('Default "live" suffix') + .setDesc('The default suffix, inserted after "on the fly" transcriptions.') + .addText((text) => { + text.inputEl.style.width = '24ch' + text.setValue(this.plugin.settings.GLADDIS_WHISPER_LIVE_SUFFIX).onChange(async (value) => { + this.plugin.settings.GLADDIS_WHISPER_LIVE_SUFFIX = value + await this.plugin.saveSettings() + }) + }) + + new Setting(this.containerEl) + .setName('Default "read" suffix') + .setDesc('The default suffix, inserted after already seen transcriptions.') + .addText((text) => { + text.inputEl.style.width = '24ch' + text.setValue(this.plugin.settings.GLADDIS_WHISPER_READ_SUFFIX).onChange(async (value) => { + this.plugin.settings.GLADDIS_WHISPER_READ_SUFFIX = value + await this.plugin.saveSettings() + }) + }) new Setting(this.containerEl) .setName('Default temperature') .setDesc('Value in percentage (or Celsius), from 0 (freezing) to 100 (boiling).') .addSlider((slider) => { - slider.sliderEl.style.width = '100%' + slider.sliderEl.style.width = '80%' slider .setDynamicTooltip() .setLimits(0, 100, 1) diff --git a/src/types/context.ts b/src/types/context.ts index 9882b4c..b41e690 100644 --- a/src/types/context.ts +++ b/src/types/context.ts @@ -42,6 +42,7 @@ export interface Context { label: string config?: string model: string + server?: string temperature: number top_p_param: number freq_penalty: number @@ -51,6 +52,7 @@ export interface Context { input: string config?: string model: string + server?: string liveSuffix: string readSuffix: string temperature: number diff --git a/src/utils/loaders.ts b/src/utils/loaders.ts index 75f1aa4..b6442d5 100644 --- a/src/utils/loaders.ts +++ b/src/utils/loaders.ts @@ -26,7 +26,8 @@ export async function loadContext(context: Context): Promise { gladdis: { label: context.user.env.GLADDIS_NAME_LABEL ?? 'Gladdis', config: context.user.env.GLADDIS_CONFIG_FILE, - model: context.user.env.GLADDIS_DEFAULT_MODEL ?? 'gpt-3.5-turbo', + model: context.user.env.GLADDIS_DEFAULT_MODEL ?? 'gpt-4o-mini', + server: context.user.env.GLADDIS_DEFAULT_SERVER, temperature: Number(context.user.env.GLADDIS_TEMPERATURE ?? 0), top_p_param: Number(context.user.env.GLADDIS_TOP_P_PARAM ?? 100), freq_penalty: Number(context.user.env.GLADDIS_FREQ_PENALTY ?? 0), @@ -36,6 +37,7 @@ export async function loadContext(context: Context): Promise { input: context.user.env.GLADDIS_WHISPER_INPUT ?? 'Gladdis', config: context.user.env.GLADDIS_WHISPER_CONFIG, model: context.user.env.GLADDIS_WHISPER_MODEL ?? 'whisper-1', + server: context.user.env.GLADDIS_WHISPER_SERVER, liveSuffix: context.user.env.GLADDIS_WHISPER_LIVE_SUFFIX ?? 'dictated, but not read', readSuffix: context.user.env.GLADDIS_WHISPER_READ_SUFFIX ?? 'transcribed and read', temperature: Number(context.user.env.GLADDIS_WHISPER_TEMPERATURE ?? 0), diff --git a/src/utils/whisper.ts b/src/utils/whisper.ts index 5bcfc1d..70e0345 100644 --- a/src/utils/whisper.ts +++ b/src/utils/whisper.ts @@ -18,7 +18,7 @@ export async function transcribe(content: string, context: Context): Promise { const openai = new OpenAI({ apiKey: context.user.env.OPENAI_API_KEY, + baseURL: context.whisper.server, dangerouslyAllowBrowser: true, }) @@ -85,5 +86,10 @@ export async function transcription(filePath: string, context: Context): Promise language: context.whisper.language, }) + // The LocalAI API does not join segments correctly. + if ((transcription as any).segments !== undefined) { + return (transcription as any).segments.map((segment: any) => segment.text).join(' ') + } + return transcription.text } diff --git a/tests/__snapshots__/context.test.ts.snap b/tests/__snapshots__/context.test.ts.snap index 61a3fd3..5648eaf 100644 --- a/tests/__snapshots__/context.test.ts.snap +++ b/tests/__snapshots__/context.test.ts.snap @@ -9,6 +9,7 @@ exports[`the Context loaders > loads CallEnv values 1`] = ` "label": "Gladdis", "model": "gpt-8", "pres_penalty": -20, + "server": undefined, "temperature": 50, "top_p_param": 80, }, @@ -33,6 +34,7 @@ exports[`the Context loaders > loads CallEnv values 1`] = ` "liveSuffix": "Whisper", "model": "whisper-4", "readSuffix": "Gladdis", + "server": undefined, "temperature": 50, }, } @@ -53,6 +55,7 @@ exports[`the Context loaders > loads EnvVars values 1`] = ` "label": "EnvVars", "model": "gpt-4", "pres_penalty": -10, + "server": undefined, "temperature": 10, "top_p_param": 90, }, @@ -72,6 +75,7 @@ exports[`the Context loaders > loads EnvVars values 1`] = ` "liveSuffix": "EnvVars", "model": "whisper-2", "readSuffix": "EnvVars", + "server": undefined, "temperature": 10, }, } @@ -89,6 +93,7 @@ exports[`the Context loaders > loads any extra values 1`] = ` "label": "Gladdis", "model": "gpt-8", "pres_penalty": -25, + "server": undefined, "temperature": 50, "top_p_param": 80, }, @@ -102,6 +107,7 @@ exports[`the Context loaders > loads any extra values 1`] = ` "liveSuffix": "Whisper", "model": "whisper-4", "readSuffix": "Gladdis", + "server": undefined, "temperature": 50, }, } @@ -116,6 +122,7 @@ exports[`the Context loaders > loads configs values 1`] = ` "label": "Gladdis", "model": "gpt-5", "pres_penalty": -20, + "server": undefined, "temperature": 20, "top_p_param": 80, }, @@ -135,6 +142,7 @@ exports[`the Context loaders > loads configs values 1`] = ` "liveSuffix": "Whisper", "model": "whisper-3", "readSuffix": "Gladdis", + "server": undefined, "temperature": 30, }, } @@ -153,8 +161,9 @@ exports[`the Context loaders > loads default values 1`] = ` "config": undefined, "freq_penalty": 0, "label": "Gladdis", - "model": "gpt-3.5-turbo", + "model": "gpt-4o-mini", "pres_penalty": 0, + "server": undefined, "temperature": 0, "top_p_param": 100, }, @@ -176,6 +185,7 @@ exports[`the Context loaders > loads default values 1`] = ` "liveSuffix": "dictated, but not read", "model": "whisper-1", "readSuffix": "transcribed and read", + "server": undefined, "temperature": 0, }, } @@ -190,6 +200,7 @@ exports[`the Context loaders > loads input file values 1`] = ` "label": "Gladdis", "model": "gpt-7", "pres_penalty": -20, + "server": undefined, "temperature": 40, "top_p_param": 80, }, @@ -203,6 +214,7 @@ exports[`the Context loaders > loads input file values 1`] = ` "liveSuffix": "Whisper", "model": "whisper-4", "readSuffix": "Gladdis", + "server": undefined, "temperature": 40, }, } diff --git a/tests/context.test.ts b/tests/context.test.ts index 9eb407c..a2a31d4 100644 --- a/tests/context.test.ts +++ b/tests/context.test.ts @@ -120,7 +120,7 @@ describe('the Context loaders', () => { gladdis: { label: 'Gladdis', config: undefined, - model: 'gpt-3.5-turbo', + model: 'gpt-4o-mini', temperature: 0, top_p_param: 100, freq_penalty: 0,