initiate Smart Templates integration

This commit is contained in:
Brian Joseph Petro 2024-07-03 22:25:17 -04:00
parent 0482f04f1f
commit eb6a3c5085
7 changed files with 407 additions and 205 deletions

View file

@ -5,7 +5,7 @@ export class SmartMemosAudioRecordModal extends Modal {
private chunks: BlobPart[] = [];
private resolve: (value: Blob | PromiseLike<Blob>) => void;
private reject: (reason?: any) => void;
private handleAudioRecording: (audioFile: Blob | null, transcribe: boolean) => void;
private handleAudioRecording: (audioFile: Blob | null, transcribe: boolean, template: string | null) => void;
private isRecording: boolean = false;
private timer: HTMLElement;
private intervalId: number | null = null;
@ -14,7 +14,7 @@ export class SmartMemosAudioRecordModal extends Modal {
private redDot: HTMLElement;
private isResetting: boolean = false; // Flag to track reset state
constructor(app: any, handleAudioRecording: (audioFile: Blob | null, transcribe: boolean) => void) {
constructor(app: any, handleAudioRecording: (audioFile: Blob | null, transcribe: boolean, template: string | null) => void) {
super(app);
this.handleAudioRecording = handleAudioRecording;
}
@ -53,7 +53,7 @@ export class SmartMemosAudioRecordModal extends Modal {
stopButton.addEventListener('click', async () => {
const audioFile = await this.stopRecording();
this.handleAudioRecording(audioFile, false);
this.handleAudioRecording(audioFile, false, null);
});
playPauseButton.addEventListener('click', () => {
@ -73,11 +73,29 @@ export class SmartMemosAudioRecordModal extends Modal {
this.isRecording = !this.isRecording;
});
// if window.smart_env.smart_transcribe then list templates in dropdown
let template: string | null = null;
if (window.smart_env?.smart_templates) {
const transcribeDropdown = controlGroupWrapper.createEl('select', { cls: 'smart-memo-modal-button smart-memo-full-width-button smart-memo-transcribe-dropdown' });
const transcribeTemplates = window.smart_env.smart_templates.templates;
// add empty option
transcribeDropdown.createEl('option', { text: 'Select Smart Template' });
for (const template of transcribeTemplates) {
const option = transcribeDropdown.createEl('option', { text: template });
}
transcribeDropdown.addEventListener('change', (event: Event) => {
template = (event.target as HTMLSelectElement).value;
if (template === 'Select Smart Template') {
template = null;
}
});
}
const transcribeButton = controlGroupWrapper.createEl('button', { cls: 'smart-memo-modal-button smart-memo-full-width-button smart-memo-transcribe-button' });
transcribeButton.addEventListener('click', async () => {
const audioFile = await this.stopRecording();
this.handleAudioRecording(audioFile, true);
this.handleAudioRecording(audioFile, true, template);
});
setIcon(transcribeButton, 'file-text'); // Initially set to bulb

View file

@ -6,6 +6,25 @@ dotenv.config();
import fs from "fs";
import path from "path";
// Compile EJS templates into JSON
const views = {};
fs.readdir("./", (err, files) => {
if (err) {
console.error('Error reading the directory', err);
return;
}
files.forEach(file => {
if(!['.ejs', '.md'].includes(path.extname(file))) return;
const file_path = path.join("./", file);
const content = fs.readFileSync(file_path, 'utf8').replace(/\r\n/g, '\n');
views[path.basename(file, path.extname(file))] = content;
});
// console.log('views', views);
// add dist folder if not exists
if (!fs.existsSync('dist')) fs.mkdirSync('dist');
fs.writeFileSync('dist/views.json', JSON.stringify(views, null, 2));
console.log('EJS templates compiled into templates.json');
});
const banner =
`/*

356
main.ts
View file

@ -1,65 +1,66 @@
import { App, Editor, MarkdownView, normalizePath, Notice, Plugin, PluginSettingTab, requestUrl, RequestUrlParam, Setting, TAbstractFile, TFile, MarkdownPostProcessorContext } from 'obsidian';
import {SmartTemplates} from '../jsbrains/smart-templates/smart_templates.mjs';
import {
App,
Editor,
MarkdownView,
normalizePath,
Notice,
Plugin,
PluginSettingTab,
requestUrl,
RequestUrlParam,
Setting,
TAbstractFile,
TFile,
MarkdownPostProcessorContext
} from 'obsidian';
// import Obsidian from 'obsidian';
import { SmartEnv } from 'smart-environment';
import views from "./dist/views.json";
import ejs from "ejs";
import { SmartMemosAudioRecordModal } from './SmartMemosAudioRecordModal'; // Update with the correct path
import { saveFile } from 'Utils';
interface AudioPluginSettings {
model: string;
apiKey: string;
prompt: string;
includeTranscript: boolean;
}
let DEFAULT_SETTINGS: AudioPluginSettings = {
model: 'gpt-4-0613',
apiKey: '',
prompt: 'You are an expert note-making AI for obsidian who specializes in the Linking Your Thinking (LYK) strategy. The following is a transcription of recording of someone talking aloud or people in a conversation. There may be a lot of random things said given fluidity of conversation or thought process and the microphone\'s ability to pick up all audio. Give me detailed notes in markdown language on what was said in the most easy-to-understand, detailed, and conceptual format. Include any helpful information that can conceptualize the notes further or enhance the ideas, and then summarize what was said. Do not mention \"the speaker\" anywhere in your response. The notes your write should be written as if I were writting them. Finally, ensure to end with code for a mermaid chart that shows an enlightening concept map combining both the transcription and the information you added to it. The following is the transcribed audio:\n\n',
includeTranscript: true
}
const MODELS: string[] = [
'gpt-3.5-turbo-16k',
'gpt-3.5-turbo-0613',
'text-davinci-003',
'text-davinci-002',
'code-davinci-002',
'code-davinci-001',
'gpt-4-0613',
'gpt-4-32k-0613',
'gpt-4o'
];
export default class SmartMemosPlugin extends Plugin {
settings: AudioPluginSettings;
writing: boolean;
transcript: string;
writing: boolean;
transcript: string;
apiKey: string = 'sk-as123mkqwenjasdasdj12...';
apiKey: string = 'sk-as123mkqwenjasdasdj12...';
model: string = 'gpt-4-0613';
appJsonObj : any;
appJsonObj: any;
// Add a new property to store the audio file
audioFile: Blob;
async onload() {
settings: any;
active_template: string | null = null;
env: SmartEnv;
obsidian: any;
await this.loadSettings();
async onload() {
this.obsidian = {
Setting,
};
await this.load_settings();
this.env = new SmartEnv(this, {
ejs: ejs,
// settings: this.settings,
views: views,
global_ref: window,
});
const app_json = await this.app.vault.adapter.read(".obsidian/app.json");
this.appJsonObj = JSON.parse(app_json);
this.addCommand({
id: 'open-transcript-modal',
name: 'Smart transcribe',
editorCallback: (editor: Editor, view: MarkdownView) => {
this.addCommand({
id: 'open-transcript-modal',
name: 'Smart transcribe',
editorCallback: (editor: Editor, view: MarkdownView) => {
this.commandGenerateTranscript(editor);
}
});
});
this.registerMarkdownPostProcessor((el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
const audioLinks = el.querySelectorAll('a.internal-link[data-href$=".wav"]');
@ -73,13 +74,13 @@ export default class SmartMemosPlugin extends Plugin {
console.error('Failed to get the href attribute from the link element.');
return; // Skip this iteration because there's no href
}
const abstractFile = this.app.vault.getAbstractFileByPath(href);
if (!(abstractFile instanceof TFile)) {
console.error('The path does not point to a valid file in the vault.');
return; // Skip this iteration because there's no file
}
const audio = document.createElement('audio');
audio.src = this.app.vault.getResourcePath(abstractFile);
audio.controls = true;
@ -96,21 +97,41 @@ export default class SmartMemosPlugin extends Plugin {
});
// Add the audio recorder ribbon
// Update the callback for the audio recorder ribbon
// Add the audio recorder ribbon
// Update the callback for the audio recorder ribbon
this.addRibbonIcon('microphone', 'Record smart memo', async (evt: MouseEvent) => {
// Open the audio recorder and store the recorded audio
this.audioFile = await new SmartMemosAudioRecordModal(this.app, this.handleAudioRecording.bind(this)).open();
});
});
this.addSettingTab(new SmartMemosSettingTab(this.app, this));
}
this.addSettingTab(new SmartMemosSettingTab(this.app, this));
}
static get defaults() {
return {
openai: {},
chat_model_platform_key: 'openai',
system_prompt: 'You are an expert note-making AI for obsidian who specializes in the Linking Your Thinking (LYK) strategy. The following is a transcription of recording of someone talking aloud or people in a conversation. There may be a lot of random things said given fluidity of conversation or thought process and the microphone\'s ability to pick up all audio. Give me detailed notes in markdown language on what was said in the most easy-to-understand, detailed, and conceptual format. Include any helpful information that can conceptualize the notes further or enhance the ideas, and then summarize what was said. Do not mention \"the speaker\" anywhere in your response. The notes your write should be written as if I were writting them. Finally, ensure to end with code for a mermaid chart that shows an enlightening concept map combining both the transcription and the information you added to it. The following is the transcribed audio:\n\n',
includeTranscript: true
}
}
async load_settings() {
this.settings = {
...SmartMemosPlugin.defaults,
...(await this.loadData()),
};
}
async save_settings() {
await this.saveData(this.settings); // Obsidian API->saveData
await this.load_settings(); // re-load settings into memory
}
// Add a new method to handle the audio recording and processing
async handleAudioRecording(audioFile: Blob, transcribe: boolean) {
async handleAudioRecording(audioFile: Blob, transcribe: boolean, template: string | null) {
try {
if (template) this.active_template = template;
console.log('Handling audio recording:', audioFile);
if (!audioFile) {
@ -171,22 +192,22 @@ export default class SmartMemosPlugin extends Plugin {
this.transcript = result;
const prompt = this.settings.prompt + result;
new Notice('Transcript generated...');
this.generateText(prompt, editor , editor.getCursor('to').line);
this.generateText(prompt, editor, editor.getCursor('to').line);
}).catch(error => {
console.warn(error.message);
new Notice(error.message);
this.writing = false;
});
});
}
}
writeText(editor: Editor, LnToWrite: number, text: string) {
writeText(editor: Editor, LnToWrite: number, text: string) {
const newLine = this.getNextNewLine(editor, LnToWrite);
editor.setLine(newLine, '\n' + text.trim() + '\n');
return newLine;
}
getNextNewLine(editor: Editor, Ln: number) {
getNextNewLine(editor: Editor, Ln: number) {
let newLine = Ln;
while (editor.getLine(newLine).trim().length > 0) {
if (newLine == editor.lastLine()) editor.setLine(newLine, editor.getLine(newLine) + '\n');
@ -195,7 +216,7 @@ export default class SmartMemosPlugin extends Plugin {
return newLine;
}
commandGenerateTranscript(editor: Editor) {
commandGenerateTranscript(editor: Editor) {
const position = editor.getCursor();
const text = editor.getRange({ line: 0, ch: 0 }, position);
const regex = [/(?<=\[\[)(([^[\]])+)\.(mp3|mp4|mpeg|mpga|m4a|wav|webm)(?=]])/g,
@ -213,7 +234,7 @@ export default class SmartMemosPlugin extends Plugin {
this.transcript = result;
const prompt = this.settings.prompt + result;
new Notice('Transcript generated...');
this.generateText(prompt, editor , editor.getCursor('to').line);
this.generateText(prompt, editor, editor.getCursor('to').line);
}).catch(error => {
console.warn(error.message);
new Notice(error.message);
@ -226,9 +247,9 @@ export default class SmartMemosPlugin extends Plugin {
});
}
async generateTranscript(audioBuffer: ArrayBuffer, filetype: string) {
async generateTranscript(audioBuffer: ArrayBuffer, filetype: string) {
if (this.settings.apiKey.length <= 1) throw new Error('OpenAI API Key is not provided.');
// Reference: www.stackoverflow.com/questions/74276173/how-to-send-multipart-form-data-payload-with-typescript-obsidian-library
const N = 16 // The length of our random boundry string
const randomBoundryString = 'WebKitFormBoundary' + Array(N + 1).join((Math.random().toString(36) + '00000000000000000').slice(2, 18)).slice(0, N)
@ -236,13 +257,13 @@ export default class SmartMemosPlugin extends Plugin {
const post_string = `\r\n------${randomBoundryString}\r\nContent-Disposition: form-data; name="model"\r\n\r\nwhisper-1\r\n------${randomBoundryString}--\r\n`
const pre_string_encoded = new TextEncoder().encode(pre_string);
const post_string_encoded = new TextEncoder().encode(post_string);
// Calculate the size of each chunk
const chunkSize = 20 * 1024 * 1024; // 15 MB
// Calculate the number of chunks
const numChunks = Math.ceil(audioBuffer.byteLength / chunkSize);
if (numChunks < 2) {
new Notice(`Transcribing audio...`);
} else {
@ -255,19 +276,19 @@ export default class SmartMemosPlugin extends Plugin {
// Process each chunk
for (let i = 0; i < numChunks; i++) {
new Notice(`Transcribing chunk #${i + 1}...`);
// Get the start and end indices for this chunk
const start = i * chunkSize;
const end = Math.min(start + chunkSize, audioBuffer.byteLength);
// Extract the chunk from the audio buffer
const chunk = audioBuffer.slice(start, end);
// Concatenate the chunk with the pre and post strings
const concatenated = await new Blob([pre_string_encoded, chunk, post_string_encoded]).arrayBuffer()
const options: RequestUrlParam = {
url: 'https://api.openai.com/v1/audio/transcriptions',
method: 'POST',
@ -277,12 +298,12 @@ export default class SmartMemosPlugin extends Plugin {
},
body: concatenated
};
const response = await requestUrl(options).catch((error) => {
const response = await requestUrl(options).catch((error) => {
if (error.message.includes('401')) throw new Error('OpenAI API Key is not valid.');
else throw error;
else throw error;
});
if ('text' in response.json) {
// Add the result to the results array
results.push(response.json.text);
@ -299,18 +320,18 @@ export default class SmartMemosPlugin extends Plugin {
async findFilePath(text: string, regex: RegExp[]) {
let filename = '';
let result: RegExpExecArray | null;
// Find the filename using the provided regex patterns
for (const reg of regex) {
while ((result = reg.exec(text)) !== null) {
filename = normalizePath(decodeURI(result[0])).trim();
}
}
if (filename === '') {
throw new Error('No file found in the text.');
}
let fullPath;
if (filename.includes(this.appJsonObj.attachmentFolderPath)) {
fullPath = filename;
@ -320,7 +341,7 @@ export default class SmartMemosPlugin extends Plugin {
// Ensure no leading or trailing slashes in the attachment folder path and filename
const folderPath = this.appJsonObj.attachmentFolderPath.replace(/\/$/, ''); // Remove trailing slash if any
const filePath = filename.replace(/^\//, ''); // Remove leading slash if any
// Construct the full path
fullPath = `${folderPath}/${filePath}`;
fullPath = normalizePath(fullPath); // Normalize the full path
@ -328,7 +349,7 @@ export default class SmartMemosPlugin extends Plugin {
console.log('full path 2: ', fullPath);
}
// Attempt to find the file in the vault
const file = this.app.vault.getAbstractFileByPath(fullPath);
if (file instanceof TFile) {
@ -338,11 +359,11 @@ export default class SmartMemosPlugin extends Plugin {
}
}
async generateText(prompt: string, editor: Editor, currentLn: number, contextPrompt?: string) {
async generateText(prompt: string, editor: Editor, currentLn: number, contextPrompt?: string) {
if (prompt.length < 1) throw new Error('Cannot find prompt.');
if ( this.settings.apiKey.length <= 1) throw new Error('OpenAI API Key is not provided.');
if (this.settings.apiKey.length <= 1) throw new Error('OpenAI API Key is not provided.');
prompt = prompt + '.';
prompt = prompt + '.';
let newPrompt = prompt;
@ -353,102 +374,101 @@ export default class SmartMemosPlugin extends Plugin {
content: newPrompt,
});
new Notice(`Performing customized superhuman analysis...`);
new Notice(`Performing customized superhuman analysis...`);
let LnToWrite = this.getNextNewLine(editor, currentLn);
const template = "# Summary\n<%- summary %>\n# Notes\n<%- notes %>\n# Mermaid\n```mermaid\n<%- mermaid %>\n```";
const smart_templates = new SmartTemplates({
settings: {
smart_templates: {
var_prompts: {
'summary': {prompt: 'Write a summary paragraph of the transcript.'},
'notes': {prompt: 'Write concise notes on the transcript.'},
'mermaid': {prompt: 'Create a mermaid chart code that complements the outline.'}
},
api_key: this.settings.apiKey,
if (this.active_template && window.smart_env?.smart_templates) {
const resp = await window.smart_env.smart_templates.render(this.active_template, this.transcript);
console.log('resp: ', resp);
editor.setLine(LnToWrite, editor.getLine(LnToWrite) + resp);
} else {
let lastLine = LnToWrite;
this.env.chunk_handler = (chunk: string) => {
editor.setLine(LnToWrite, editor.getLine(LnToWrite) + chunk);
if (chunk.includes('\n')) {
LnToWrite = this.getNextNewLine(editor, LnToWrite);
}
}
})
const resp = await smart_templates.render(template, this.transcript);
console.log('resp: ', resp);
editor.setLine(LnToWrite, editor.getLine(LnToWrite) + resp);
};
this.env.done_handler = (final_resp: string) => {
LnToWrite = this.getNextNewLine(editor, lastLine);
if (this.settings.includeTranscript) {
editor.setLine(LnToWrite, editor.getLine(LnToWrite) + '\n# Transcript\n' + this.transcript);
}
};
const smart_chat_model = new SmartChatModel(
this.env,
"openai",
{
api_key: this.settings.apiKey,
model: this.settings.model,
}
);
await smart_chat_model.complete({ messages: messages });
}
this.writing = false;
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class SmartMemosSettingTab extends PluginSettingTab {
plugin: SmartMemosPlugin;
constructor(app: App, plugin: SmartMemosPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
let {containerEl} = this;
containerEl.empty();
new Setting(containerEl)
.setName('OpenAI api key')
.setDesc('Ex: sk-as123mkqwenjasdasdj12...')
.addText(text => text
.setPlaceholder(DEFAULT_SETTINGS.apiKey)
.setValue(this.plugin.settings.apiKey)
.onChange(async (value) => {
// console.log('API Key: ' + value);
this.plugin.settings.apiKey = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Model')
.setDesc('Select the model to use for note-generation')
.addDropdown(dropdown => {
dropdown.addOptions(MODELS.reduce((models: {[key: string]: string}, model) => {
models[model] = model;
return models;
}, {}));
dropdown.setValue(this.plugin.settings.model);
dropdown.onChange(async (value) => {
// console.log('Model: ' + value);
this.plugin.settings.model = value;
await this.plugin.saveSettings();
});
});
new Setting(containerEl)
.setName('Custom transcription-to-notes prompt')
.setDesc('Prompt that will be sent to Chatpgt right before adding your transcribed audio')
.addTextArea(text => {
if (text.inputEl) {
text.inputEl.classList.add('smart-memo-text-box');
}
text.setPlaceholder(
'Act as my personal secretary and worlds greatest entreprenuer and know I will put these notes in my personal obsidian where I have all my notes linked by categories, tags, etc. The following is a transcription of recording of someone talking aloud or people in a conversation. May be a lot of random things that are said given fluidity of conversation and the microphone ability to pick up all audio. Make outline of all topics and points within a structured hierarchy. Make sure to include any quantifiable information said such as the cost of headphones being $400. Then go into to detail with summaries that explain things more eloquently. Finally, Create a mermaid chart code that complements the outline.\n\n')
.setValue(this.plugin.settings.prompt)
.onChange(async (value) => {
this.plugin.settings.prompt = value;
await this.plugin.saveSettings();
})});
new Setting(containerEl)
.setName('Include Transcript')
.setDesc('Toggle this setting if you want to include the raw transcript on top of custom notes.')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.includeTranscript)
.onChange(async (value) => {
this.plugin.settings.includeTranscript = value;
await this.plugin.saveSettings();
}));
}
plugin: SmartMemosPlugin;
smart_settings: SmartMemosSettings;
config: any;
constructor(app: App, plugin: SmartMemosPlugin) {
super(app, plugin);
this.plugin = plugin;
this.config = plugin.settings;
}
display() {
this.smart_settings = new SmartMemosSettings(this.plugin.env, this.containerEl, "settings");
return this.smart_settings.render();
}
}
import { SmartChatModel } from "smart-chat-model";
import { SmartSettings } from "smart-setting";
// Smart Templates Specific Settings
class SmartMemosSettings extends SmartSettings {
get settings() { return this.env.smart_memos_plugin.settings; }
set settings(settings) {
this.env.smart_memos_plugin.settings = settings.smart_memos_plugin || settings;
}
get model_config() { return this.settings[this.settings.chat_model_platform_key]; }
async get_view_data() {
// get chat platforms
const chat_platforms = SmartChatModel.platforms;
console.log(chat_platforms);
const smart_chat_model = new SmartChatModel(
this.env,
this.settings.chat_model_platform_key || 'openai',
this.model_config,
)
const platform_chat_models = await smart_chat_model.get_models();
console.log(platform_chat_models);
return {
chat_platforms,
platform_chat_models,
chat_platform: smart_chat_model.platform,
settings: this.settings,
};
}
get template() { return this.env.views[this.template_name]; }
async changed_smart_chat_platform(render = true) {
if (render) this.render();
}
// import model config from smart-connections
async import_model_config_from_smart_connections() {
const config_file = await this.main.app.vault.adapter.read('.obsidian/plugins/smart-connections/data.json');
if (!config_file) return new Notice("[Smart Templates] No model config found in smart-connections");
const config = JSON.parse(config_file);
const settings = this.settings;
SmartChatModel.platforms.forEach(platform => {
if (config[platform.key]) settings[platform.key] = config[platform.key];
});
if (config.chat_model_platform_key) settings.chat_model_platform_key = config.chat_model_platform_key;
this.settings = settings;
await this.main.save_settings();
this.render();
}
}

141
package-lock.json generated
View file

@ -1,16 +1,20 @@
{
"name": "smart-memos",
"version": "1.0.10",
"version": "1.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "smart-memos",
"version": "1.0.10",
"version": "1.1.1",
"license": "MIT",
"dependencies": {
"bootstrap": "^5.3.3",
"smart-chat-model": "^1.0.5",
"ejs": "^3.1.10",
"smart-chat-model": "file:../jsbrains/smart-chat-model",
"smart-environment": "file:../jsbrains/smart-environment",
"smart-setting": "file:../jsbrains/smart-setting",
"smart-templates": "file:../jsbrains/smart-templates",
"wavesurfer.js": "^7.8.0"
},
"devDependencies": {
@ -26,6 +30,31 @@
"typescript": "4.7.4"
}
},
"../jsbrains/smart-chat-model": {
"version": "1.0.5",
"license": "MIT",
"devDependencies": {
"ava": "^6.1.2",
"esbuild": "^0.20.2"
}
},
"../jsbrains/smart-environment": {
"version": "1.0.0",
"license": "MIT"
},
"../jsbrains/smart-setting": {
"version": "1.0.1",
"license": "MIT"
},
"../jsbrains/smart-templates": {
"version": "1.0.1",
"license": "MIT",
"dependencies": {
"ava": "^6.1.3",
"dotenv": "^16.4.5",
"ejs": "^3.1.10"
}
},
"node_modules/@codemirror/state": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
@ -1080,8 +1109,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"peer": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@ -1108,12 +1135,15 @@
"node": ">=8"
}
},
"node_modules/async": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"peer": true
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/bootstrap": {
"version": "5.3.3",
@ -1137,8 +1167,6 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"peer": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -1182,8 +1210,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"peer": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@ -1199,8 +1225,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"peer": true,
"dependencies": {
"color-name": "~1.1.4"
},
@ -1211,16 +1235,12 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"peer": true
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"peer": true
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/cross-spawn": {
"version": "7.0.3",
@ -1298,6 +1318,20 @@
"url": "https://dotenvx.com"
}
},
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/esbuild": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz",
@ -1635,6 +1669,33 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"dependencies": {
"minimatch": "^5.0.1"
}
},
"node_modules/filelist/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -1781,8 +1842,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=8"
}
@ -1889,6 +1948,23 @@
"dev": true,
"peer": true
},
"node_modules/jake": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz",
"integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==",
"dependencies": {
"async": "^3.2.3",
"chalk": "^4.0.2",
"filelist": "^1.0.4",
"minimatch": "^3.1.2"
},
"bin": {
"jake": "bin/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@ -1996,8 +2072,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"peer": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -2322,9 +2396,20 @@
}
},
"node_modules/smart-chat-model": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/smart-chat-model/-/smart-chat-model-1.0.5.tgz",
"integrity": "sha512-IWFt0Y8Zn9+ZucJtmk+SdqTaIlW8nbMUQjkrn0FSNeavP978jaI7g8pze+NxnYfhn7kC26MFxx4FUC56unt7/g=="
"resolved": "../jsbrains/smart-chat-model",
"link": true
},
"node_modules/smart-environment": {
"resolved": "../jsbrains/smart-environment",
"link": true
},
"node_modules/smart-setting": {
"resolved": "../jsbrains/smart-setting",
"link": true
},
"node_modules/smart-templates": {
"resolved": "../jsbrains/smart-templates",
"link": true
},
"node_modules/strip-ansi": {
"version": "6.0.1",
@ -2363,8 +2448,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},

View file

@ -36,7 +36,11 @@
},
"dependencies": {
"bootstrap": "^5.3.3",
"smart-chat-model": "^1.0.5",
"ejs": "^3.1.10",
"smart-chat-model": "file:../jsbrains/smart-chat-model",
"smart-environment": "file:../jsbrains/smart-environment",
"smart-setting": "file:../jsbrains/smart-setting",
"smart-templates": "file:../jsbrains/smart-templates",
"wavesurfer.js": "^7.8.0"
}
}

56
settings.ejs Normal file
View file

@ -0,0 +1,56 @@
<div class="smart-settings" id="smart-memos-settings">
<h1>Smart Memos</h1>
<div class="setting-component"
data-name="Text Generation Model Platform"
data-setting="chat_model_platform_key"
data-type="dropdown"
data-description="Select a model platform to use with Smart Chat."
data-callback="changed_smart_chat_platform"
<%- chat_platforms.map((platform, i) => `data-option-${i + 1}="${platform.key}|${platform.description}"`).join('\n') %>
></div>
<% if(chat_platform?.fetch_models) { %>
<% if(settings[settings.chat_model_platform_key]?.api_key) { %>
<div class="setting-component"
data-name="Model Name"
data-type="dropdown"
data-setting="<%= settings.chat_model_platform_key %>.model_name"
<%- platform_chat_models.map((model, i) => `data-option-${i}="${model.key}|${model.model_name} (${model.description})"`).join('\n') %>
></div>
<% } %>
<% if(!platform_chat_models.length) { %>
<div class="setting-component"
data-name="Refresh Models List"
data-type="button"
data-callback="changed_smart_chat_platform"
></div>
<% } %>
<div class="setting-component"
data-name="Import Model Config from Smart Connections"
data-type="button"
data-callback="import_model_config_from_smart_connections"
></div>
<div class="setting-component"
data-name="<%= chat_platform.description %> API Key"
data-type="text"
data-setting="<%= settings.chat_model_platform_key %>.api_key"
<% if(chat_platform.signup_url) { %>
data-description="<a href='<%= chat_platform.signup_url %>'>Get API Key</a> for <%= chat_platform.description %>."
<% } else { %>
data-description="API Key for <%= chat_platform.description %>."
<% } %>
data-placeholder="Enter an API Key"
data-button="Save"
data-callback="changed_smart_chat_platform"
></div>
<% } %>
<div class="setting-component"
data-name="Custom transcription-to-notes prompt"
data-type="textarea"
data-setting="system_prompt"
></div>
<div class="setting-component"
data-name="Include Transcript"
data-type="toggle"
data-setting="includeTranscript"
></div>
</div>

View file

@ -1,6 +1,8 @@
.smart-memo-text-box {
width: 350px;
height: 400px;
.smart-settings{
textarea {
width: 350px;
height: 400px;
}
}